Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/162.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 带有虚拟COM端口的USB串行设备-如果将CreateFile()与USB路径一起使用,则ReadFile()读取零字节_C++_Winapi_Usb - Fatal编程技术网

C++ 带有虚拟COM端口的USB串行设备-如果将CreateFile()与USB路径一起使用,则ReadFile()读取零字节

C++ 带有虚拟COM端口的USB串行设备-如果将CreateFile()与USB路径一起使用,则ReadFile()读取零字节,c++,winapi,usb,C++,Winapi,Usb,我有一个销售点应用程序,它使用串行通信端口(RS-232)与称重产品的秤进行通信。我现在正致力于能够直接支持USB设备,而不是使用虚拟串行通信端口,因为它们有移动的恼人倾向 我们发现,虽然Windows 7似乎可以自动创建虚拟串行通信端口,但其他版本的Windows(如POS Ready 7)可能不会。我们怀疑这是由于POS Ready 7中缺少Windows 7的特定.inf文件造成的。有人能证实吗 我有一个间歇工作的USB示例应用程序。我与ReadFile()Windows API函数的US

我有一个销售点应用程序,它使用串行通信端口(RS-232)与称重产品的秤进行通信。我现在正致力于能够直接支持USB设备,而不是使用虚拟串行通信端口,因为它们有移动的恼人倾向

我们发现,虽然Windows 7似乎可以自动创建虚拟串行通信端口,但其他版本的Windows(如POS Ready 7)可能不会。我们怀疑这是由于POS Ready 7中缺少Windows 7的特定.inf文件造成的。有人能证实吗

我有一个间歇工作的USB示例应用程序。我与
ReadFile()
Windows API函数的USB级通信有问题。我正在使用
CreateFile()
指定USB设备路径以获取I/O句柄,然后使用
WriteFile()
ReadFile()
与电子秤通信。在某些情况下,
ReadFile()
不提供数据

背景信息

我使用的是Brecknell 67xx台秤,它使用虚拟串行通信端口,直接与销售点应用程序一起开箱即用。我用USB电缆将电子秤连接到我的Windows 7电脑,Windows自动安装了驱动程序,创建了一个虚拟串行端口,在我的例子中是COM4。然后,我将应用程序配置为通过COM4与秤对话,一切正常

使用电子秤的协议是向电子秤发送一个两字节的命令“W\r”(大写字母W后跟回车字符),然后读取一个16字节的响应,该响应包含当前重量以及有关电子秤机械装置(如运动中)的状态信息

我正在学习的示例USB应用程序将成功地提供一个权重。然后,它将停止正常工作,其行为为
ReadFile()
返回零字节读取。一旦停止工作,它将继续无法从
ReadFile()
提供数据,即使我拔下并重新插入USB电缆或重新启动电脑

先前版本的学习应用程序挂起在
ReadFile()
上,当Visual Studio完成全部中断时,将显示一个暂停,然后显示一条指示死锁的消息。但是,自从我开始在
ReadTotalTimeoutConstant
中使用带有5000毫秒超时值的
SetCommTimeouts()
以来,我看到在
ReadFile()返回读取零字节之前有一个持续5秒的暂停

奇怪的是,如果我使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,秤会报告物品的重量

然后,我可以返回到使用直接USB而不是虚拟串行通信端口的示例应用程序,它可以很好地工作

但是,如果我随后拔下连接电子秤和PC的USB电缆(该电缆也关闭了电子秤的电源),然后重新插入USB电缆,示例应用程序将不再正常工作,我再次看到暂停超时

然后我尝试使用原始的销售点应用程序,该应用程序依赖于使用虚拟串行端口COM4的串行通信端口,并且该应用程序可以很好地称量物品

然后,当我重试我的示例应用程序时,它还将报告项目权重

我的问题。

如果USB设备在插入时创建了虚拟串行通信端口,那么是否只需要通过在
CreateFile()
调用中指定通信端口COM4来使用虚拟串行端口

