为啥有些设备不能用 SetupDiGetDeviceInterfaceDetail() 枚举?

Posted

技术标签:

【中文标题】为啥有些设备不能用 SetupDiGetDeviceInterfaceDetail() 枚举?【英文标题】:Why do some devices not enumerate with SetupDiGetDeviceInterfaceDetail()?为什么有些设备不能用 SetupDiGetDeviceInterfaceDetail() 枚举? 【发布时间】:2009-02-20 21:07:11 【问题描述】:

我正在维护一个应用程序,该应用程序使用 SetupDiGetDeviceInterfaceDetail() 来查找有关计算机上已安装串行端口的信息。我在测试时注意到有一些设备,例如我的 Lucent WinModem,没有出现在该枚举中。事实证明,我公司生产的一组实现串行端口接口的设备也遇到了类似的问题。我的假设是设备的 INF 文件中缺少某些内容。有谁知道什么样的条件会导致这种遗漏?

编辑:这是我用来枚举串行端口的代码示例。我尝试了各种标志组合,但在行为上没有发现任何显着差异。

DEFINE_GUID(GUID_CLASS_COMPORT, 0x4d36e978, 0xe325, 0x11ce, 0xbf, 0xc1, \
            0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);


GUID *serial_port_guid = const_cast<GUID *>(&GUID_CLASS_COMPORT);
HDEVINFO device_info = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA *detail_data = 0;

