0

I have a code use SetupDiGetDeviceRegistryPropertyW to enumerate USB vendor id and product id , I read from buffer as <ctypes.LP_c_wchar object at 0x000001A3361F4C40>,how can i parse ?

Buffer

result = ct.cast(PropertyBuffer,ct.POINTER(w.WCHAR))

code

import ctypes as ct
from ctypes import wintypes as w
import uuid

SetupAPI = ct.WinDLL('SetupAPI')

ULONG_PTR = w.WPARAM

class HDEVINFO(w.HANDLE):
    pass

class GUID(ct.Structure):
    _fields_ = (('Data1', ct.c_ulong),
                ('Data2', ct.c_ushort),
                ('Data3', ct.c_ushort),
                ('Data4', ct.c_ubyte * 8))

    def __repr__(self):
        return f"GUID('{self}')"

    def __str__(self):
        return (f'{{{self.Data1:08x}-{self.Data2:04x}-{self.Data3:04x}-'
                f'{bytes(self.Data4[:2]).hex()}-{bytes(self.Data4[2:]).hex()}}}')

    def __init__(self,guid=None):
        if guid is not None:
            data = uuid.UUID(guid)
            self.Data1 = data.time_low
            self.Data2 = data.time_mid
            self.Data3 = data.time_hi_version
            self.Data4[0] = data.clock_seq_hi_variant
            self.Data4[1] = data.clock_seq_low
            self.Data4[2:] = data.node.to_bytes(6,'big')

PGUID = ct.POINTER(GUID)

GUID_DEVINTERFACE_USB_DEVICE = GUID('{A5DCBF10-6530-11D2-901F-00C04FB951ED}')

class SP_DEVINFO_DATA(ct.Structure):

    _fields_ = (('cbSize', w.DWORD), 
                ('ClassGuid', GUID), 
                ('DevInst', w.DWORD), 
                ('Reserved', ULONG_PTR))

    def __repr__(self):
        return f'SP_DEVINFO_DATA(ClassGuid={self.ClassGuid}, DevInst={self.DevInst})'

    def __init__(self):
        self.cbSize = ct.sizeof(SP_DEVINFO_DATA)      

PSP_DEVINFO_DATA = ct.POINTER(SP_DEVINFO_DATA)

SetupAPI.SetupDiGetClassDevsW.argtypes = PGUID, w.PWCHAR, w.HWND, w.DWORD
SetupAPI.SetupDiGetClassDevsW.restype = HDEVINFO

SetupAPI.SetupDiEnumDeviceInfo.argtypes = HDEVINFO, w.DWORD, PSP_DEVINFO_DATA
SetupAPI.SetupDiEnumDeviceInfo.restype = w.BOOL 

SetupAPI.SetupDiGetDeviceRegistryPropertyA.argtypes = HDEVINFO, PSP_DEVINFO_DATA, 
w.DWORD, w.PDWORD, w.PBYTE, w.DWORD, w.PDWORD
SetupAPI.SetupDiGetDeviceRegistryPropertyA.restype = w.BOOL

SetupAPI.SetupDiDestroyDeviceInfoList.argtypes = HDEVINFO,
SetupAPI.SetupDiDestroyDeviceInfoList.restype = w.BOOL

DIGCF_DEFAULT         =  0x00000001  
DIGCF_PRESENT         =  0x00000002
DIGCF_ALLCLASSES      =  0x00000004
DIGCF_PROFILE         =  0x00000008
DIGCF_DEVICEINTERFACE =  0x00000010

ClassGuid = GUID_DEVINTERFACE_USB_DEVICE
Enumerator = None
hwndParent = None
Flags = DIGCF_DEVICEINTERFACE | DIGCF_PRESENT

devinfo = SP_DEVINFO_DATA()

hDevInfo = SetupAPI.SetupDiGetClassDevsW(ClassGuid, Enumerator, hwndParent, Flags)


DeviceInfoSet = hDevInfo
DeviceInfoData = ct.byref(devinfo)    
SPDRP_HARDWAREID = 0x00000001
PropertyRegDataType = None
PropertyBufferSize = 0
RequiredSize = w.DWORD()

