【问题标题】:Cannot detect serial ports available under Windows 10 with winAPI tools无法使用 winAPI 工具检测 Windows 10 下可用的串行端口
【发布时间】:2017-09-22 13:41:20
【问题描述】:

我的程序通常会在启动时检测操作系统中可用的串行端口。如果端口可以通过名称访问,这是一种简单的轮询方法。

项目定义为串口

std::string COMPortNumber[MAXPORTS] {"\\\\.\\COM1",  "\\\\.\\COM2",  "\\\\.\\COM3",  "\\\\.\\COM4",  "\\\\.\\COM5",
                                 "\\\\.\\COM6",  "\\\\.\\COM7",  "\\\\.\\COM8",  "\\\\.\\COM9",  "\\\\.\\COM10",
                                 "\\\\.\\COM11", "\\\\.\\COM12", "\\\\.\\COM13", "\\\\.\\COM14", "\\\\.\\COM15",
                                 "\\\\.\\COM16", "\\\\.\\COM17", "\\\\.\\COM18", "\\\\.\\COM19", "\\\\.\\COM20"};

std::string COMPortName[MAXPORTS] = {"com1",  "com2",  "com3",  "com4",  "com5",  "com6",  "com7",  "com8",  "com9", "com10",
                                "com11", "com12", "com13", "com14", "com15", "com16", "com17", "com18", "com19", "com20"};

轮询功能:

