DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
chao-samu
Posts: 6
Joined: 05 Dec 2017, 11:44

DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

05 Dec 2017, 12:03

Hi,

currently i try to enumerate devices with the help of DllCalls.

My Dll call goes to "Setupapi.dll\SetupDiEnumDeviceInfo" [line 69] but i get windows error "1784" (ERROR_INVALID_USER_BUFFER).

It think its a problem with the cbSize of the "SP_DEVINFO_DATA" structure or with the size of the structure itself.

I'm a beginner in Dll so you can threath me like that and also say whats not so good coding style and should be made better.

Thank you!

Code: Select all

; ==============================================================================
        ; Using Windows "Setupapi.dll" for dive enumeration
; ==============================================================================
; Source: https://docs.microsoft.com/de-de/windows-hardware/drivers/install/device-information-sets


; ==============================================================================
                                    ; AHK Flags
; ==============================================================================
#NoEnv
; #Warn

; ==============================================================================
                                 ; Windows Flags
; ==============================================================================
DIGCF_DEFAULT :=          0x00000001  // only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT :=          0x00000002
DIGCF_ALLCLASSES :=       0x00000004
DIGCF_PROFILE :=          0x00000008
DIGCF_DEVICEINTERFACE :=  0x00000010

; ==============================================================================
                             ; Required Structures
; ==============================================================================

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
typedef struct _GUID {
  DWORD Data1;
  WORD  Data2;
  WORD  Data3;
  BYTE  Data4[8];
} GUID;
 */

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
typedef struct _SP_DEVINFO_DATA {
  DWORD     cbSize;
  GUID      ClassGuid;
  DWORD     DevInst;
  ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
*/
; ==============================================================================
                                    ; MAIN
; ==============================================================================


;1. Get device information set
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551069(v=vs.85).aspx
handle := DllCall("Setupapi.dll\SetupDiGetClassDevs", "Ptr", 0, "Ptr", 0, "Ptr", 0, "UInt", DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_PROFILE)
If (handle = -1) or ErrorLevel or A_LastError <> 0 {
    ErrorLevel = SetupDiGetClassDevs call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
    MsgBox % ErrorLevel
}

;2. Enumerate devices from set

;2.1 make Structure SP_DEVINFO_DATA
StructSize := 8 + 32 + 8 + A_PtrSize
VarSetCapacity(SP_DEVINFO_DATA, StructSize, 0)
;2.2 Set cbSize in SP_DEVINFO_DATA
NumPut(StructSize, SP_DEVINFO_DATA, 0, "UInt")

;2.3 get first device from "device information set" to "SP_DEVINFO_DATA"
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551010(v=vs.85).aspx
bool_DllCall := DllCall("Setupapi.dll\SetupDiEnumDeviceInfo", "UPtr", handle, "UInt", 0, "Ptr", &SP_DEVINFO_DATA)
If (bool_DllCall <= 0) or ErrorLevel or A_LastError <> 0 {
    ErrorLevel = SetupDiEnumDeviceInfo call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
    MsgBox % ErrorLevel
}

;Problem: Getting Error "1784" ERROR_INVALID_USER_BUFFER
cbSize := NumGet(SP_DEVINFO_DATA, 0, "UInt")
ClassGuid := NumGet(SP_DEVINFO_DATA, 8, "Int")
DevInst := NumGet(SP_DEVINFO_DATA, 8+32, "UPtr")
Reserved := NumGet(SP_DEVINFO_DATA, 8 + 32 + 8, "UPtr")
MsgBox % cbSize
MsgBox % ClassGuid
MsgBox % DevInst
MsgBox % Reserved[strike][/strike]
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

05 Dec 2017, 12:39

HI,
As you're getting handles returned by setupapi.dll, it's probably best to load setupapi.dll yourself manually first. Otherwise, as neither AutoHotkey or the DLLs AutoHotkey links to it, it will keep getting loaded and unloaded every time you DllCall. There's no guarantee the handles obtained from the result of one DllCall will be valid for the next.

You can sort that out by doing hModule := DllCall("LoadLibrary", "Str", "setupapi.dll", "Ptr") before you DllCall anything from setupapi.dll and add DllCall("FreeLibrary", "Ptr", hModule) when you're finished with setupapi.dll.


HDEVINFO is a typedef for void *. So I guess it might not matter in this case, but it's probably best to do LoadLibrary anyway. (I'd add "Ptr" as the last parameter to the first DllCall,
though. And maybe remember to call SetupDiDestroyDeviceInfoList when done, perhaps.)