try:
    MemberIndex = 0
    while SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, MemberIndex, ct.byref(devinfo)):       
        print(devinfo)          
        SetupAPI.SetupDiGetDeviceRegistryPropertyW(
                                               hDevInfo,
                                               ct.byref(devinfo),
                                               SPDRP_HARDWAREID,
                                               PropertyRegDataType,
                                               None, 
                                               PropertyBufferSize ,
                                               ct.byref(RequiredSize))
    
        PropertyBuffer = (w.BYTE * RequiredSize.value)()
        if not SetupAPI.SetupDiGetDeviceRegistryPropertyW(
                                                     hDevInfo,
                                                     ct.byref(devinfo),
                                                     SPDRP_HARDWAREID,
                                                     PropertyRegDataType,
                                                     PropertyBuffer, 
                                                     ct.sizeof(PropertyBuffer),
                                                     None):
           break
        result = ct.cast(PropertyBuffer,ct.POINTER(w.WCHAR))
        print(result)
        MemberIndex += 1            
           
finally:
    SetupAPI.SetupDiDestroyDeviceInfoList(hDevInfo)

2 Answers2

2

Make sure you are initializing and calling the "W" version of the SetupDiGetDeviceRegistryProperty API:

SetupAPI.SetupDiGetDeviceRegistryPropertyW.argtypes = HDEVINFO, PSP_DEVINFO_DATA, w.DWORD, w.PDWORD, w.PBYTE, w.DWORD, w.PDWORD
SetupAPI.SetupDiGetDeviceRegistryPropertyW.restype = w.BOOL

The registry value being read is a MULTI_SZ, which is multiple zero-terminated strings, the last of which is double-terminated. The following function casts the return buffer to a C wchar_t*, but then uses string slicing to extracts "length" characters starting at that pointer address. The length is determined by dividing the returned buffer size by the size of a WCHAR. Then the trailing nulls are stripped from the data and the individual strings are split by the other nulls:

def get_multi_sz(buffer, length):
    result = ct.cast(buffer, ct.POINTER(w.WCHAR))
    strings = result[:RequiredSize.value // ct.sizeof(w.WCHAR)]
    return strings.rstrip('\x00').split('\x00')

Finally, in your SetupDiEnumDeviceInfo while loop, print the list of strings returned by each device:

MemberIndex = 0
while SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, MemberIndex, ct.byref(devinfo)):
    print(devinfo)
    SetupAPI.SetupDiGetDeviceRegistryPropertyW(
            hDevInfo, ct.byref(devinfo), SPDRP_HARDWAREID, PropertyRegDataType,
            None, PropertyBufferSize, ct.byref(RequiredSize))

    PropertyBuffer = (w.BYTE * RequiredSize.value)()
    if not SetupAPI.SetupDiGetDeviceRegistryPropertyW(
            hDevInfo, ct.byref(devinfo), SPDRP_HARDWAREID, PropertyRegDataType,
            PropertyBuffer, ct.sizeof(PropertyBuffer), None):
        break

    print(get_multi_sz(PropertyBuffer, RequiredSize.value))
    MemberIndex += 1

Output with these changes (on my system, anyway):

SP_DEVINFO_DATA(ClassGuid={36fc9e60-c465-11cf-8056-444553540000}, DevInst=1)
['USB\\VID_046D&PID_C52B&REV_1201', 'USB\\VID_046D&PID_C52B']
SP_DEVINFO_DATA(ClassGuid={745a17a0-74d3-11d0-b6fe-00a0c90f57da}, DevInst=2)
['USB\\VID_046D&PID_C01B&REV_1800', 'USB\\VID_046D&PID_C01B']
SP_DEVINFO_DATA(ClassGuid={36fc9e60-c465-11cf-8056-444553540000}, DevInst=3)
['USB\\VID_03F0&PID_0122&REV_0100', 'USB\\VID_03F0&PID_0122']
SP_DEVINFO_DATA(ClassGuid={36fc9e60-c465-11cf-8056-444553540000}, DevInst=4)
['USB\\VID_046D&PID_0A87&REV_0112', 'USB\\VID_046D&PID_0A87']

Using a tool like UsbTreeView, you can verify the Hardware ID strings match. Here's an excerpt from that tool that matches the 3rd device's hardware IDs above:

      ======================== USB Device ========================

        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : USB Composite Device