如果设备导致Windows生成虚拟通信端口,如何通过使用USB设备路径进行直接USB串行通信

是否有某种方法可以指定任何版本的Windows在插入设备时自动为其创建虚拟串行通信端口

示例USB应用程序的源代码

使用Visual Studio 2005的示例USB Windows控制台应用程序的源代码如下所示,main位于底部,其中大部分是用于查找特定USB设备的类,然后允许
ReadFile()
WriteFile()

//usb\u test\u cons.cpp:定义控制台应用程序的入口点。
//
#包括“stdafx.h”
#包括
#包括
#包括
#包括
//这是USB设备类的GUID。
//它在Microsoft Windows驱动程序工具包的include文件Usbiodef.h中定义。
//另见https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85)。aspx哪个
//提供有关此GUID的基本文档。
定义GUID(GUID设备接口USB设备,0xA5DCBF10L、0x6530、0x11D2、0x90、0x1F、0x00、0xC0、0x4F、0xB9、0x51、0xED);
//(A5DCBF10-6530-11D2-901F-00C04FB951ED)
//以下是将与所有应用程序一起使用的标准定义
//通过UIE接口使用的设备。
#访问设备时定义UIE_设备错误(-11)/*错误*/
#定义UIE_设备\u未提供(-12)/*未提供设备*/
#定义UIE_设备_错误_范围(-13)/*范围错误*/
#定义UIE\U设备错误\U COM(-14)/*通信错误*/
#定义UIE_设备_超时(-15)/*通信错误*/
#定义特定于UIE_设备的(-20)/*特定于设备的错误从这里开始*/
#定义UIE_比例_ETX 0x03/*ETX字符*/
#定义运动中的UIE\U比例0x01/*运动中的比例*/
#定义UIE_标尺_零0x02/*零重量*/
#定义
// usb_test_cons.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <windows.h>
#include <setupapi.h>
#include <initguid.h>

#include <stdio.h>

// This is the GUID for the USB device class.
// It is defined in the include file Usbiodef.h of the Microsoft Windows Driver Kit.
// See also https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85).aspx which
// provides basic documentation on this GUID.
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
// (A5DCBF10-6530-11D2-901F-00C04FB951ED)

// Following are standard defines to be used with all of the
// devices that are use through the UIE interface.
#define UIE_DEVICE_ERROR             (-11)        /* error when accessing the device */
#define UIE_DEVICE_NOT_PROVIDE       (-12)        /* device is not provided */
#define UIE_DEVICE_ERROR_RANGE       (-13)        /* range error         */
#define UIE_DEVICE_ERROR_COM         (-14)        /* communication error */
#define UIE_DEVICE_TIMEOUT           (-15)        /* communication error */
#define UIE_DEVICE_SPECIFIC          (-20)        /* device specific errors start here */


#define UIE_SCALE_ETX           0x03        /* ETX character         */
#define UIE_SCALE_IN_MOTION     0x01        /* scale in motion       */
#define UIE_SCALE_ZERO          0x02        /* zero weight           */
#define UIE_SCALE_UNDER         0x01        /* under capacity        */
#define UIE_SCALE_OVER          0x02        /* over capacity         */

#define UIE_SCALE_ERROR             UIE_DEVICE_ERROR          /* error          */
#define UIE_SCALE_NOT_PROVIDE       UIE_DEVICE_NOT_PROVIDE    /* not provide    */
#define UIE_SCALE_TIMEOUT           UIE_DEVICE_TIMEOUT        /* time out when reading from scale  */
#define UIE_SCALE_MOTION            (UIE_DEVICE_SPECIFIC-1)   /* motion         */
#define UIE_SCALE_UNDER_CAPACITY    (UIE_DEVICE_SPECIFIC-2)   /* under capacity */
#define UIE_SCALE_OVER_CAPACITY     (UIE_DEVICE_SPECIFIC-3)   /* over capacity  */
#define UIE_SCALE_DATAFORMAT        (UIE_DEVICE_SPECIFIC-4)   /* Data read from scale incorrect format in UieScaleAnalysis()  */
#define UIE_SCALE_DATAUNITS         (UIE_DEVICE_SPECIFIC-5)   /* Units read from scale incorrect in UieScaleAnalysis()  */