StructSize := 8 + 32 + 8 + A_PtrSize is wrong. A DWORD is four bytes, and the size of a GUID struct is 16 bytes. Try StructSize := 4 + 16 + 4 + A_PtrSize.

Your DevInst offset in NumGet is also off (well, so is Reserved's, but it's pointless looking at it - it's probably there for future expansion).
Use DevInst := NumGet(SP_DEVINFO_DATA, 20, "UInt") ; DevInst is a DWORD. UPtr would read eight bytes on x64

If you really must see the value of ClassGuid in string form for MsgBox (as opposed to some weird mix of GUID's Data2 and Data3 members?), then do something like the following:

Code: Select all

VarSetCapacity(szClassGuid, 80)
if (DllCall("ole32\StringFromGUID2", "Ptr", &SP_DEVINFO_DATA+4, "WStr", szClassGuid, "Int", 39))
	MsgBox % szClassGuid
Last edited by qwerty12 on 05 Dec 2017, 14:13, edited 1 time in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

05 Dec 2017, 13:50

Hello chao-samu and welcome to the forum, hello qwerty12 :wave:
Minor comment, :arrow: DIGCF_DEFAULT := 0x00000001 // only valid with DIGCF_DEVICEINTERFACE

Cheers.
chao-samu
Posts: 6
Joined: 05 Dec 2017, 11:44

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

06 Dec 2017, 08:14

Thanks for your effort qwerty12, thx helgef,

That helped me a lot I thought in AHK x64 int is 8 bit. That with DLL unload seems logical why you have crossed it out? It works without pre-load but no clue why :).

My goal is to enumerate the USB devices and get the unquie ids of the usb sticks but yet i don't figured it out how to proceed yet.

Here the updated code

Code: Select all

; ==============================================================================
              ; Using Windows "Setupapi.h" for drive enumeration
; ==============================================================================
; Source: https://docs.microsoft.com/de-de/windows-hardware/drivers/install/device-information-sets


; ==============================================================================
                                ; ahk flags
; ==============================================================================
#NoEnv
; #Warn
SendMode Input
SetWorkingDir %A_ScriptDir%

; ==============================================================================
                              ; windows flags
; ==============================================================================
DIGCF_DEFAULT :=          0x00000001  ; only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT :=          0x00000002
DIGCF_ALLCLASSES :=       0x00000004
DIGCF_PROFILE :=          0x00000008
DIGCF_DEVICEINTERFACE :=  0x00000010

; ==============================================================================
                            ; required structures
; ==============================================================================

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
typedef struct _GUID {
  DWORD Data1;
  WORD  Data2;
  WORD  Data3;
  BYTE  Data4[8];
} GUID;
 */

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
typedef struct _SP_DEVINFO_DATA {
  DWORD     cbSize;
  GUID      ClassGuid;
  DWORD     DevInst;
  ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
*/
; ==============================================================================
                                    ; main
; ==============================================================================

;1. get device information set
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551069(v=vs.85).aspx
handle := DllCall("Setupapi.dll\SetupDiGetClassDevs", "Ptr", 0, "Ptr", 0, "Ptr", 0, "UInt", DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_PROFILE)
If (handle = -1) or ErrorLevel or A_LastError <> 0 {
    ErrorLevel = SetupDiGetClassDevs call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
    MsgBox % ErrorLevel
}


;2. enumerate devices from set
Member := Object()
MemberIndex := 0
while (!A_LastError)
{
    ;2.1 create Structure SP_DEVINFO_DATA
    StructSize := 4 + 16 + 4 + A_PtrSize
    VarSetCapacity(SP_DEVINFO_DATA, StructSize, 0)
    
    ;2.2 set cbSize in SP_DEVINFO_DATA
    NumPut(StructSize, SP_DEVINFO_DATA, 0, "UInt")
    
    ;2.3 get first device from "device information set" to "SP_DEVINFO_DATA"
    ; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551010(v=vs.85).aspx
    bool := DllCall("Setupapi.dll\SetupDiEnumDeviceInfo", "Ptr", handle, "UInt", MemberIndex, "Ptr", &SP_DEVINFO_DATA)
    If (bool <= 0) or ErrorLevel or A_LastError  0 {
        If (A_LastError = 259) ;ERROR_NO_MORE_ITEMS
        {
            break
        }
        else
        {
            ErrorLevel = SetupDiEnumDeviceInfo call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
            MsgBox % ErrorLevel
        }
    }
    
    ; 2.4 save value in array
    cbSize := NumGet(SP_DEVINFO_DATA, 0, "UInt")
    
    VarSetCapacity(szClassGuid, 80)
    if (DllCall("ole32\StringFromGUID2", "Ptr", &SP_DEVINFO_DATA+4, "WStr", szClassGuid, "Int", 39))
    
    DevInst := NumGet(SP_DEVINFO_DATA, 4+16, "UPtr")
    
    Member[MemberIndex] := "cbSize: " cbSize " ClassGuid: " szClassGuid " DevInst: " DevInst
    MemberIndex += 1
}


; 3. destroy handle
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff550996(v=vs.85).aspx
bool := DllCall("Setupapi.dll\SetupDiDestroyDeviceInfoList", "Ptr", handle)
If (bool <= 0) or ErrorLevel or A_LastError <> 0 {
    ErrorLevel = SetupDiDestroyDeviceInfoList call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
    MsgBox % ErrorLevel
}


; 4. write to file
Loop, %MemberIndex%
{
    add_line := Member[A_Index]
    FileAppend, %add_line%`n, test.txt
}
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

06 Dec 2017, 11:06

chao-samu wrote:That with DLL unload seems logical why you have crossed it out? It works without pre-load but no clue why :).
You're right, I put it back in. I crossed it out because my explanation wasn't correct in this situation. In this case, a handle is a pointer to some opaque struct (that you're expected to free) - everything needed for SetupDiEnumDeviceInfo to work is probably in that struct and presumably persists until it's freed.
In the past, I've dealt with function where such information is stored inside a table - memory for which is allocated by the DLL's code itself - and you get returned a handle that is simply an integer. When DllCall returned from that function, the DLL (it was presumably a good citizen so it made sure to clean up after itself) got unloaded and the handle meant nothing when the DLL got loaded again. There I had to ensure myself the DLL remained loaded between calls.
My goal is to enumerate the USB devices and get the unquie ids of the usb sticks but yet i don't figured it out how to proceed yet.
I took a look, but honestly, I don't know how the SetupAPI functions work. You should be able to change "USB" to "USBSTOR" in SetupDiGetClassDevs to get information on USB mass storage devices, but I couldn't get it work. Please forgive my horrible attempts at Hungarian notation...

EDIT: I should point out that the source to the semi-famous devcon tool is available: https://github.com/Microsoft/Windows-dr ... tup/devcon

Code: Select all

; ==============================================================================
              ; Using Windows "Setupapi.h" for drive enumeration
; ==============================================================================
; Source: https://docs.microsoft.com/de-de/windows-hardware/drivers/install/device-information-sets


; ==============================================================================
                                ; ahk flags
; ==============================================================================
#NoEnv
; #Warn
SendMode Input
SetWorkingDir %A_ScriptDir%

; ==============================================================================
                              ; windows flags
; ==============================================================================
DIGCF_DEFAULT :=          0x00000001  ; only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT :=          0x00000002
DIGCF_ALLCLASSES :=       0x00000004
DIGCF_PROFILE :=          0x00000008
DIGCF_DEVICEINTERFACE :=  0x00000010

; ==============================================================================
                            ; required structures
; ==============================================================================

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
typedef struct _GUID {
  DWORD Data1;
  WORD  Data2;
  WORD  Data3;
  BYTE  Data4[8];
} GUID;
 */

/* 
MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
typedef struct _SP_DEVINFO_DATA {
  DWORD     cbSize;
  GUID      ClassGuid;
  DWORD     DevInst;
  ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
*/
; ==============================================================================
                                    ; main
; ==============================================================================

;0. Ensure setupapi.dll remains loaded between each DllCall
hModule := DllCall("LoadLibrary", "Str", "setupapi.dll", "Ptr")
if (!hModule) {
	MsgBox % "LoadLibrary failed. A_LastError: " . A_LastError
	ExitApp 1
}

;1. get device information set
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551069(v=vs.85).aspx
handle := DllCall("Setupapi.dll\SetupDiGetClassDevs", "Ptr", 0, "Str", "USB", "Ptr", 0, "UInt", DIGCF_PRESENT | DIGCF_ALLCLASSES, "Ptr")

/*
 The handle in this case is a pointer. While handles from kernel interfaces might be just plain ints, where INVALID_HANDLE_VALUE / -1 is valid to denote a bad handle, in this case NULL / 0 would be representing an invalid handle
 I also don't necessarily recommend checking ErrorLevel and A_LastError. For the latter, I think you aren't always guaranteed it's set to zero on success 
*/
If (!handle) {
    MsgBox SetupDiGetClassDevs call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
}

;2. enumerate devices from set
Member := Object()

;2.1 create Structure SP_DEVINFO_DATA
StructSize := 4 + 16 + 4 + A_PtrSize
VarSetCapacity(SP_DEVINFO_DATA, StructSize, 0)
;2.2 set cbSize in SP_DEVINFO_DATA
NumPut(StructSize, SP_DEVINFO_DATA, 0, "UInt")

; Define DEVPKEY_Device_DeviceDesc
VarSetCapacity(DEVPKEY_Device_DeviceDesc, 20)
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_DeviceDesc) ; fill in fmtid member of DEVPROPKEY struct
,NumPut(2, DEVPKEY_Device_DeviceDesc, 16, "UInt") ; fill in pid

VarSetCapacity(DEVPKEY_Device_FriendlyName, 20) ; you might consider looking at DEVPKEY_Device_BusReportedDeviceDesc too/instead
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_FriendlyName)
,NumPut(14, DEVPKEY_Device_FriendlyName, 16, "UInt")

VarSetCapacity(DEVPKEY_Device_HardwareIds, 20)
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_HardwareIds)
,NumPut(3, DEVPKEY_Device_HardwareIds, 16, "UInt")

Loop
{
    ;2.3 get first device from "device information set" to "SP_DEVINFO_DATA"
    ; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551010(v=vs.85).aspx
    If (!DllCall("Setupapi.dll\SetupDiEnumDeviceInfo", "Ptr", handle, "UInt", A_Index - 1, "Ptr", &SP_DEVINFO_DATA)) {
        If (A_LastError != 259) ;ERROR_NO_MORE_ITEMS
            MsgBox SetupDiEnumDeviceInfo call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
		break
    }

	; Empty all of these variables so that the values obtained from previous calls aren't used if SetupDiGetDevicePropertyW fails
	wszDeviceDesc := wszFriendlyName := hardwareIDs := ""

	; Get amount of memory needed to hold the resulting device description string
	if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_DeviceDesc, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) { ; ERROR_INSUFFICIENT_BUFFER
		VarSetCapacity(wszDeviceDesc, RequiredSize) ; allocate it
		if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_DeviceDesc, "UInt*", PropType, "WStr", wszDeviceDesc, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_DeviceDesc) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
	}

	if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_FriendlyName, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) { ; ERROR_INSUFFICIENT_BUFFER
		VarSetCapacity(wszFriendlyName, RequiredSize)
		if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_FriendlyName, "UInt*", PropType, "WStr", wszFriendlyName, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_FriendlyName) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
	}

	if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_HardwareIds, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) {
		VarSetCapacity(wmszHardwareIDs, RequiredSize)
		if (!DllCall("Setupapi.dll\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_HardwareIds, "UInt*", PropType, "Ptr", &wmszHardwareIDs, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_HardwareIds) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
		else {
			; Reading from this string is a little different, as it's a string containing multiple strings, delimited by a standard \0, but with a \0\0 at the very end
			lpHardwareID := &wmszHardwareIDs
			while (*lpHardwareID) {
				wszHardwareID := StrGet(lpHardwareID,, "UTF-16") ; Read string from buffer up to \0
				hardwareIDs .= wszHardwareID . ", " ; append said string to new string, delimited by a comma and space pair
				lpHardwareID += (DllCall("ntdll\wcslen", "Ptr", lpHardwareID, "CDecl UPtr") + 1) * 2 ; Advance to next null-terminated string
			}
			if ((cchhardwareIDs := StrLen(hardwareIDs)) && cchhardwareIDs > 2)
				NumPut(0, hardwareIDs, (cchhardwareIDs - 2) * (A_IsUnicode ? 2 : 1), A_IsUnicode ? "UShort" : "Char") ; Remove the last extra comma and space pair
		}
	}

	; 2.4 save value in array - I use Format here, but it doesn't add much. I guess plain string concatenation is a possibility...
	Member.Push(Format("{1:s}{2:s}{3:s}", wszDeviceDesc ? wszDeviceDesc . " " : ""
										, wszFriendlyName ? "(" . wszFriendlyName . ")" : ""
										, hardwareIDs ? " - " . hardwareIDs : ""))
}


; 3. destroy handle
; MSDN: https://msdn.microsoft.com/en-us/library/windows/hardware/ff550996(v=vs.85).aspx
DllCall("Setupapi.dll\SetupDiDestroyDeviceInfoList", "Ptr", handle)  ; you don't need error checking here: if it frees, it frees. No point in otherwise fretting. You already checked to see if handle != NULL and you got the DllCall right for this function

; 4. write to file
for _, add_line in Member
    FileAppend, %add_line%`n, test.txt

; 5. Unload setupapi.dll
DllCall("FreeLibrary", "Ptr", hModule)
chao-samu
Posts: 6
Joined: 05 Dec 2017, 11:44

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

09 Dec 2017, 12:58

Hi qwerty12, first off all, again thank you for your help without you it would have taken me very long to get so far. It have taken me even these days to figure it out what you have done. :)

Thx for the comments they helped me, i just deleted the ones i understand, I added also a additional function to get the Device Instance ID. Also i changed file encoding to UTF-16 but don't really know if that is really necessary,takes conversion place trough the FileAppend automatically? It's not necessary because an implicit conversion takes place on assigment isn't it?

Still not the end of questions maybe you can help me with this too ^^

- [line55-56] you added a comma before your DllCall and NumPut
- [line102] the asterix symbol before the lpHardwareID in the while condition
- [line115-120] If the DllCall "CM_Get_Device_IDW" fails LastError is 0 in the MsgBox dialogue, but the DllCall returns 0 if it succeed

Thank you! Thank you! Thank you! :xmas: :xmas: :xmas:

Code: Select all

; /////////////////////////////// INFO /////////////////////////////////////////
/*
Identify USB devices

Infos:
https://docs.microsoft.com/de-de/windows-hardware/drivers/install/device-information-sets

*/
; ////////////////////////////// AHK FLAGS /////////////////////////////////////
#NoEnv
; #Warn
SendMode Input
SetWorkingDir %A_ScriptDir%

; //////////////////////////////// FLAGS ///////////////////////////////////////
; Setupapi.h
DIGCF_DEFAULT :=          0x00000001  ; only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT :=          0x00000002
DIGCF_ALLCLASSES :=       0x00000004
DIGCF_PROFILE :=          0x00000008
DIGCF_DEVICEINTERFACE :=  0x00000010

;Devpkey.h
; -
; ////////////////////////////// MAIN //////////////////////////////////////////

; Ensure setupapi.dll remains loaded between each DllCall
hModule := DllCall("LoadLibrary", "Str", "setupapi.dll", "Ptr")
if (!hModule) {
	MsgBox % "LoadLibrary failed. A_LastError: " . A_LastError
	ExitApp 1
}

; get handle (DeviceInfoList)
handle := DllCall("setupapi.dll\SetupDiGetClassDevs", "Ptr", 0, "Str", "USB", "Ptr", 0, "UInt", DIGCF_PRESENT | DIGCF_ALLCLASSES, "Ptr")
If (!handle) {
    MsgBox SetupDiGetClassDevs call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%
}

; enumerate devices from set

; DEFINE
; ¯¯¯¯¯¯

; array holding file string
Member := Object()

; structure SP_DEVINFO_DATA (SP_DEVINFO_DATA structure)
StructSize := 4 + 16 + 4 + A_PtrSize
VarSetCapacity(SP_DEVINFO_DATA, StructSize, 0)
NumPut(StructSize, SP_DEVINFO_DATA, 0, "UInt") ; fill in cbSize

; structure DEVPKEY_Device_DeviceDesc (DEVPROPKEY structure)
VarSetCapacity(DEVPKEY_Device_DeviceDesc, 20)
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_DeviceDesc) ; fill in fmtid member of DEVPROPKEY struct
,NumPut(2, DEVPKEY_Device_DeviceDesc, 16, "UInt") ; fill in pid

; structure DEVPKEY_Device_FriendlyName (DEVPROPKEY structure)
VarSetCapacity(DEVPKEY_Device_FriendlyName, 20) ; you might consider looking at DEVPKEY_Device_BusReportedDeviceDesc too/instead
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_FriendlyName)
,NumPut(14, DEVPKEY_Device_FriendlyName, 16, "UInt")

