Вот мой проект с открытым исходным кодом: https://github.com/Skewjo/SysLat_Software
Сегодня я хотел бы попытаться очистить мою реализацию USB.
Заголовочный файл здесь: https://github.com/Skewjo/SysLat_Software/blob/master/USBController.h
Реализация здесь: https://github.com/Skewjo/SysLat_Software/blob/master/USBController.cpp
Определение и использование здесь: https://github.com/Skewjo/SysLat_Software/blob/master/SysLat_SoftwareDlg.cpp
#pragma once
#ifndef USBCONTROLLER_H
#define USBCONTROLLER_H
struct SSerInfo {
SSerInfo() : bUsbDevice(FALSE) {}
CString strDevPath; // Device path for use with CreateFile()
CString strPortName; // Simple name (i.e. COM1)
CString strFriendlyName; // Full name to be displayed to a user
BOOL bUsbDevice; // Provided through a USB connection?
CString strPortDesc; // friendly name without the COMx
};
class CUSBController
{
/*
protected:
//These vars aren't actually in use anywhere yet...
const CString& PortSpecifier;
DWORD BaudRate = CBR_9600; //9600 Baud
/*other Baud options:
CBR_110 110
CBR_300 300
CBR_600 600
CBR_1200 1200
CBR_2400 2400
CBR_4800 4800
CBR_9600 9600
CBR_14400 14400
CBR_19200 19200
CBR_38400 38400
CBR_56000 56000
CBR_57600 57600
CBR_115200 115200
CBR_128000 128000
CBR_256000 256000
BYTE ByteSize = 8; //8 data bits
BYTE Parity = NOPARITY; //no parity
BYTE StopBits = ONESTOPBIT; //1 stop
*/
//---------------------------------------------------------------
// Helpers for enumerating the available serial ports.
// These throw a CString on failure, describing the nature of
// the error that occurred.
void EnumPortsWdm(CArray<SSerInfo, SSerInfo&>& asi);
void EnumPortsWNt4(CArray<SSerInfo, SSerInfo&>& asi);
void EnumPortsW9x(CArray<SSerInfo, SSerInfo&>& asi);
void SearchPnpKeyW9x(HKEY hkPnp, BOOL bUsbDevice, CArray<SSerInfo, SSerInfo&>& asi);
public:
HANDLE OpenComPort(const CString& PortSpecifier);
void CloseComPort(HANDLE hPort);
bool IsComPortOpened(HANDLE hPort);
int ReadByte(HANDLE port);
//1-6-21
//This only half works on my system, so I also have to use "_WINBASE_::GetCommPorts()", but it does give me the "friendly name", which is what I really need.
void EnumSerialPorts(CArray<SSerInfo, SSerInfo&>& asi, BOOL bIgnoreBusyPorts);
};
#endif
#include "stdafx.h"
#include "USBController.h"
//#include <objbase.h>
//#include <initguid.h>
#include <Setupapi.h>
#include <ntddser.h>
#pragma comment(lib, "Setupapi.lib")
HANDLE CUSBController::OpenComPort(const CString& PortSpecifier)
{
HANDLE hPort = CreateFile(PortSpecifier, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPort == INVALID_HANDLE_VALUE)
return INVALID_HANDLE_VALUE;
PurgeComm(hPort, PURGE_RXCLEAR);
DCB dcb = { 0 };
if (!GetCommState(hPort, &dcb))
{
CloseHandle(hPort);
return INVALID_HANDLE_VALUE;
}
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(hPort, &dcb))
{
CloseHandle(hPort);
return INVALID_HANDLE_VALUE;
}
SetCommMask(hPort, EV_RXCHAR | EV_ERR); //receive character event
// Read this carefully because timeouts are important
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
COMMTIMEOUTS timeouts = { 0 };
return hPort;
}
void CUSBController::CloseComPort(HANDLE hPort)
{
PurgeComm(hPort, PURGE_RXCLEAR);
CloseHandle(hPort);
}
bool CUSBController::IsComPortOpened(HANDLE hPort)
{
return hPort != INVALID_HANDLE_VALUE;
}
int CUSBController::ReadByte(HANDLE hPort)
{
int retVal;
BYTE Byte;
DWORD dwBytesTransferred;
if (FALSE == ReadFile(hPort, &Byte, 1, &dwBytesTransferred, 0)) //read 1
retVal = 0x101;
retVal = Byte;
return retVal;
}
/*************************************************************************
* Serial port enumeration routines
*
* The EnumSerialPort function will populate an array of SSerInfo structs,
* each of which contains information about one serial port present in
* the system. Note that this code must be linked with setupapi.lib,
* which is included with the Win32 SDK.
*
* by Zach Gorman <gormanjz@hotmail.com>
*
* Copyright (c) 2002 Archetype Auction Software, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following condition is
* met: Redistributions of source code must retain the above copyright
* notice, this condition and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ARCHETYPE AUCTION SOFTWARE OR ITS
* AFFILIATES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************/
// The following define is from ntddser.h in the DDK. It is also
// needed for serial port enumeration.
/*#ifndef GUID_CLASS_COMPORT
DEFINE_GUID(GUID_CLASS_COMPORT, 0x86e0d1e0L, 0x8089, 0x11d0, 0x9c, 0xe4,
0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73);
#endif*/
//---------------------------------------------------------------
// Routine for enumerating the available serial ports.
// Throws a CString on failure, describing the error that
// occurred. If bIgnoreBusyPorts is TRUE, ports that can't
// be opened for read/write access are not included.
void CUSBController::EnumSerialPorts(CArray<SSerInfo, SSerInfo&>& asi, BOOL bIgnoreBusyPorts)
{
// Clear the output array
asi.RemoveAll();
// Use different techniques to enumerate the available serial
// ports, depending on the OS we're using
OSVERSIONINFO vi;
vi.dwOSVersionInfoSize = sizeof(vi);
if (!::GetVersionEx(&vi)) {
CString str;
str.Format("Could not get OS version. (err=%lx)",
GetLastError());
throw str;
}
// Handle windows 9x and NT4 specially
if (vi.dwMajorVersion < 5) {
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
EnumPortsWNt4(asi);
else
EnumPortsW9x(asi);
}
else {
// Win2k and later support a standard API for
// enumerating hardware devices.
EnumPortsWdm(asi);
}
for (int ii = 0; ii < asi.GetSize(); ii++)
{
SSerInfo& rsi = asi[ii];
if (bIgnoreBusyPorts) {
// Only display ports that can be opened for read/write
HANDLE hCom = CreateFile(rsi.strDevPath,
GENERIC_READ | GENERIC_WRITE,
0, /* comm devices must be opened w/exclusive-access */
NULL, /* no security attrs */
OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
0, /* not overlapped I/O */
NULL /* hTemplate must be NULL for comm devices */
);
if (hCom == INVALID_HANDLE_VALUE) {
// It can't be opened; remove it.
asi.RemoveAt(ii);
ii--;
continue;
}
else {
// It can be opened! Close it and add it to the list
::CloseHandle(hCom);
}
}
// Come up with a name for the device.
// If there is no friendly name, use the port name.
if (rsi.strFriendlyName.IsEmpty())
rsi.strFriendlyName = rsi.strPortName;
// If there is no description, try to make one up from
// the friendly name.
if (rsi.strPortDesc.IsEmpty()) {
// If the port name is of the form "ACME Port (COM3)"
// then strip off the " (COM3)"
rsi.strPortDesc = rsi.strFriendlyName;
int startdex = rsi.strPortDesc.Find(" (");
int enddex = rsi.strPortDesc.Find(")");
if (startdex > 0 && enddex ==
(rsi.strPortDesc.GetLength() - 1))
rsi.strPortDesc = rsi.strPortDesc.Left(startdex);
}
}
}
// Helpers for EnumSerialPorts
void CUSBController::EnumPortsWdm(CArray<SSerInfo, SSerInfo&>& asi)
{
CString strErr;
// Create a device information set that will be the container for
// the device interfaces.
GUID* guidDev = (GUID*)&GUID_CLASS_COMPORT;
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA* pDetData = NULL;
try {
hDevInfo = SetupDiGetClassDevs(guidDev,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
strErr.Format("SetupDiGetClassDevs failed. (err=%lx)",
GetLastError());
throw strErr;
}
// Enumerate the serial ports
BOOL bOk = TRUE;
SP_DEVICE_INTERFACE_DATA ifcData;
DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA*) new char[dwDetDataSize];
// This is required, according to the documentation. Yes,
// it's weird.
ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
for (DWORD ii = 0; bOk; ii++) {
bOk = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guidDev, ii, &ifcData);
if (bOk) {
// Got a device. Get the details.
SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
bOk = SetupDiGetDeviceInterfaceDetail(hDevInfo, &ifcData, pDetData, dwDetDataSize, NULL, &devdata);
if (bOk) {
CString strDevPath(pDetData->DevicePath);
// Got a path to the device. Try to get some more info.
CHAR fname[256];
CHAR desc[256];
BOOL bSuccess = SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname, sizeof(fname), NULL);
bSuccess = bSuccess && SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_DEVICEDESC, NULL, (PBYTE)desc, sizeof(desc), NULL);
BOOL bUsbDevice = FALSE;
CHAR locinfo[256];
if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_LOCATION_INFORMATION, NULL, (PBYTE)locinfo, sizeof(locinfo), NULL))
{
// Just check the first three characters to determine
// if the port is connected to the USB bus. This isn't
// an infallible method; it would be better to use the
// BUS GUID. Currently, Windows doesn't let you query
// that though (SPDRP_BUSTYPEGUID seems to exist in
// documentation only).
bUsbDevice = (strncmp(locinfo, "USB", 3) == 0);
}
if (bSuccess) {
// Add an entry to the array
SSerInfo si;
si.strDevPath = strDevPath;
si.strFriendlyName = fname;
si.strPortDesc = desc;
si.bUsbDevice = bUsbDevice;
asi.Add(si);
}
}
else {
strErr.Format("SetupDiGetDeviceInterfaceDetail failed. (err=%lx)",
GetLastError());
throw strErr;
}
}
else {
DWORD err = GetLastError();
if (err != ERROR_NO_MORE_ITEMS) {
strErr.Format("SetupDiEnumDeviceInterfaces failed. (err=%lx)", err);
throw strErr;
}
}
}
}
catch (CString strCatchErr) {
strErr = strCatchErr;
}
if (pDetData != NULL)
delete[](char*)pDetData;
if (hDevInfo != INVALID_HANDLE_VALUE)
SetupDiDestroyDeviceInfoList(hDevInfo);
if (!strErr.IsEmpty())
throw strErr;
}
void CUSBController::EnumPortsWNt4(CArray<SSerInfo, SSerInfo&>& asi)
{
// NT4's driver model is totally different, and not that
// many people use NT4 anymore. Just try all the COM ports
// between 1 and 16
SSerInfo si;
for (int ii = 1; ii <= 16; ii++) {
CString strPort;
strPort.Format("COM%d", ii);
si.strDevPath = CString("\\.\") + strPort;
si.strPortName = strPort;
asi.Add(si);
}
}
void CUSBController::EnumPortsW9x(CArray<SSerInfo, SSerInfo&>& asi)
{
// Look at all keys in HKLMEnum, searching for subkeys named
// *PNP0500 and *PNP0501. Within these subkeys, search for
// sub-subkeys containing value entries with the name "PORTNAME"
// Search all subkeys of HKLMEnumUSBPORTS for PORTNAME entries.
// First, open HKLMEnum
HKEY hkEnum = NULL;
HKEY hkSubEnum = NULL;
HKEY hkSubSubEnum = NULL;
try {
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Enum", 0, KEY_READ,
&hkEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\Enum");
// Enumerate the subkeys of HKLMEnum
char acSubEnum[128];
DWORD dwSubEnumIndex = 0;
DWORD dwSize = sizeof(acSubEnum);
while (RegEnumKeyEx(hkEnum, dwSubEnumIndex++, acSubEnum, &dwSize,
NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
HKEY hkSubEnum = NULL;
if (RegOpenKeyEx(hkEnum, acSubEnum, 0, KEY_READ,
&hkSubEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\Enum\") + acSubEnum;
// Enumerate the subkeys of HKLMEnum*, looking for keys
// named *PNP0500 and *PNP0501 (or anything in USBPORTS)
BOOL bUsbDevice = (strcmp(acSubEnum, "USBPORTS") == 0);
char acSubSubEnum[128];
dwSize = sizeof(acSubSubEnum); // set the buffer size
DWORD dwSubSubEnumIndex = 0;
while (RegEnumKeyEx(hkSubEnum, dwSubSubEnumIndex++, acSubSubEnum,
&dwSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
BOOL bMatch = (strcmp(acSubSubEnum, "*PNP0500") == 0 ||
strcmp(acSubSubEnum, "*PNP0501") == 0 ||
bUsbDevice);
if (bMatch) {
HKEY hkSubSubEnum = NULL;
if (RegOpenKeyEx(hkSubEnum, acSubSubEnum, 0, KEY_READ,
&hkSubSubEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\Enum\") +
acSubEnum + "\" + acSubSubEnum;
SearchPnpKeyW9x(hkSubSubEnum, bUsbDevice, asi);
RegCloseKey(hkSubSubEnum);
hkSubSubEnum = NULL;
}
dwSize = sizeof(acSubSubEnum); // restore the buffer size
}
RegCloseKey(hkSubEnum);
hkSubEnum = NULL;
dwSize = sizeof(acSubEnum); // restore the buffer size
}
}
catch (CString strError) {
if (hkEnum != NULL)
RegCloseKey(hkEnum);
if (hkSubEnum != NULL)
RegCloseKey(hkSubEnum);
if (hkSubSubEnum != NULL)
RegCloseKey(hkSubSubEnum);
throw strError;
}
RegCloseKey(hkEnum);
}
void CUSBController::SearchPnpKeyW9x(HKEY hkPnp, BOOL bUsbDevice, CArray<SSerInfo, SSerInfo&>& asi)
{
// Enumerate the subkeys of the given PNP key, looking for values with
// the name "PORTNAME"
// First, open HKLMEnum
HKEY hkSubPnp = NULL;
try {
// Enumerate the subkeys of HKLMEnum*PNP050[01]
char acSubPnp[128];
DWORD dwSubPnpIndex = 0;
DWORD dwSize = sizeof(acSubPnp);
while (RegEnumKeyEx(hkPnp, dwSubPnpIndex++, acSubPnp, &dwSize,
NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
HKEY hkSubPnp = NULL;
if (RegOpenKeyEx(hkPnp, acSubPnp, 0, KEY_READ,
&hkSubPnp) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\Enum\...\")
+ acSubPnp;
// Look for the PORTNAME value
char acValue[128];
dwSize = sizeof(acValue);
if (RegQueryValueEx(hkSubPnp, "PORTNAME", NULL, NULL, (BYTE*)acValue,
&dwSize) == ERROR_SUCCESS)
{
CString strPortName(acValue);
// Got the portname value. Look for a friendly name.
CString strFriendlyName;
dwSize = sizeof(acValue);
if (RegQueryValueEx(hkSubPnp, "FRIENDLYNAME", NULL, NULL, (BYTE*)acValue,
&dwSize) == ERROR_SUCCESS)
strFriendlyName = acValue;
// Prepare an entry for the output array.
SSerInfo si;
si.strDevPath = CString("\\.\") + strPortName;
si.strPortName = strPortName;
si.strFriendlyName = strFriendlyName;
si.bUsbDevice = bUsbDevice;
// Overwrite duplicates.
BOOL bDup = FALSE;
for (int ii = 0; ii < asi.GetSize() && !bDup; ii++)
{
if (asi[ii].strPortName == strPortName) {
bDup = TRUE;
asi[ii] = si;
}
}
if (!bDup) {
// Add an entry to the array
asi.Add(si);
}
}
RegCloseKey(hkSubPnp);
hkSubPnp = NULL;
dwSize = sizeof(acSubPnp); // restore the buffer size
}
}
catch (CString strError) {
if (hkSubPnp != NULL)
RegCloseKey(hkSubPnp);
throw strError;
}
}
И наконец, вот одна из функций в SysLat_SoftwareDlg.cpp, который, как мне кажется, вызывает у меня проблемы, когда используется класс:
void CSysLat_SoftwareDlg::R_DynamicComPortMenu()
{
CMenu* MainMenu = GetMenu();
CMenu* SettingsMenu = MainMenu->GetSubMenu(1);
CMenu* ComPortMenu = SettingsMenu->GetSubMenu(0);
if (ComPortMenu)
{
BOOL appended = false;
BOOL deleted = false;
m_COMPortCount = 0;
for(auto i = 0; i < m_COMPortInfo.GetSize(); i++) {
ComPortMenu->DeleteMenu(ID_COMPORT_START + m_COMPortCount, MF_BYCOMMAND);
if (m_COMPortCount < ID_COMPORT_END - ID_COMPORT_START) {
string usb_info = m_COMPortInfo[i].strFriendlyName;
DEBUG_PRINT("Friendly Name: " + usb_info)
appended = ComPortMenu->AppendMenu(MF_STRING, ID_COMPORT_START + m_COMPortCount, m_COMPortInfo[i].strFriendlyName);
string menuString = m_COMPortInfo[i].strFriendlyName;
size_t pos = menuString.rfind("(");
menuString.replace(0, pos + 1, "");
pos = menuString.rfind(")");
menuString.replace(pos, menuString.size(), "");
if (strcmp(menuString.c_str(), SysLatOpt.m_PortSpecifier.c_str()) == 0) {
MainMenu->CheckMenuItem(ID_COMPORT_START + m_COMPortCount, MF_CHECKED);
}
m_COMPortCount++;
}
else { //catch or throw errors here maybe?
break;
}
}
deleted = ComPortMenu->DeleteMenu(ID_USBPORT_PLACEHOLDER, MF_BYCOMMAND);
DEBUG_PRINT(("String appended: " + to_string(appended)).c_str())
DEBUG_PRINT(("Placeholder deleted: " + to_string(deleted)).c_str())
}
DrawMenuBar();
}