static SHORT UieScaleStatus(char *puchBuffer, DWORD sLength)
{
    UCHAR   uchByte;

    switch (sLength) {
        case 16:
            // The scale message is a weight message with a status section.
            // Move the buffer pointer to where the status section should begin.
            // A status only message has the same format as the status section of a weight message.
            puchBuffer += 10;
        case 6:
            // The scale message may be a status only message if there is a problem with the scale.
            // A status only message is 6 characters with the letter S as the second character.
            if (*(puchBuffer + 0) != '\n' ||
                *(puchBuffer + 1) != 'S'  ||
                *(puchBuffer + 4) != '\r' ||
                *(puchBuffer + 5) != UIE_SCALE_ETX) {
                return (UIE_SCALE_DATAFORMAT);               /* exit ... */
            }
            break;
        default:
            return (UIE_SCALE_DATAFORMAT);               /* exit ... */
            break;
    }

    /* --- check status of low byte --- */
    uchByte = *(puchBuffer + 3) - (UCHAR)0x30;

    if (uchByte & UIE_SCALE_UNDER) {
        return (UIE_SCALE_UNDER_CAPACITY);
    } else if (uchByte & UIE_SCALE_OVER) {
        return (UIE_SCALE_OVER_CAPACITY);
    }

    /* --- check status of high byte --- */
    uchByte = *(puchBuffer + 2) - (UCHAR)0x30;

    if (uchByte & UIE_SCALE_IN_MOTION) {
        return (UIE_SCALE_MOTION);
    } else if (uchByte & UIE_SCALE_ZERO) {
        return (0);
    } else {
        return (TRUE);
    }
}

class UsbSerialDevice
{
public:
    UsbSerialDevice();
    ~UsbSerialDevice();
    int CreateEndPoint (wchar_t *wszVendorId);
    int CloseEndPoint ();
    int ReadStream (void *bString, size_t nBytes);
    int WriteStream (void *bString, size_t nBytes);

    DWORD   m_dwError;          // GetLastError() for last action
    DWORD   m_dwErrorWrite;     // GetLastError() for last write
    DWORD   m_dwErrorRead;      // GetLastError() for last read
    DWORD   m_dwBytesWritten;
    DWORD   m_dwBytesRead;

private:
    HANDLE        m_hFile;
    DWORD         m_dwStatError;
    COMMTIMEOUTS  m_timeOut;
    COMSTAT       m_statOut;
};

UsbSerialDevice::UsbSerialDevice() :
    m_dwError(0),
    m_dwErrorWrite(0),
    m_dwErrorRead(0),
    m_dwBytesWritten(0),
    m_dwBytesRead(0),
    m_hFile(NULL)
{
}

UsbSerialDevice::~UsbSerialDevice()
{
    CloseHandle (m_hFile);
}

int UsbSerialDevice::WriteStream(void *bString, size_t nBytes)
{
    BOOL  bWrite = FALSE;

    if (m_hFile) {
        m_dwError = m_dwErrorWrite = 0;
        m_dwBytesWritten = 0;
        ClearCommError (m_hFile, &m_dwStatError, &m_statOut);
        bWrite = WriteFile (m_hFile, bString, nBytes, &m_dwBytesWritten, NULL);
        m_dwError = m_dwErrorWrite = GetLastError();
        return 0;
    }

    return -1;
}