; structure DEVPKEY_Device_HardwareIds (DEVPROPKEY structure)
VarSetCapacity(DEVPKEY_Device_HardwareIds, 20)
,DllCall("ole32\CLSIDFromString", "WStr", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "Ptr", &DEVPKEY_Device_HardwareIds)
,NumPut(3, DEVPKEY_Device_HardwareIds, 16, "UInt")

; ENUMERATE
; ¯¯¯¯¯¯¯¯¯

Loop
{
    ; get item from list
    If (!DllCall("setupapi.dll\SetupDiEnumDeviceInfo", "Ptr", handle, "UInt", A_Index - 1, "Ptr", &SP_DEVINFO_DATA)) {
        If (A_LastError != 259) ;ERROR_NO_MORE_ITEMS
            MsgBox SetupDiEnumDeviceInfo call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
		break
    }

	; Empty all of these variables so that the values obtained from previous calls aren't used if SetupDiGetDevicePropertyW fails
	wszDeviceDesc := wszFriendlyName := hardwareIDs := DeviceID := ""

	if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_DeviceDesc, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) { ; ERROR_INSUFFICIENT_BUFFER
		VarSetCapacity(wszDeviceDesc, RequiredSize)
		if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_DeviceDesc, "UInt*", PropType, "WStr", wszDeviceDesc, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_DeviceDesc) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
	}

	if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_FriendlyName, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) { ; ERROR_INSUFFICIENT_BUFFER
		VarSetCapacity(wszFriendlyName, RequiredSize)
		if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_FriendlyName, "UInt*", PropType, "WStr", wszFriendlyName, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_FriendlyName) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
	}

	if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_HardwareIds, "UInt*", PropType, "Ptr", 0, "UInt", 0, "UInt*", RequiredSize, "UInt", 0) && A_LastError == 122) {
		VarSetCapacity(wmszHardwareIDs, RequiredSize)
		if (!DllCall("setupapi\SetupDiGetDevicePropertyW", "Ptr", handle, "Ptr", &SP_DEVINFO_DATA, "Ptr", &DEVPKEY_Device_HardwareIds, "UInt*", PropType, "Ptr", &wmszHardwareIDs, "UInt", RequiredSize, "Ptr", 0, "UInt", 0))
			MsgBox SetupDiGetDevicePropertyW (DEVPKEY_Device_HardwareIds) call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
		else {
			; Reading from this string is a little different, as it's a string containing multiple strings, delimited by a standard \0, but with a \0\0 at the very end
			lpHardwareID := &wmszHardwareIDs
			while (*lpHardwareID) {
				wszHardwareID := StrGet(lpHardwareID,, "UTF-16") ; Read string from buffer up to \0
				hardwareIDs .= wszHardwareID . ", " ; append said string to new string, delimited by a comma and space pair
				lpHardwareID += (DllCall("ntdll\wcslen", "Ptr", lpHardwareID, "CDecl UPtr") + 1) * 2 ; Advance to next null-terminated string
			}
			if ((cchhardwareIDs := StrLen(hardwareIDs)) && cchhardwareIDs > 2)
				NumPut(0, hardwareIDs, (cchhardwareIDs - 2) * (A_IsUnicode ? 2 : 1), A_IsUnicode ? "UShort" : "Char") ; Remove the last extra comma and space pair
		}
	}

    DevInst := NumGet(SP_DEVINFO_DATA, 20, "UInt")
    If (!DllCall("Cfgmgr32\CM_Get_Device_ID_Size", "UInt*", RequiredSize, "UInt", DevInst, "UInt", 0)) {
        VarSetCapacity(wszDeviceID, RequiredSize)
        If (!DllCall("Cfgmgr32\CM_Get_Device_IDW", "UInt", DevInst, "Ptr", &wszDeviceID, "UInt", RequiredSize, "UInt", 0)) {
            lpDeviceID := &wszDeviceID           
            DeviceID := StrGet(lpDeviceID,, "UTF-16")
        }
        else
            MsgBox CM_Get_Device_IDW call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
    }

	; save value in array - I use Format here, but it doesn't add much. I guess plain string concatenation is a possibility...
	Member.Push(Format("{1:s}{2:s}{3:s}{4:s}"   , wszDeviceDesc ? wszDeviceDesc . " " : ""
                                                , wszFriendlyName ? "(" . wszFriendlyName . ")" : ""
                                                , hardwareIDs ? " - " . hardwareIDs : ""
                                                , DeviceID ? " - " . DeviceID : ""))                                                                         
}