void updateSerialList(){
ComboBox_ResetContent(SerialPortDropDown); //clears all content from drop down box
//int iresult = ComboBox_AddString(SerialPortDropDown, "Update Port List\0");
for(int n=0; n<MAXPORTS; n++)
{
    COMPortAvailable[n] = serial.getComPortList( COMPortNumber[n] );
    if(COMPortAvailable[n] == true)
    {
        char* tempBuf = new char[COMPortName[n].length() + 1];
        for(unsigned int t=0; t<COMPortName[n].length(); t++)
        {
            tempBuf[t] = COMPortName[n][t];
        }
        tempBuf[COMPortName[n].length()] = '\0';
        int iResult = ComboBox_AddString(SerialPortDropDown, tempBuf);
        {
            if(iResult == CB_ERR){std::cout << "error adding string" << std::endl;}
            else if(iResult == CB_ERRSPACE){std::cout << "error no room" << std::endl;}
        }
        delete[] tempBuf;
    }
}
//place baud rates in select box
for(int n=NUMBERBAUDRATES-1; n>-1; n--)
{
    char* tempBuf = new char[BaudRateName[n].length() + 1];
    for(unsigned int t=0; t<BaudRateName[n].length(); t++)
    {
        tempBuf[t] = BaudRateName[n][t];
    }
    tempBuf[BaudRateName[n].length()] = '\0';
    int iResult = ComboBox_AddString(BaudRateDropDown, tempBuf);
    {
        if(iResult == CB_ERR){std::cout << "error adding string" << std::endl;}
        else if(iResult == CB_ERRSPACE){std::cout << "error no room" << std::endl;}
    }
    delete[] tempBuf;
}

这会在下拉框中编译一个列表供用户选择。它使用类中的函数作为串行实例。这是类内部的函数调用。

bool getComPortList(std::string portName)
{
    bool test;
    HANDLE testSerial;
    testSerial = CreateFile(   (portName.c_str())  , GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, NULL, NULL);
    if(testSerial == INVALID_HANDLE_VALUE)
    {
        test = false;
    }
    else
    {
        test = true;
        cout << "port number " << portName << " is available" << endl;
    }
    CloseHandle(testSerial);
    return test;
}

在我尝试在 Windows 10 上运行该程序之前,此方法一直运行良好。它之前已在 Vista、Win7、Win 8.1 上进行过测试和使用,但是即使 Windows10 设备管理器显示系统上有可用的通讯端口,我的程序也不能获取它们的列表。

Win10串口访问有什么不同?

【问题讨论】:

  • 起初这种方法很丑陋,而且不正确。如果“不工作”,您第二次需要 CM_Get_Device_Interface_ListW(&amp;GUID_DEVINTERFACE_COMPORT,..) - 查看错误返回的位置
  • 这种情况正在发生变化,物联网正在大肆宣传,Windows.Devices 命名空间很好地支持,没有人愿意再支持这些传统的虚拟 COM 端口了。您需要为您使用的任何设备找到另一个驱动程序。
  • 如果CreateFile 返回INVALID_HANDLE_VALUE 这并不意味着该文件不存在。你需要调用GetLastError() 并且只有当它返回ERROR_FILE_NOT_FOUND - 文件真的不存在。我猜你得到的是ERROR_ACCESS_DENIED
  • 此程序仅用于从嵌入式设备发送和接收串行 ASCII 消息。它还允许向设备发送配置消息,也严格使用 ASCII 行。我的设备最终用户并非都拥有 win10 笔记本电脑。所以问题是如果我确实实现了 windows.devices 命名空间,它会向后兼容吗?不是每个人都购买新笔记本电脑。我还有一个正在运行的 Vista。关于调用 get last 错误,如果我被拒绝访问,那么该端口仍然不可供我使用,那该怎么办?
  • 您尝试做什么 - 确定所有 com 端口的列表或使用某个端口?对于第一个任务,您需要使用CM_Get_Device_Interface_ListW(&amp;GUID_DEVINTERFACE_COMPORT,..)。 com 设备通常是专有的。这意味着 - 只有一个客户可以打开它。如果两次调用CreateFile,您可以轻松测试这个 - 如果第一次调用返回您确定(处理),第二次返回给您ERROR_ACCESS_DENIED那该怎么办? - 什么都没有 - 或向有此错误的用户显示消息

标签: c++ winapi serial-port


【解决方案1】:

您假设的主要逻辑错误 - 如果某些 nameCreateFile 返回 INVALID_HANDLE_VALUE - 这意味着此 name 不存在。但这当然是错误的,因为CreateFile 可能因不同的原因而失败。失败后您需要致电GetLastError。仅当它返回ERROR_FILE_NOT_FOUND name 确实不存在时(ERROR_PATH_NOT_FOUND 不能用于"\\\\.\\COMX",因为此处的路径始终存在且正确)。对于 com 设备,非常常见的错误是 STATUS_ACCESS_DENIED - 因为它有 DO_EXCLUSIVE 标志。带有此标志only one file on device can be open at a time

但是,对于枚举 com 设备 - 您需要通过 CM_Get_Device_Interface_ListW 枚举 GUID_DEVINTERFACE_COMPORT 的接口

enumInterfaces(const_cast<PGUID>(&GUID_DEVINTERFACE_COMPORT));

static volatile UCHAR guz;

void enumInterfaces(PGUID InterfaceClassGuid)
{
    CONFIGRET status;
    ULONG len = 0, cb = 0, rcb;
    PVOID stack = alloca(guz);
    PWSTR buf = 0;

    do 
    {
        if (status = CM_Get_Device_Interface_List_SizeW(&len, InterfaceClassGuid, 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
        {
            break;
        }

        if (cb < (rcb = len * sizeof(WCHAR)))
        {
            len = (cb = RtlPointerToOffset(buf = (PWSTR)alloca(rcb - cb), stack)) / sizeof(WCHAR);
        }

        status = CM_Get_Device_Interface_ListW(InterfaceClassGuid, 0, buf, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

        if (status == CR_SUCCESS)
        {
            while (*buf)
            {
                DbgPrint("use this name in CreateFile = %S\n", buf);

                PrintFriendlyNameByInterface(buf);

                buf += 1 + wcslen(buf);
            }
        }

    } while (status == CR_BUFFER_SMALL);
}

CONFIGRET PrintFriendlyNameByInterface(PCWSTR pszDeviceInterface)
{
    ULONG cb = 0, rcb = 64;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET status;

    union {
        PVOID pv;
        PWSTR DeviceID;
        PBYTE pb;
    };

    do 
    {
        if (cb < rcb)
        {
            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
        }

        status = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0);

        if (status == CR_SUCCESS)
        {
            if (PropertyType == DEVPROP_TYPE_STRING)
            {
                DbgPrint("DeviceID = %S\n", DeviceID);

                status = PrintFriendlyNameByDeviceID(DeviceID);
            }
            else
            {
                status = CR_WRONG_TYPE;
            }

            break;
        }

    } while (status == CR_BUFFER_SMALL);

    return status;
}

CONFIGRET PrintFriendlyNameByDeviceID(PWSTR DeviceID)
{
    DEVINST dnDevInst;

    CONFIGRET status = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL);

    if (status == CR_SUCCESS)
    {
        ULONG cb = 0, rcb = 256;

        PVOID stack = alloca(guz);

        DEVPROPTYPE PropertyType;

        union {
            PVOID pv;
            PWSTR sz;
            PBYTE pb;
        };

        do 
        {
            if (cb < rcb)
            {
                rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
            }

            status = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_NAME, &PropertyType, pb, &rcb, 0);

            if (status == CR_SUCCESS)
            {
                if (PropertyType == DEVPROP_TYPE_STRING)
                {
                    DbgPrint("show this name for user = %S\n", sz);
                }
                else
                {
                    status = CR_WRONG_TYPE;
                }
            }

        } while (status == CR_BUFFER_SMALL);
    }

    return status;
}

和演示输出:

use this name in CreateFile = \\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73}
DeviceID = ACPI\PNP0501\0
show this name for user = Communications Port (COM1)