int UsbSerialDevice::ReadStream(void *bString, size_t nBytes)
{
    BOOL  bRead = FALSE;

    if (m_hFile) {
        m_dwError = m_dwErrorRead = 0;
        m_dwBytesRead = 0;
        ClearCommError (m_hFile, &m_dwStatError, &m_statOut);
        bRead = ReadFile (m_hFile, bString, nBytes, &m_dwBytesRead, NULL);
        m_dwError = m_dwErrorRead = GetLastError();
        return 0;
    }

    return -1;
}

int UsbSerialDevice::CreateEndPoint (wchar_t *wszVendorId)
{
    HDEVINFO    hDevInfo;


    m_dwError = ERROR_INVALID_HANDLE;

    // We will try to get device information set for all USB devices that have a
    // device interface and are currently present on the system (plugged in).
    hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    if (hDevInfo != INVALID_HANDLE_VALUE)
    {
        DWORD    dwMemberIdx;
        BOOL     bContinue = TRUE;
        SP_DEVICE_INTERFACE_DATA         DevIntfData;

        // Prepare to enumerate all device interfaces for the device information
        // set that we retrieved with SetupDiGetClassDevs(..)
        DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        dwMemberIdx = 0;

        // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
        // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
        // call the dwMemberIdx value needs to be incremented to retrieve the next
        // device interface information.
        for (BOOL bContinue = TRUE; bContinue; ) {
            PSP_DEVICE_INTERFACE_DETAIL_DATA  DevIntfDetailData;
            SP_DEVINFO_DATA    DevData;
            DWORD  dwSize;

            dwMemberIdx++;
            SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);

            if (GetLastError() == ERROR_NO_MORE_ITEMS) break;

            // As a last step we will need to get some more details for each
            // of device interface information we are able to retrieve. This
            // device interface detail gives us the information we need to identify
            // the device (VID/PID), and decide if it's useful to us. It will also
            // provide a DEVINFO_DATA structure which we can use to know the serial
            // port name for a virtual com port.

            DevData.cbSize = sizeof(DevData);

            // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
            // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
            // of zero, and a valid RequiredSize variable. In response to such a call,
            // this function returns the required buffer size at dwSize.

            SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);

            // Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
            // deallocate it later!
            DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
            DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
            {
                // Finally we can start checking if we've found a useable device,
                // by inspecting the DevIntfDetailData->DevicePath variable.
                //
                // The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
                // \\?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
                //
                // The VID for a particular vendor will be the same for a particular vendor's equipment.
                // The PID is variable for each device of the vendor.
                //
                // As you can see it contains the VID/PID for the device, so we can check
                // for the right VID/PID with string handling routines.

                // See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h

                if (wcsstr (DevIntfDetailData->DevicePath, wszVendorId)) {
                    m_dwError = 0;
                    m_hFile = CreateFile (DevIntfDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
                    if (m_hFile == INVALID_HANDLE_VALUE) {
                        m_dwError = GetLastError();
                    } else {
                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_timeOut.ReadIntervalTimeout = 0;
                        m_timeOut.ReadTotalTimeoutMultiplier = 0;
                        m_timeOut.ReadTotalTimeoutConstant = 5000;
                        SetCommTimeouts (m_hFile, &m_timeOut);
                        m_dwError = GetLastError();
                    }
                    bContinue = FALSE;    // found the vendor so stop processing after freeing the heap.
                }
            }

            HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
        }

        SetupDiDestroyDeviceInfoList(hDevInfo);
    }

    return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
    UsbSerialDevice  myDev;

    myDev.CreateEndPoint (L"vid_1a86&pid_7523");
    switch (myDev.m_dwError) {
        case 0:
            // no error so just ignore.
            break;
        case ERROR_ACCESS_DENIED:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_ACCESS_DENIED: Access is denied.\n      Is it already in use?\n"), myDev.m_dwError);
            break;
        case ERROR_GEN_FAILURE:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_GEN_FAILURE: A device attached to the system is not functioning.\n      Is it an HID?\n"), myDev.m_dwError);
            break;
        case ERROR_INVALID_HANDLE:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_INVALID_HANDLE: The handle is invalid.\n      CreateFile() failed?\n"), myDev.m_dwError);
            break;
        default:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n"), myDev.m_dwError);
            break;
    }

    if (myDev.m_dwError == 0) {
        char   reqWeight[] = "W\r";
        char   resWeight[256] = {0};

        myDev.WriteStream (reqWeight, strlen (reqWeight));
        wprintf (_T("    Sent request now get response.\n"));
        Sleep (50);
        myDev.ReadStream (resWeight, 16);
        wprintf (_T("    Got response.\n"));
        if (resWeight[0] != '\n' || resWeight[9] != '\r') {
            wprintf (_T("    Unexpected format of response.\n"));
        }

        short sRet = UieScaleStatus (resWeight, myDev.m_dwBytesRead);

        resWeight[9] = 0;      // terminate the weight string so that we can write it out.
        wprintf (_T("   ScaleStatus = %d, Response from device - \"%S\"\n"), sRet, resWeight + 1);
    }

    return 0;
}
class UsbSerialDevice
{
public:
    UsbSerialDevice();
    UsbSerialDevice(DWORD BaudRate, BYTE ByteSize = 8, BYTE Parity = NOPARITY, BYTE StopBits = ONESTOPBIT);
    ~UsbSerialDevice();
    int CreateEndPoint (wchar_t *wszVendorId);
    int SetCommPortSettings (DWORD BaudRate, BYTE ByteSize = 8, BYTE Parity = NOPARITY, BYTE StopBits = ONESTOPBIT);
    int CloseEndPoint ();
    int ReadStream (void *bString, size_t nBytes);
    int WriteStream (void *bString, size_t nBytes);
    int UpdateSettingsProxy (void);