Device Path              : \\?\USB#VID_03F0&PID_0122#5&35eda8e7&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Kernel Name              : \Device\USBPDO-10
Device ID                : USB\VID_03F0&PID_0122\5&35EDA8E7&0&1
Hardware IDs             : USB\VID_03F0&PID_0122&REV_0100 USB\VID_03F0&PID_0122
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • I can also read back the message using `result = ct.cast(PropertyBuffer,ct.POINTER(w.WCHAR))[:52]`, but I tried it randomly, I don't know the reason – user19212551 Jul 05 '22 at 02:26
  • @user19212551 A `REG_MULTI_SZ` is returned as a pointer to a string. In Python to read multiple characters from a string pointer, string slicing reads the memory and converts it to a Python string. You need to use the correct buffer size returned by the function, but be aware it is a *byte*-size, so if using the "W" functions the characters are 16-bit and the returned size must be divided by 2. The `get_multi_sz` function above does that. – Mark Tolonen Jul 05 '22 at 05:30
  • @user19212551 Also note that different registry queries return different registry types. `PropertyRegDataType` returns the registry type. Instead of passing `None` pass a `w.DWORD()` instance by reference like was done for `RequiredSize` and it will return the type. Each type is parsed differently. – Mark Tolonen Jul 05 '22 at 05:32
1

Listing:

Your code had several flaws (out of which matter most):

  • Continuation on a separate line of SetupAPI.SetupDiGetDeviceRegistryPropertyA.argtypes (without a \) - your program only works because the following bullet is masking this

  • Definig them for SetupDiGetDeviceRegistryPropertyA but using SetupDiGetDeviceRegistryPropertyW - which yields Undefined Behavior. Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for more details

  • Converting the array to a pointer doesn't make any sense, as it loses size information

I modified your script to a working version:

  • Extended parse_data to accommodate new data types (REG_DWORD, REG_SZ, REG_MULTI_SZ) - as originally only REG_MULTI_SZ data (corresponding to SPDRP_HARDWAREID property) was mentioned / required

  • Improved error handling

  • Extended functionality

  • Other minor things

code00.py:

#!/usr/bin/env python

import ctypes as ct
import sys
import uuid
import winreg as wr

from ctypes import wintypes as wt


ULONG_PTR = wt.WPARAM
HDEVINFO = wt.HANDLE


class GUID(ct.Structure):
    _fields_ = (
        ("Data1", ct.c_ulong),
        ("Data2", ct.c_ushort),
        ("Data3", ct.c_ushort),
        ("Data4", ct.c_ubyte * 8),
    )

    def __repr__(self):
        return f"GUID('{self}')"

    def __str__(self):
        return (f"{{{self.Data1:08x}-{self.Data2:04x}-{self.Data3:04x}-"
                f"{bytes(self.Data4[:2]).hex()}-{bytes(self.Data4[2:]).hex()}}}")

    def __init__(self,guid=None):
        if guid is not None:
            data = uuid.UUID(guid)
            self.Data1 = data.time_low
            self.Data2 = data.time_mid
            self.Data3 = data.time_hi_version
            self.Data4[0] = data.clock_seq_hi_variant
            self.Data4[1] = data.clock_seq_low
            self.Data4[2:] = data.node.to_bytes(6, "big")

PGUID = ct.POINTER(GUID)


class SP_DEVINFO_DATA(ct.Structure):
    _fields_ = (
        ("cbSize", wt.DWORD),
        ("ClassGuid", GUID),
        ("DevInst", wt.DWORD),
        ("Reserved", ULONG_PTR),
    )

    def __repr__(self):
        return f"SP_DEVINFO_DATA(ClassGuid={self.ClassGuid}, DevInst={self.DevInst})"

    def __init__(self):
        self.cbSize = ct.sizeof(self.__class__)


PSP_DEVINFO_DATA = ct.POINTER(SP_DEVINFO_DATA)

GUID_DEVINTERFACE_USB_DEVICE = GUID("{A5DCBF10-6530-11D2-901F-00C04FB951ED}")


kernel32 = ct.WinDLL("Kernel32")

GetLastError = kernel32.GetLastError
GetLastError.argtypes = ()
GetLastError.restype = wt.DWORD

setupapi = ct.WinDLL("Setupapi")

#is_wide = True  # Determine whether using WIDE or ANSI functions

SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW #if is_wide else setupapi.SetupDiGetClassDevsA
SetupDiGetClassDevs.argtypes = (PGUID, wt.PWCHAR, wt.HWND, wt.DWORD)
SetupDiGetClassDevs.restype = HDEVINFO

SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
SetupDiEnumDeviceInfo.argtypes = (HDEVINFO, wt.DWORD, PSP_DEVINFO_DATA)
SetupDiEnumDeviceInfo.restype = wt.BOOL

SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW #if is_wide else setupapi.SetupDiGetDeviceRegistryPropertyA
SetupDiGetDeviceRegistryProperty.argtypes = (HDEVINFO, PSP_DEVINFO_DATA, wt.DWORD, wt.PDWORD, wt.PBYTE, wt.DWORD, wt.PDWORD)
SetupDiGetDeviceRegistryProperty.restype = wt.BOOL

SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
SetupDiDestroyDeviceInfoList.argtypes = (HDEVINFO,)
SetupDiDestroyDeviceInfoList.restype = wt.BOOL


DIGCF_DEFAULT = 0x00000001
DIGCF_PRESENT = 0x00000002
DIGCF_ALLCLASSES = 0x00000004
DIGCF_PROFILE = 0x00000008
DIGCF_DEVICEINTERFACE = 0x00000010


def parse_data(arr, reg_data_type):
    if reg_data_type is wr.REG_DWORD:
        return wt.DWORD.from_buffer(arr).value
    elif reg_data_type in (wr.REG_SZ, wr.REG_MULTI_SZ):
        WArrType = wt.WCHAR * (arr._length_ // ct.sizeof(wt.WCHAR))  # Convert from char array to wchar_t array (halving size)
        warr = WArrType.from_buffer(arr)   # @TODO - cfati: You should probably use MultiByteToWideChar or mbstowcs here.
        ret = str(warr[:len(warr)]).rstrip("\x00")
        if reg_data_type is wr.REG_MULTI_SZ:
            return ret.split("\x00")
        elif reg_data_type is wr.REG_SZ:
            return ret
    else:  # More types could be handled here
        return arr


def main(*argv):
    ClassGuid = GUID_DEVINTERFACE_USB_DEVICE
    Enumerator = None
    hwndParent = None
    Flags = DIGCF_DEVICEINTERFACE | DIGCF_PRESENT

    dev_info = SP_DEVINFO_DATA()

    hDevInfo = SetupDiGetClassDevs(ClassGuid, Enumerator, hwndParent, Flags)


    DeviceInfoSet = hDevInfo
    property_reg_data_type = wt.DWORD(0)
    required_size = wt.DWORD(0)

    SPDRP_HARDWAREID = 0x00000001
    SPDRP_CLASS = 0x00000007
    SPDRP_FRIENDLYNAME = 0x0000000C
    SPDRP_CAPABILITIES = 0x0000000F
    SPDRP_BUSNUMBER = 0x00000015
    SPDRP_DEVTYPE = 0x00000019
    SPDRP_EXCLUSIVE = 0x0000001A
    SPDRP_CHARACTERISTICS = 0x0000001B
    SPDRP_ADDRESS = 0x0000001C
    SPDRP_INSTALL_STATE = 0x00000022
    props = (SPDRP_HARDWAREID, SPDRP_CLASS, SPDRP_FRIENDLYNAME, SPDRP_CAPABILITIES)

    ERROR_INVALID_DATA = 0x0D
    ERROR_INSUFFICIENT_BUFFER = 0x7A

    try:
        MemberIndex = 0
        while SetupDiEnumDeviceInfo(hDevInfo, MemberIndex, ct.byref(dev_info)):
            print(dev_info)
            keep_running = True
            for prop in props:
                print(f"  Prop: {prop}")
                if not SetupDiGetDeviceRegistryProperty(
                        hDevInfo, ct.byref(dev_info), prop,
                        None, None, 0, ct.byref(required_size)):
                    gle = GetLastError()
                    if gle == ERROR_INVALID_DATA:
                        print("    Unsupported")
                        continue
                    elif gle != ERROR_INSUFFICIENT_BUFFER:
                        print("    Fail0:", gle)
                        keep_running = False
                        break

                #print("  kkt", prop, required_size.value)
                PropertyBuffer = wt.BYTE * required_size.value
                property_buffer = PropertyBuffer()
                if not SetupDiGetDeviceRegistryProperty(
                        hDevInfo, ct.byref(dev_info), prop,
                        ct.byref(property_reg_data_type),
                        property_buffer, ct.sizeof(PropertyBuffer), None):
                    gle = GetLastError()
                    print("    Fail1:", gle)
                    if gle != ERROR_INVALID_DATA:
                        keep_running = False
                        break
                print(f"    Data type: {property_reg_data_type.value} \
                        \n    Buf: {property_buffer} \
                        \n    Data: {parse_data(property_buffer, property_reg_data_type.value)}")
            if not keep_running:
                break
            MemberIndex += 1

    finally:
        SetupDiDestroyDeviceInfoList(hDevInfo)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q072847468]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" ./code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32