; destroy handle (DeviceInfoList)
DllCall("setupapi\SetupDiDestroyDeviceInfoList", "Ptr", handle)  ; you don't need error checking here: if it frees, it frees. No point in otherwise fretting. You already checked to see if handle != NULL and you got the DllCall right for this function

; write to file
for _, add_line in Member
    FileAppend, %add_line%`n, device_enumeration.txt, UTF-16

; Unload setupapi.dll, Cfgmgr32.dll
DllCall("FreeLibrary", "Ptr", hModule)
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

09 Dec 2017, 13:39

Hi chao-samu,
I added also a additional function to get the Device Instance ID.
Nice addition :)
chao-samu wrote:Also i changed file encoding to UTF-16 but don't really know if that is really necessary, takes conversion place trough the FileAppend automatically?
According to FileAppend, it uses the value set by FileEncoding, which says "Empty or omitted: the system default ANSI code page, which is also the default setting" so I guess you would be right to explicitly specify UTF-16 for Encoding if that's what you want.
- [line55-56] you added a comma before your DllCall and NumPut
I'm quite inconsistent in my use of them usually, but I recall doing it there because visually, I wanted to show how those three lines are connected: the first allocates, the second fills in the first member of the structure, and the NumPut fills in the third member. This might be of some help:
https://autohotkey.com/docs/Variables.htm#Expression_Operators_in_descending_precedence_order wrote:Comma (multi-statement) [v1.0.46+]. Commas may be used to write multiple sub-expressions on a single line. This is most commonly used to group together multiple assignments or function calls. For example: x:=1, y+=2, ++index, func(). Such statements are executed in order from left to right. Note: A line that begins with a comma (or any other operator) is automatically appended to the line above it. See also: comma performance.

In v1.0.46.01+, when a comma is followed immediately by a variable and an equal sign, that equal sign is automatically treated as an assignment (:=). For example, all of the following are assignments: x:=1, y=2, a=b=c. New scripts should not rely on this behavior as it may change. The rule applies only to plain variables and not double-derefs, so the following contains only one assignment: x:=1, %y%=2

Performance: In v1.0.48+, the comma operator is usually faster than writing separate expressions, especially when assigning one variable to another (e.g. x:=y, a:=b). Performance continues to improve as more and more expressions are combined into a single expression; for example, it may be 35% faster to combine five or ten simple expressions into a single expression.
- [line102] the asterix symbol before the lpHardwareID in the while condition
https://autohotkey.com/docs/Variables.htm#Expression_Operators_in_descending_precedence_order wrote:Dereference (*): *Expression assumes that Expression resolves to a numeric memory address; it retrieves the byte at that address as a number between 0 and 255 (0 is always retrieved if the address is 0; but any other invalid address must be avoided because it might crash the script). However, NumGet() generally performs much better when retrieving binary numbers.
I think, technically, me using * is incorrect here for something that is two bytes a character (I should be using NumGet - which does the same thing, but allows for more flexibility - like the documentation page says, but I'm lazy and stuck with what worked for me twice before)
- [line115-120] If the DllCall "CM_Get_Device_IDW" fails LastError is 0 in the MsgBox dialogue, but the DllCall returns 0 if it succeed
https://msdn.microsoft.com/en-us/library/windows/hardware/ff538405(v=vs.85).aspx wrote:If the operation succeeds, the function returns CR_SUCCESS. Otherwise, it returns one of the CR_-prefixed error codes defined in Cfgmgr32.h.
As you saw, CR_SUCCESS is defined as 0x00000000. As for LastError being zero, I would assume CM_Get_Device_IDW doesn't even take a glance at SetLastError (so A_LastError is going to be 0 from whatever succeeded last and then called SLE(0)) because if CM_Get_Device_IDW fails, it returns an error code itself.

Happy holidays!
chao-samu
Posts: 6
Joined: 05 Dec 2017, 11:44

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

10 Dec 2017, 10:26

Thx for the clarification and effort again, normaly i'dont wanna ask a again, it feels already bad not to find own solutions.

But yesterday I found out, that my additional call destory the other Strings as soon as i change the identifier "USBSTOR" to "USB" in SetupDiGetClassDevs.

I have spend a few hours to find out the problem and have looked on the buffers in binary but was unable to locate the problem :(. Something always destroy the other strings, if i only call one dll function to look at one variable (wszDeviceDesc, wszFriendlyName, hardwareIDs or DeviceID) there are always correct, but as soon as all three are in action something always destroy the strings in the array/file, the code is still the same as before. For USB mass storages it works for me. Not yet sure if you can reproduce the problem.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

11 Dec 2017, 09:25

I'm sorry, but using the code from your last post, everything seems to work fine here on Windows 10 with one USB stick plugged in:
USBSTOR:
Disk drive (SanDisk Cruzer Blade USB Device) - USBSTOR\DiskSanDisk_Cruzer_Blade____1.00, USBSTOR\DiskSanDisk_Cruzer_Blade____, USBSTOR\DiskSanDisk_, USBSTOR\SanDisk_Cruzer_Blade____1, SanDisk_Cruzer_Blade____1, USBSTOR\GenDisk, GenDisk - USBSTOR\DISK&VEN_SANDISK&PROD_CRUZER_BLADE&REV_1.00\4C530001260927116233&0
USB:
Generic USB Hub - USB\VID_8087&PID_0024&REV_0000, USB\VID_8087&PID_0024 - USB\VID_8087&PID_0024\5&2738E312&0&1
USB Root Hub - USB\ROOT_HUB20&VID8086&PID1E2D&REV0004, USB\ROOT_HUB20&VID8086&PID1E2D, USB\ROOT_HUB20 - USB\ROOT_HUB20\4&19D0FD2A&0_8087&PID_0024\5&2738E312&0&1
USB Mass Storage Device - USB\VID_0781&PID_5567&REV_0100, USB\VID_0781&PID_5567 - USB\VID_0781&PID_5567\4C530001260927116233
Generic USB Hub - USB\VID_8087&PID_0024&REV_0000, USB\VID_8087&PID_0024 - USB\VID_8087&PID_0024\5&27A3C715&0&1116233
ThinkPad Bluetooth 4.0 - USB\VID_0A5C&PID_21E6&REV_0112, USB\VID_0A5C&PID_21E6 - USB\VID_0A5C&PID_21E6\9C2A708230A2&1116233
USB Root Hub - USB\ROOT_HUB20&VID8086&PID1E26&REV0004, USB\ROOT_HUB20&VID8086&PID1E26, USB\ROOT_HUB20 - USB\ROOT_HUB20\4&182122DF&008230A2&1116233
USB Root Hub (USB 3.0) - USB\ROOT_HUB30&VID8086&PID1E31&REV0004, USB\ROOT_HUB30&VID8086&PID1E31, USB\ROOT_HUB30 - USB\ROOT_HUB30\4&23ACE5CB&0&0230A2&1116233
That said, looking at the MSDN pages, there's quite the difference between SetupDiGetDevicePropertyW and CM_Get_Device_ID_Size / CM_Get_Device_ID.

SetupDiGetDevicePropertyW returns the size it wants in bytes, which is why I passed it straight to VarSetCapacity without a second thought. CM_Get_Device_ID_Size says it returns the size in characters and and does not include the space needed for the final terminating NULL character (0).

You call CM_Get_Device_IDW - the UTF-16 version of the function - but using the raw character count might not be enough because in a UTF-16 string, it's two bytes per character.

This might fix it for you:

Code: Select all

    DevInst := NumGet(SP_DEVINFO_DATA, 20, "UInt")
    If (!DllCall("Cfgmgr32\CM_Get_Device_ID_Size", "UInt*", RequiredSize, "UInt", DevInst, "UInt", 0)) {
		if (RequiredSize) { ; "If the specified device instance does not exist, the function supplies a size value of zero."
			RequiredSize++ ; add extra character to hold null character 
			VarSetCapacity(DeviceID, RequiredSize * (A_IsUnicode ? 2 : 1)) ; holding a utf-16 string requires twice the amount of the character count, because there's two characters per character
			; Like I said, the multiple string way was longer because of having to get multiple strings out of it. This is a normal, single string.
			; Knock off the A/W suffix (unless you want to explicitly call the ASCII / Unicode function) to have AutoHotkey call the appropriate one for the AutoHotkey version you're using (unlike SetupDiGetDeviceProperty, CM_Get_Device_ID comes in both forms)
			; and specify "Str" (not "WStr" like in previous calls, because here, the ASCII version might be called) to obtain a usable string directly from the buffer w/out going through StrGet. However, if the memory backing the string is allocated by the library itself - so not us with VarSetCapacity - and you're expected to free it yourself (here, you are not), then, yes, always use Ptr/StrGet
			If (DllCall("Cfgmgr32\CM_Get_Device_ID", "UInt", DevInst, "Str", DeviceID, "UInt", RequiredSize, "UInt", 0))
				MsgBox CM_Get_Device_IDW call failed.`nReturn value: %r%`nErrorLevel: %ErrorLevel%`nLine: %A_LineNumber%`nLast Error: %A_LastError%`nA_Index: %A_Index%
		}
	}
chao-samu wrote:Your right as always, without reproducing the problem you could fix it anyways. Nice work!

If have learned much from you, this helped me a lot. Nice to have such a member like you in this community. Thank you again!

Happy holidays!
No problem and glad you got your script working. Happy holidays!
Last edited by qwerty12 on 11 Dec 2017, 20:46, edited 1 time in total.
chao-samu
Posts: 6
Joined: 05 Dec 2017, 11:44

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

11 Dec 2017, 16:28

Your right as always, without reproducing the problem you could fix it anyways. Nice work!

If have learned much from you, this helped me a lot. Nice to have such a member like you in this community. Thank you again!

Happy holidays!
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: DllCall to SetupDiEnumDeviceInfo throws "ERROR_INVALID_USER_BUFFER"

01 Jul 2019, 06:53

Have you ever tried to query the driver version? SetupDiEnumDriverInfo function
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Anput, JoeWinograd, mikeyww, Nerafius, RandomBoy, Spawnova and 127 guests