device_info = SetupDiGetClassDevs(
   serial_port_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(device_info != INVALID_HANDLE_VALUE)

   uint4 const detail_data_size = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
   detail_data = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA *>(new char[detail_data_size]);
   SP_DEVICE_INTERFACE_DATA ifc_data;
   bool more_interfaces = true;
   int rcd;
   memset(&ifc_data, 0, sizeof(ifc_data)); 
   memset(detail_data, 0, detail_data_size);
   ifc_data.cbSize = sizeof(ifc_data);
   detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
   for(uint4 index = 0; more_interfaces; ++index)
   
      rcd = SetupDiEnumDeviceInterfaces(device_info, 0, serial_port_guid, index, &ifc_data);
      if(rcd)
      
         // we need to get the details of this device
         SP_DEVINFO_DATA device_data =  sizeof(SP_DEVINFO_DATA) ;
         rcd = SetupDiGetDeviceInterfaceDetail(
            device_info, &ifc_data, detail_data, detail_data_size, 0, &device_data);
         if(rcd)
         
            StrAsc device_path(detail_data->DevicePath);
            byte friendly_name[256];

            rcd = SetupDiGetDeviceRegistryProperty(
               device_info, &device_data, SPDRP_FRIENDLYNAME, 0, friendly_name, sizeof(friendly_name), 0);
            if(rcd)
            
               std::for_each(
                  port_names.begin(),
                  port_names.end(),
                  update_friendly_name(
                     reinterpret_cast<char const *>(friendly_name)));
            
         
         else
            more_interfaces = false;
      
   

【问题讨论】:

【参考方案1】:

这更像是一个关于这个问题的问题。当您调用该函数时,您传入的第一个参数应该是您可能从SetupDiGetClassDevs 函数获得的 DeviceInfoSet。当您调用 SetupDiGetClassDevs 函数时,您在函数上引用 Microsoft 的页面时为标志(最后一个参数)指定了什么:

DIGCF_ALLCLASSES 返回所有设备设置类或所有设备的已安装设备列表 设备接口类。

DIGCF_DEVICEINTERFACE 返回支持指定设备的设备接口的设备 接口类。这个标志必须是 在 Flags 参数中设置,如果 枚举器参数指定一个 设备实例 ID。

DIGCF_DEFAULT 仅返回与系统默认值关联的设备 设备接口,如果设置为 指定的设备接口 类。

DIGCF_PRESENT 仅返回系统中当前存在的设备。

DIGCF_PROFILE 仅返回属于当前硬件配置文件的设备。

根据您的选择,设备列表会发生变化。例如,当前标志只会显示主动插入的设备。


更新:感谢您提供示例代码。

我现在的问题是,如果您想知道调制解调器的友好名称,为什么不使用相同的调用,而是指定调制解调器 Guid 而不是 COM 端口?我的调制解调器 GUID 是 4D36E96D-E325-11CE-BFC1-08002BE10318

在注册表中,我可以看到一个名为“AttachedTo”的值,它指定了一个 COM 端口。我将不得不研究与 API 相关联的属性。注册表项位于

HKLM\SYSTEM\CurrentControlSet\Control\Class4D36E96D-E325-11CE-BFC1-08002BE10318\


另一个更新:

仔细查看示例代码。基于此,如果您尝试获取应该返回SP_DEVICE_INTERFACE_DETAIL_DATA 结构的设备接口类。这不会提供获取设备友好名称的方法。我相信你会想要设备实例。

根据我的阅读,设备接口用于获取可用于写入的设备路径。

我为测试您的代码所做的一件事是再次尝试磁盘设备接口。我进行了一些更改以使其在我的系统上运行,但仍未完成。我认为一个问题(可能更多)是我需要在 SetupDiGetDeviceInterfaceDetail 调用之间调整 DevicePath 变量的大小。

void Test()


GUID *serial_port_guid = const_cast<GUID *>(&GUID_DEVINTERFACE_DISK);
HDEVINFO device_info = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA detail_data;

device_info = SetupDiGetClassDevs(
   serial_port_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(device_info != INVALID_HANDLE_VALUE)

   //uint4 const detail_data_size = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);// + 256;
   //detail_data = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA *>(new char[detail_data_size]);
   SP_DEVICE_INTERFACE_DATA ifc_data;
   bool more_interfaces = true;
   int rcd;
   memset(&ifc_data, 0, sizeof(ifc_data)); 
   //memset(detail_data, 0, detail_data_size);
   ifc_data.cbSize = sizeof(ifc_data);
   detail_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
   for(uint4 index = 0; more_interfaces; ++index)
   
      rcd = SetupDiEnumDeviceInterfaces(device_info, 0, serial_port_guid, index, &ifc_data);
      if(rcd)
      
         // we need to get the details of this device
         SP_DEVINFO_DATA device_data;
         device_data.cbSize = sizeof(SP_DEVINFO_DATA);
         DWORD intReqSize;
         rcd = SetupDiGetDeviceInterfaceDetail(device_info, &ifc_data, 0, 0, &intReqSize, &device_data);

         rcd = SetupDiGetDeviceInterfaceDetail(device_info, &ifc_data, &detail_data,intReqSize,&intReqSize,&device_data);
         if(rcd)
         
            //StrAsc device_path(detail_data->DevicePath);
            byte friendly_name[256];

            rcd = SetupDiGetDeviceRegistryProperty(
               device_info, &device_data, SPDRP_FRIENDLYNAME, 0, friendly_name, sizeof(friendly_name), reinterpret_cast<DWORD *>(sizeof(friendly_name)));
            if(rcd)
            
              cout<<reinterpret_cast<char const *>(friendly_name);
            
            else
               int num = GetLastError();
            
         
         else
         
                int num = GetLastError();
            
      
      else
            more_interfaces = false;
       

SetupDiDestroyDeviceInfoList(device_info);

此外,在 INF 中,您可能必须添加 AddInterface 指令以将您的驱动程序与正确的接口相关联。

【讨论】:

我添加了一个代码示例来展示我是如何调用这些函数的。请注意,当我进行这些测试时,我尝试检测的设备是“存在的”。 问题是我希望设备实现串口接口。它的 USB 端口更像是一个配置端口,而不是用于调制解调器通信的操作端口(它被设计为我们其他一些设备的外围设备)。 我还应该提到,我尝试使用 DIGCF_ALLCLASSES 选项,但仍然没有在结果枚举中看到设备。 那么,在 INF 中,您为 Class 和 Class Guid 指定了什么?调制解调器或端口 INF 指定端口如下:Class=Ports ClassGuid=4D36E978-E325-11CE-BFC1-08002BE10318【参考方案2】:

我不确定以下修补程序是否会解决您在

中提到的问题

http://support.microsoft.com/kb/327868

还有一个有趣的点:GUID_CLASS_COMPORT 从 Win2000 开始就已经过时了..

http://msdn.microsoft.com/en-us/library/bb663140.aspx

http://msdn.microsoft.com/en-us/library/bb663174.aspx

我发现另一个网站有 9 种不同的枚举方式。祝你好运。

http://www.naughter.com/enumser.html

【讨论】:

这是一个非常好的线索,但遗憾的是没有描述我遇到的行为。在我的情况下,可以访问 potr。我在枚举中根本看不到它。 我看到了对 GUID 的相同引用,并对其进行了更新以匹配当前用于识别串行端口的引用。由于我自己声明了 GUID 对象,所以我只是保持相同的名称。 好的,添加了另一个有趣的网站【参考方案3】:

您说您的设备存在且可访问,但您是直接访问您的设备还是通过名称和编号 COMn 访问端口:

我有一个连接到我的音频驱动程序的 WinModem。我没有串口,连模拟的都没有。

【讨论】:

我可以用comxx打开设备就好了。我的代码正在尝试查找串行端口的“友好名称”。【参考方案4】:

我决定放弃这一点并消除对 SetupDi() 函数的依赖。相反,我编写了遍历 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum 中的子键的代码,以查找支持串行端口 GUID 的任何驱动程序。我感觉这就是设备管理器所做的。如果有人感兴趣,可以在下面看到我的代码片段:

typedef std::string StrAsc;
typedef std::pair<StrAsc, StrAsc> port_name_type;
typedef std::list<port_name_type> friendly_names_type;
void SerialPortBase::list_ports_friendly(friendly_names_type &port_names)

   // we will first get the list of names.  This will ensure that, at the very least, we get
   // the same list of names as we would have otherwise obtained. 
   port_names_type simple_list;
   list_ports(simple_list);
   port_names.clear();
   for(port_names_type::iterator pi = simple_list.begin(); pi != simple_list.end(); ++pi)
      port_names.push_back(friendly_name_type(*pi, *pi));

   // we will now need to enumerate the subkeys of the Enum registry key. We will need to
   // consider many levels of the registry key structure in doing this so we will use a list
   // of key handles as a stack.
   HKEY enum_key ;
   char const enum_key_name[] = "SYSTEM\\CurrentControlSet\\Enum";
   StrAsc const com_port_guid("4d36e978-e325-11ce-bfc1-08002be10318");
   char const class_guid_name[] = "ClassGUID";
   char const friendly_name_name[] = "FriendlyName";
   char const device_parameters_name[] = "Device Parameters";
   char const port_name_name[] = "PortName";
   long rcd = ::RegOpenKeyEx(
      HKEY_LOCAL_MACHINE, enum_key_name, 0, KEY_READ, &enum_key);
   char value_buff[MAX_PATH];
   StrAsc port_name, friendly_name;

   if(!port_names.empty() && rcd == ERROR_SUCCESS)
   
      std::list<HKEY> key_stack;
      key_stack.push_back(enum_key);
      while(!key_stack.empty())
      
         // we need to determine whether this key has a "ClassGUID" value
         HKEY current = key_stack.front();
         uint4 value_buff_len = sizeof(value_buff);
         key_stack.pop_front();
         rcd = ::RegQueryValueEx(
            current,
            class_guid_name,
            0,
            0,
            reinterpret_cast<byte *>(value_buff),
            &value_buff_len);
         if(rcd == ERROR_SUCCESS)
         
            // we will only consider devices that match the com port GUID
            if(com_port_guid == value_buff)
            
               // this key appears to identify a com port.  We will need to get the friendly name
               // and try to get the 'PortName' from the 'Device Parameters' subkey.  Once we
               // have those things, we can update the friendly name in our original list
               value_buff_len = sizeof(value_buff);
               rcd = ::RegQueryValueEx(
                  current,
                  friendly_name_name,
                  0,
                  0,
                  reinterpret_cast<byte *>(value_buff),
                  &value_buff_len);
               if(rcd == ERROR_SUCCESS)
               
                  HKEY device_parameters_key;
                  rcd = ::RegOpenKeyEx(
                     current,
                     device_parameters_name,
                     0,
                     KEY_READ,
                     &device_parameters_key);
                  if(rcd == ERROR_SUCCESS)
                  
                     friendly_name = value_buff;
                     value_buff_len = sizeof(value_buff);
                     rcd = ::RegQueryValueEx(
                        device_parameters_key,
                        port_name_name,
                        0,
                        0,
                        reinterpret_cast<byte *>(value_buff),
                        &value_buff_len);
                     if(rcd == ERROR_SUCCESS)
                     
                        friendly_names_type::iterator fi;
                        port_name = value_buff;
                        fi = std::find_if(
                           port_names.begin(), port_names.end(), port_has_name(port_name));
                        if(fi != port_names.end())
                           fi->second = friendly_name;
                     
                     ::RegCloseKey(device_parameters_key);
                  
               
            
         
         else
         
            // since this key did not have what we expected, we will need to check its
            // children
            uint4 index = 0;
            rcd = ERROR_SUCCESS;
            while(rcd == ERROR_SUCCESS)
            
               value_buff_len = sizeof(value_buff);
               rcd = ::RegEnumKeyEx(
                  current, index, value_buff, &value_buff_len, 0, 0, 0, 0);
               if(rcd == ERROR_SUCCESS)
               
                  HKEY child;
                  rcd = ::RegOpenKeyEx(current, value_buff, 0, KEY_READ, &child);
                  if(rcd == ERROR_SUCCESS)
                     key_stack.push_back(child);
               
               ++index;
            
         
         ::RegCloseKey(current);
      
   
 // list_ports_friendly

【讨论】:

此代码依赖于 Microsoft 设备管理器的未记录实现。如果您想要设备的友好名称,您应该使用 SetupDiEnumDeviceInterfaces 问题是它不起作用,我可以给你打赌设备管理器也不使用它。

以上是关于为啥有些设备不能用 SetupDiGetDeviceInterfaceDetail() 枚举?的主要内容,如果未能解决你的问题,请参考以下文章

为啥有些网站不能直接用FTP上传而只能用"SSH Secure Shell"

为啥有些门禁卡不能被华为NFC复制?

为啥有些 Enumerable 可以在 foreach 中更改,而有些则不能?

STM32 FLASH已经很大了,为啥有些开发板还外扩FLASH

PowerDesigner卸载以后为啥再安装后不能用了

为啥有些网页使用火狐浏览器能打开,使用IE却不能打开?