在我的系统中\\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73} 是到PDO 设备\Device\00000034(由aspi.sys 创建)的符号链接,它没有DO_EXCLUSIVE 标志。尽管在第二次调用 CreateFile 时我收到了拒绝访问错误。连接到此设备 FDO - \Device\Serial0\\?\COM1 符号链接)已附加。它已经有DO_EXCLUSIVE 标志。无论如何SerialCreateOpen (IRP_MJ_CREATE 过程serial.sys) 拒绝访问创建多个文件 - 一开始在设备扩展中增加一些计数器,如果它!= 1 - 返回STATUS_ACCESS_DENIED

所以即使我们尝试打开 PDO (\\?\ACPI#PNP0501#0#{86e0d1e0-8089-11d0-9ce4-08003e301f73}) 这不是独占设备 (setting the exclusive flag for the FDO has no effect 这里) - 创建请求开始在堆栈顶部从 \Device\Serial0serial .sys enforce exclusivity themselves within their SerialCreateOpen 例程。

【讨论】:

  • 句柄和对象的措辞可能很微妙。当我们谈论打开一个句柄时,我们的意思是通过按名称或引用打开一个对象来创建一个新的句柄。所以这里的独占访问意味着 Device 通常被限制为由类型的解析过程强制执行的一个 File 对象引用,但我们实际上可以通过复制或继承来拥有该 File 的多个句柄。
  • 相对于另一个文件引用(即使用现有句柄作为RootDirectory)打开独占设备时,通常允许附加文件引用,但显然串行设备的IRP_MJ_CREATE功能( SerialCreateOpen) 即使在这种情况下也拒绝访问。你能确认一下吗?
  • @eryksun - 是的。你说得对。如果使用相对开放 - github.com/Zer0Mem0ry/ntoskrnl/blob/master/Io/iomgr/parse.c#L87,我们可以创建额外的句柄。但为此,我们最初需要处理设备。从内核我们也可以使用IO_ATTACH_DEVICE 选项调用IoCreateFile。所以真的可以不止一个句柄 :) 这是记录在案的Specifying Exclusive Access to Device Objects。但是对于用户模式应用程序,如果 com 端口已经打开,我们(如果还没有处理)
  • 相对打开) - 无法再次打开。
  • 也来自同一个链接 - 如果设备堆栈中的两个对象被命名(不推荐),I/O 管理器允许为每个对象打开一个句柄
猜你喜欢
  • 1970-01-01
  • 2018-10-02
  • 1970-01-01
  • 2012-10-03
  • 2015-09-15
  • 1970-01-01
  • 1970-01-01
  • 2018-06-29
  • 1970-01-01
相关资源
最近更新 更多