    DWORD   m_dwError;          // GetLastError() for last action
    DWORD   m_dwErrorWrite;     // GetLastError() for last write
    DWORD   m_dwErrorRead;      // GetLastError() for last read
    DWORD   m_dwErrorCommState;
    DWORD   m_dwErrorCommTimeouts;
    DWORD   m_dwBytesWritten;
    DWORD   m_dwBytesRead;

    COMMTIMEOUTS  m_timeOut;           // last result from GetCommTimeouts(), updated by UpdateSettingsProxy()
    COMSTAT       m_statOut;           // last result from ClearCommError()
    DCB           m_commSet;           // last result from GetCommState(), updated by UpdateSettingsProxy()

private:
    HANDLE        m_hFile;
    DWORD         m_dwStatError;
    DCB           m_commSetDefault;    // the defaults used as standard
    wchar_t       m_portName[24];      // contains portname if defined for device in form \\.\\COMnn
};

UsbSerialDevice::UsbSerialDevice() :
    m_dwError(0),
    m_dwErrorWrite(0),
    m_dwErrorRead(0),
    m_dwBytesWritten(0),
    m_dwBytesRead(0),
    m_hFile(NULL)
{
    // initialize our COM port settings and allow people to change with 
    memset (&m_commSetDefault, 0, sizeof(m_commSetDefault));
    m_commSetDefault.DCBlength = sizeof(m_commSetDefault);
    m_commSetDefault.BaudRate = CBR_9600;
    m_commSetDefault.ByteSize = 7;
    m_commSetDefault.Parity = EVENPARITY;
    m_commSetDefault.StopBits = ONESTOPBIT;
    m_commSet.fDtrControl = DTR_CONTROL_DISABLE;
    m_portName[0] = 0;
}
int UsbSerialDevice::CreateEndPoint (wchar_t *wszVendorId)
{
    HDEVINFO    hDevInfo;


    m_dwError = ERROR_INVALID_HANDLE;

    // We will try to get device information set for all USB devices that have a
    // device interface and are currently present on the system (plugged in).
    hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    if (hDevInfo != INVALID_HANDLE_VALUE)
    {
        DWORD    dwMemberIdx;
        BOOL     bContinue = TRUE;
        SP_DEVICE_INTERFACE_DATA         DevIntfData;

        // Prepare to enumerate all device interfaces for the device information
        // set that we retrieved with SetupDiGetClassDevs(..)
        DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        dwMemberIdx = 0;

        // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
        // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
        // call the dwMemberIdx value needs to be incremented to retrieve the next
        // device interface information.
        for (BOOL bContinue = TRUE; bContinue; ) {
            PSP_DEVICE_INTERFACE_DETAIL_DATA  DevIntfDetailData;
            SP_DEVINFO_DATA    DevData;
            DWORD  dwSize;

            dwMemberIdx++;
            SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);

            if (GetLastError() == ERROR_NO_MORE_ITEMS) break;

            // As a last step we will need to get some more details for each
            // of device interface information we are able to retrieve. This
            // device interface detail gives us the information we need to identify
            // the device (VID/PID), and decide if it's useful to us. It will also
            // provide a DEVINFO_DATA structure which we can use to know the serial
            // port name for a virtual com port.

            DevData.cbSize = sizeof(DevData);

            // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
            // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
            // of zero, and a valid RequiredSize variable. In response to such a call,
            // this function returns the required buffer size at dwSize.

            SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);

            // Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
            // deallocate it later!
            DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
            DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
            {
                // Finally we can start checking if we've found a useable device,
                // by inspecting the DevIntfDetailData->DevicePath variable.
                //
                // The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
                // \\?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
                //
                // The VID for a particular vendor will be the same for a particular vendor's equipment.
                // The PID is variable for each device of the vendor.
                //
                // As you can see it contains the VID/PID for the device, so we can check
                // for the right VID/PID with string handling routines.

                // See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h

                if (wcsstr (DevIntfDetailData->DevicePath, wszVendorId)) {
                    HKEY   hKey;

                    m_dwError = 0;
                    // To find out the serial port for our scale device,
                    // we'll need to check the registry:
                    hKey = SetupDiOpenDevRegKey(hDevInfo, &DevData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

                    if (hKey != INVALID_HANDLE_VALUE) {
                        DWORD    dwSize = 0, dwType = 0;
                        wchar_t  lpData[16] = {0};

                        dwType = REG_SZ;
                        dwSize = sizeof(lpData);
                        LONG queryStat = RegQueryValueEx(hKey, _T("PortName"), NULL, &dwType, (LPBYTE)&lpData[0], &dwSize);
                        RegCloseKey(hKey);
                        if (queryStat == ERROR_SUCCESS) {
                            wcscpy (m_portName, L"\\\\.\\");
                            wcsncat (m_portName, lpData, dwSize / sizeof(wchar_t));
                        }
                    } else {
                        m_dwError = GetLastError();
                    }

                    m_hFile = CreateFile (DevIntfDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
                    if (m_hFile == INVALID_HANDLE_VALUE) {
                        m_dwError = GetLastError();
                    } else {
                        m_dwError = 0;
                        GetCommState (m_hFile, &m_commSet);
                        m_commSet = m_commSetDefault;
                        SetCommState (m_hFile, &m_commSet);
                        m_dwErrorCommState = GetLastError();
                        GetCommState (m_hFile, &m_commSet);

                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_timeOut.ReadIntervalTimeout = 0;
                        m_timeOut.ReadTotalTimeoutMultiplier = 0;
                        m_timeOut.ReadTotalTimeoutConstant = 5000;
                        SetCommTimeouts (m_hFile, &m_timeOut);
                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_dwErrorCommTimeouts = GetLastError();
                    }
                    bContinue = FALSE;    // found the vendor so stop processing after freeing the heap.
                }
            }

            HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
        }

        SetupDiDestroyDeviceInfoList(hDevInfo);
    }

    return 0;
}