SP_DEVINFO_DATA(ClassGuid={4d36e972-e325-11ce-bfc1-08002be10318}, DevInst=1)
  Prop: 1
    Data type: 7
    Buf: <__main__.c_byte_Array_108 object at 0x000002287E6F4F40>
    Data: ['USB\\VID_0BDA&PID_8153&REV_3100', 'USB\\VID_0BDA&PID_8153']
  Prop: 7
    Data type: 1
    Buf: <__main__.c_byte_Array_8 object at 0x000002287ECF60C0>
    Data: Net
  Prop: 12
    Data type: 1
    Buf: <__main__.c_byte_Array_74 object at 0x000002287E6F4F40>
    Data: Realtek USB GbE Family Controller #2
  Prop: 15
    Data type: 4
    Buf: <__main__.c_byte_Array_4 object at 0x000002287ECF6040>
    Data: 148
SP_DEVINFO_DATA(ClassGuid={745a17a0-74d3-11d0-b6fe-00a0c90f57da}, DevInst=2)
  Prop: 1
    Data type: 7
    Buf: <__main__.c_byte_Array_108 object at 0x000002287E6F4F40>
    Data: ['USB\\VID_045E&PID_00CB&REV_0100', 'USB\\VID_045E&PID_00CB']
  Prop: 7
    Data type: 1
    Buf: <__main__.c_byte_Array_18 object at 0x000002287ECF8040>
    Data: HIDClass
  Prop: 12
    Unsupported
  Prop: 15
    Data type: 4
    Buf: <__main__.c_byte_Array_4 object at 0x000002287E6F4F40>
    Data: 132
SP_DEVINFO_DATA(ClassGuid={36fc9e60-c465-11cf-8056-444553540000}, DevInst=3)
  Prop: 1
    Data type: 7
    Buf: <__main__.c_byte_Array_108 object at 0x000002287ECF80C0>
    Data: ['USB\\VID_045E&PID_07F8&REV_0300', 'USB\\VID_045E&PID_07F8']
  Prop: 7
    Data type: 1
    Buf: <__main__.c_byte_Array_8 object at 0x000002287E6F4F40>
    Data: USB
  Prop: 12
    Unsupported
  Prop: 15
    Data type: 4
    Buf: <__main__.c_byte_Array_4 object at 0x000002287ECF8040>
    Data: 132
SP_DEVINFO_DATA(ClassGuid={e0cbf06c-cd8b-4647-bb8a-263b43f0f974}, DevInst=4)
  Prop: 1
    Data type: 7
    Buf: <__main__.c_byte_Array_108 object at 0x000002287E6F4F40>
    Data: ['USB\\VID_8087&PID_0A2B&REV_0001', 'USB\\VID_8087&PID_0A2B']
  Prop: 7
    Data type: 1
    Buf: <__main__.c_byte_Array_20 object at 0x000002287ECF80C0>
    Data: Bluetooth
  Prop: 12
    Unsupported
  Prop: 15
    Data type: 4
    Buf: <__main__.c_byte_Array_4 object at 0x000002287E6F4F40>
    Data: 128
SP_DEVINFO_DATA(ClassGuid={36fc9e60-c465-11cf-8056-444553540000}, DevInst=5)
  Prop: 1
    Data type: 7
    Buf: <__main__.c_byte_Array_108 object at 0x000002287ECF8040>
    Data: ['USB\\VID_0C45&PID_6713&REV_5605', 'USB\\VID_0C45&PID_6713']
  Prop: 7
    Data type: 1
    Buf: <__main__.c_byte_Array_8 object at 0x000002287E6F4F40>
    Data: USB
  Prop: 12
    Unsupported
  Prop: 15
    Data type: 4
    Buf: <__main__.c_byte_Array_4 object at 0x000002287ECF80C0>
    Data: 128

Done.
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • If I use SPDRP_BUSNUMBER, how to modify the code,After I modified the code output is blank – user19212551 Jul 05 '22 at 04:56
  • @user19212551 See [Registry Value Types](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types). `SPDRP_BUSNUMBER` is a `REG_DWORD` so is returned as a 4-byte little-endian value, not a `REG_MULTI_SZ`. – Mark Tolonen Jul 05 '22 at 05:27