Is it possible to properly access a USB device with VB6?
I'm facing the daunting task of interfacing a VB program with a USB volume control device.
I've been all over the internet and have found enough information to list USB devices, obtain the full device name needed to use CreateFile to get handles for reading and writing. But I think I have reached either VB's or my limits or maybe both.
It's a rather simple device, turning the knob left sends a 1 and twisting it to the right and you get a 2. This all works without a problem.
Now I need to configure the device by sending data to a different End Point and that's got me completely lost.
Is it even possible to direct output to a different USB End Point with VB?
Vb does not really do anything with USB. It can talk to USB devices via whatever driver the USB device uses provided that driver allows it. For example a USB comm port can be used with MSComm control. A USB keyboard or Mouse just works but there are any number of things that can be connected to a USB port and back when VB6 was released USB was fairly new and had not really taken hold yet.
This isn't trivial but it may be possible. It has nothing to do with VB6's age and C# coders struggle just as hard.
See the sample code in post #5 and beyond of TEMPer Gold USB HID Thermometer Class. The earlier samples require a funky DLL and the later ones do not.
Long story short:
Even though this doesn't deal with your specific device it may serve as an example of how a VB6 program can talk fairly directly to USB devices, i.e. without a specific driver.
I haven't looked at this code in years though, so I can't answer questions about it very quickly. And different devices each require a lot of low-level research and documentation scrounging. This isn't for the faint hearted.
You will probably need the Windows Driver Kit docs just as a starting point.
Last edited by dilettante; Jan 20th, 2016 at 11:56 PM.
Hey dilettante thanks for the info. When I first took a look at the USB specifications I just about fell off my chair. Holly cow, the Apollo Moon Mission seems trivial to this thing! There has to be a bunch of people sitting around some huge conference room table doing nothing more than dreaming up ways to complicate things.
As I mentioned, I have the basic reading and writing done, but performing any configurations of the device is another story. With my code I can detect the direction of the volume knob, rather straightforward yet I'm guessing 75% if not more of the code is comprised solely of API's, pretty crazy!
Really - could they have made this anymore obscure, confusing and complicated.
I know so much more about USB devices now then when I started but all it's done for me is created abnormally high blood pressure. The USB device I am working with is a rotary encoder which indicates and reports back to the host the direction it's being turned and when the shaft is being depressed.
It's a composite device which if I have this correct, is basically two USB devices built into one. You would think that being a composite device, that two device names would show up in the windows device manager, but I have found 4 separate DevicePathNames for this nightmarish piece of hardware.
I have been able to use CreateFile to get a valid handle to all 4 of the DevicePathNames, but I can only get one of them to respond to I/O requests using WriteFile and ReadFile. The other 3 don't respond to anything I've thrown at it.
The one DevicePathName that works, is allowing me to read the status of the rotary encoder but this only gets me halfway there. One of the other 3 DevicePathNames is the interface to the other/second composite device that I need in order to configure the device. This is where I am stuck!
I downloaded a great little monitoring program called DeviceIOView which captures all transactions from the Windows API DeviceIoControl so that you can see what's going on between a host program and a USB device.
I'm having a little problem with the DeviceIOView utility I downloaded as it displays the Device Names in a format that I don't recognized.
Currently I'm using a DevicePathName format that looks like this:
The DeviceIOView utility program is using a Device Name format like this:
Code:
"\Device\00000088"
Does anybody know how to translate between these two Device Name formats? I know it doesn't sound like much but it would be so helpful in testing new approaches. Instead of running every test 3 times, I could focus on a single DevicePathName.
can you post a link to the usb volume device .....a spec sheet or name of it ?
does it have install software already ?
Yep.
@OP
...without giving us the name(vendor) of your USB-Device, there's only so much one can do from here...
They usually come with drivers - or in one of the HID-categories - or support virtual COM-ports you
could connect to over the VB6-MsComm-serial-OCX (per Rs232).
Here's another (relative) cheap solution, the Controller-Board setting its USB-port
into HID-mode, so that you will receive normal Key-Strokes and Mouse-Events on your Win-OS.
There are many user-input devices beside the traditional keyboard and mouse. For example, user input can come from a joystick, a touch screen, a microphone, or other devices that allow great flexibility in user input. These devices are collectively known as Human Interface Devices (HIDs). The raw input API provides a stable and robust way for applications to accept raw input from any HID, including the keyboard and mouse.
I mentioned above about this being a composite device, having two separate USB devices in one package. Well I have now learned that not only are there two devices, but there are also two separate drivers involved.
For reading the direction of the encoder, left & right, a HID driver is used which I have working. As for the second device it uses the WinUSB driver which I can't seem to get working.
Using VB6 I have been able to use CreateFile to get a valid Handle to pass into a call to WinUsb_Initialize which returns successfully, but that's about as far as I can get. The most important WinUsb API calls fall stating that the handle returned from WinUsb_Initialize is invalid.
These calls work OK:
Code:
CreateFile Return Status: The operation completed successfully
WinUsb_Initialize Return Status: Overlapped I/O operation is in progress
Even though WinUsb_Initialize returns without error, I question the handle it returns as it's an odd type that I don't quite understand. Here is the questionable definition of the m_UsbHandle variable In C++ which I grabbed from some sample code:
For my VB code I defined m_UsbHandle as a Long, and some WinUsb calls appear to work, but then there are others that don't. A big stumbling block here is that I've learned a good number of standard feature supported by WinUsb, were not implemented by the programmer who developed the code for the encoder. From this I don't always know when I have a problem with an API call, or if it's because the features not supported.
The following routines return successfully, but without any significant information:
Code:
WinUsb_GetAssociatedInterface Return Status: The operation completed successfully
WinUsb_GetCurrentAlternateSetting Return Status: No More Data is Available
WinUsb_QueryInterfaceSettings Return Status: No More Data is Available
WinUsb_QueryPipe Return Status: No More Data is Available
While the entire project is in VB6, I have sort of put that aside and put together some C++ code thinking I might get a bit further with there being more example code available. This also has allowed me to remove the doubt surrounding the questionable m_UsbHandle variable definition.
So I put together a new C++ project, downloading missing files here and there. I was able to get CreateFile working and was ready to test the call to WinUsb_Initialize, when I learned that I don't have the required library needed to link the project! I have WinUsb.h, WinUsb.sys but no WinUsb.lib.
I'm assuming that WinUsb.lib is the file I need for this thing to link without error but I don't know for sure as I can't find it anywhere! I searched my Disks, CD's and the internet but either it's not out there or I missed it. So I'm guessing it's back to VB.
As for the Hardware, it appears that I am working with an older/Beta version of a USB-HID Volume Controller V2.0 from morecat_lab. We have exchanged a few emails but there is a bit of a language barrier. The more we talk the more I am wondering if we are working with the same device. I might be looking for a replacement encoder If I can't get this to work.
Last edited by stuck-n-past; Jan 31st, 2016 at 12:02 AM.
So I reload my WDK disk and found my missing file WinUsb.lib which has raised another question. I copied WinUsb.lib to the proper directory but C++ wasn't able to find it. After looking around I realized that my C++ is not pointing to the correct directory.
I have a multi-boot system and C++ isn't pointing to the correct disk partition. It's looking on drive F:\ when it should be using drive I:\. I've searched for where this might be defined in C++ but no luck. Does anybody know if this is like a re-install sort of fix, or is there just a registry setting that I can change?
Thanks.
Oh yea as for the C++ code, I have gotten a little bit further, but now I am working out another problem in trying to initialize the USB device. The newest error I'm fighting is: 'Not enough storage is available to process this command'.
I don't know how to pass a variable to WinUsb_Initialize in VB6 that is compatible with the type shown above, so looks like I'm going to be creating a C++ DLL that I can call from VB6.
Has anybody else run into problems with Instance Handles like this in VB6?
Tech99, that's what I thought and what I tried. In fact it was the first thing I did, using a long variable as the second parameter but it failed every time. I worked on it endlessly without success, that's when I converted my code to C++ and things worked the first time around.
I know handles are strange animals but they tend to follow a pattern at times. Opening the USB device with CreateFile returns an integer handle in a range of 1500 - 2400. I have printed out the handle in both my VB6 and C++ programs dozens of times. Actually I'm probably closer to printing them out hundreds of times by now, and the handles are always in the same range.
I have done the same thing with the WinUsb_Initialize instance handle, printing it out every time I do a test run. In the C++ program the value oddly comes up the same each time 1325472. In VB the handle is much larger at a value of 119400256, some 90 times greater then in the C++ version! Right away this sent up a red flag. And it's consistently this large.
I know it sounds strange to make a decision based on the value of a handle since they are somewhat arbitrary/random, but not only does the handle look wrong, when I try and use it with any of the WinUsb routines an error shows up that the handle is invalid!
And it's not like the WinUsb_Initialize routine is complicated, two parameters and that's it. And some of the other WinUsb routines are just a simple, sort of hard to mess them up.
I wanted to force the WinUsb_Initialize to fail just to make sure my code was working trapping and reporting any errors. I pulled out the overlapped flag on the CreateFile routine which is mandatory for WinUsb_Initialize to work. And sure enough the call failed and I caught the error.
I know this all sounds strange, but converting VB6 to C++ is fairly straight forward, in fact the two programs look very close to one another, almost line for line. If it's not something to do with the definition of this opaque instance handle, then I'm lost as to what it could be.
It's hard to guess without code, but it really sound's like a case of a wrong API declares. Somewhere you should be using a Pointer to a handle (ByRef) in the declare and not just a handle (ByVal).
You definitely want the second parameter ByRef as Long
Public Declare Function WinUsb_Initialize Lib "winusb" (ByVal DeviceHandle As Long, ByRef InterfaceHandle As Long) As Long
Last edited by DEXWERX; Feb 1st, 2016 at 12:20 PM.
I have done the same thing with the WinUsb_Initialize instance handle, printing it out every time I do a test run. In the C++ program the value oddly comes up the same each time 1325472. In VB the handle is much larger at a value of 119400256, some 90 times greater then in the C++ version! Right away this sent up a red flag.
You can't base success/failure by evaluating handle value. Absolutely no, even if/when handle value follow/do not follow certain pattern/value range between languages.
Use the declaration listed in above and examine function return value.
Code:
success = WinUsb_Initialize (myDevInfo.deviceHandle, MyDevInfo.winUsbHandle)
If success Then...
I have a check after all the API calls, and the declaration matches what you have shown. The call to WinUsb_Initialize is successful, the very next line of code after the 'if success' is a call to WinUsb_QueryInterfaceSettings, which says the instance handle is invalid.
These few lines of VB6 code are almost identical to my C++ version, however the C++ version works. The only real difference is the deceleration of the instance handle variable.
I have a check after all the API calls, and the declaration matches what you have shown. The call to WinUsb_Initialize is successful, the very next line of code after the 'if success' is a call to WinUsb_QueryInterfaceSettings, which says the instance handle is invalid.
It seems that you didn't call IIDFromString and/or SetupDiGetClassDevs function, before init.
Actually that's close to one of the first routines I call.
I call HidD_GetHidGuid to start things off and then I call SetupDiGetClassDevs.
I have been going over both the VB6 and C++ code trying to see where there might be any differences, but I can't find anything. So with things working so well in C++ I think it's time to build a DLL wrapper that I can call from the VB project. Sometimes you just have to go with what works.
It won't take too much time to code the wrapper, and perhaps once the project is done, I can revisit the issue searching for a solution to the problem.
I've spent a fairly tremendous amount of time working with USB in VB6, specifically with USB printers.
Here's a snippet that has to do with generating a list of all the USB printers currently on the system using only built-in system APIs. It may be missing a few declares since its a snippet of a much much larger tool, but it should give the idea.
The output of the function is a 2d array containing a magic path you can use with createfile, readfile and writefile in the 0 element, and the driver key (aka software key) in the 1 element. The output would be something like:
for the first string (which has the vid, the pid, the serial number and the port's GUID), and
{36fc9e60-c465-11cf-8056-444553540000}\0152
for the second.
This particular code also does other things, like determine which USB stack is responsible for managing the printer device (in windows you can have more than one USB stack installed), and also who is the parent device (the hub) of the printer (though some of that is commented out).
If it would be useful I could boil this down into an example but it seems you are already pretty close to a working solution.
Code:
Private Const DIGCF_PRESENT = &H2
Private Const DIGCF_DEVICEINTERFACE = &H10
Const DIGCF_ALLCLASSES As Long = &H4
Const OVERLAPPED_IO_PENDING = 997
Const STATUS_PENDING = 259
Private Type OVERLAPPED
lpInternal As Long
lpInternalHigh As Long
offset As Long
OffsetHigh As Long
hEvent As Long
End Type
Private Type SP_DEVICE_INTERFACE_DETAIL_DATA
cbSize As Long
DataPath(256) As Byte
End Type
Private Type SP_DEVICE_INTERFACE_DATA
cbSize As Long
InterfaceClassGuid As GUID
Flags As Long
ReservedPtr As Long
End Type
Private Type SP_DEVINFO_DATA
cbSize As Long
InterfaceClassGuid As GUID
hDevInst As Long
ReservedPtr As Long
End Type
Private Declare Function SetupDiGetClassDevs Lib "setupapi.dll" Alias "SetupDiGetClassDevsA" (HidGuid As GUID, ByVal EnumPtr As Long, ByVal hwndParent As Long, ByVal Flags As Long) As Long
Private Declare Function SetupDiDestroyDeviceInfoList Lib "setupapi.dll" (ByVal DeviceInfoSet As Long) As Boolean
Private Declare Function SetupDiEnumDeviceInterfaces Lib "setupapi.dll" (ByVal Handle As Long, ByVal InfoPtr As Long, HidGuid As GUID, ByVal MemberIndex As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
Private Declare Function SetupDiEnumDeviceInterfacesWithInfoPtr Lib "setupapi.dll" Alias "SetupDiEnumDeviceInterfaces" (ByVal Handle As Long, ByRef InfoPtr As SP_DEVINFO_DATA, HidGuid As GUID, ByVal MemberIndex As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
Private Declare Function SetupDiGetDeviceInterfaceDetail Lib "setupapi.dll" Alias "SetupDiGetDeviceInterfaceDetailA" (ByVal Handle As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, FunctionClassDeviceData As SP_DEVICE_INTERFACE_DETAIL_DATA, ByVal DetailLength As Long, lpReturnedLength As Long, deviceInfoData As SP_DEVINFO_DATA) As Boolean
Private Declare Function SetupDiOpenDeviceInfo Lib "setupapi.dll" Alias "SetupDiOpenDeviceInfoA" (ByVal DeviceInfoSet As Long, ByRef DeviceInstanceId As Any, ByVal hwndParent As Long, ByVal OpenFlags As Long, ByRef deviceInfoData As SP_DEVINFO_DATA) As Long
Private Declare Function CM_Get_Device_ID_Size Lib "cfgmgr32.dll" (ByRef pulLen As Long, ByVal dnDevInst As Long, ByVal ulFlags As Long) As Long
Private Declare Function CM_Get_Device_ID Lib "cfgmgr32.dll" Alias "CM_Get_Device_IDA" (ByVal lDnDevInst As Long, pBuffer As Any, lLength As Long, ByVal lFlags As Long) As Long
Private Declare Function CM_Get_Parent Lib "cfgmgr32.dll" (lDnDevInst As Long, ByVal lDnDevInst As Long, ByVal lFlags As Long) As Long
Private Declare Function CM_Get_Sibling Lib "cfgmgr32.dll" (lDnDevInst As Long, ByVal lDnDevInst As Long, ByVal lFlags As Long) As Long
Private PrinterGUID As GUID 'Used for enumerating USB Devices
Private USBStackGUID As GUID 'Used for enumerating USB Devices
Function Init()
PrinterGUID.DataL = &H28D78FAD
PrinterGUID.data1 = &H5A12
PrinterGUID.data2 = &H11D1
PrinterGUID.DataB(0) = &HAE
PrinterGUID.DataB(1) = &H5B
PrinterGUID.DataB(2) = 0
PrinterGUID.DataB(3) = 0
PrinterGUID.DataB(4) = &HF8
PrinterGUID.DataB(5) = 3
PrinterGUID.DataB(6) = &HA8
PrinterGUID.DataB(7) = &HC2
USBStackGUID.DataL = &HF18A0E88
USBStackGUID.data1 = &HC30C
USBStackGUID.data2 = &H11D0
USBStackGUID.DataB(0) = &H88
USBStackGUID.DataB(1) = &H15
USBStackGUID.DataB(2) = 0
USBStackGUID.DataB(3) = &HA0
USBStackGUID.DataB(4) = &HC9
USBStackGUID.DataB(5) = &H6
USBStackGUID.DataB(6) = &HBE
USBStackGUID.DataB(7) = &HD8
End Function
Private Function CreateListOfUSBPrinters(Optional lngErrorResult As Long = 0) As String()
Dim HDEVINFO_USBPrinterClassDevs As Long
Dim HDEVINFO_USBClassDevs As Long
Dim PSP_DEVINFO_DATA_NULL As Long
Dim PDEVINST_ParentDevInst As Long
Dim typChildDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
Dim typChildDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA
Dim typChildDevInfoData As SP_DEVINFO_DATA
Dim strChildPath As String
Dim typParentDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
'Dim typParentDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA
Dim typParentDevInfoData As SP_DEVINFO_DATA
Dim bytParentDeviceInstanceID() As Byte
'Dim strParentPath As String
Dim strHoldingArray() As String
Dim lngResultingSize As Long
Dim DP_SIZE As Long
If Globals.InIde Then On Error GoTo 0 Else On Error GoTo LocalErrorHandler
DP_SIZE = UBound(typChildDeviceInterfaceDetailData.DataPath)
ReDim bytParentDeviceInstanceID(DP_SIZE) As Byte
Const MIN_SIZE = 5
Const CR_SUCCESS = 0
Const ERROR_NO_MORE_ITEMS = 259
Dim lngDeviceInterfaceIndex As Long
Dim lngDeviceInterfaceCount As Long
' Fill in initial sizes for structures
typChildDeviceInterfaceData.cbSize = Len(typChildDeviceInterfaceData)
typChildDevInfoData.cbSize = Len(typChildDevInfoData)
' Fill in initial sizes for structures
typParentDeviceInterfaceData.cbSize = Len(typParentDeviceInterfaceData)
typParentDevInfoData.cbSize = Len(typParentDevInfoData)
lngErrorResult = 0
Do
' Create initial device information lists - one for USB Printers, one for generic USB devices
HDEVINFO_USBPrinterClassDevs = SetupDiGetClassDevs(PrinterGUID, 0, 0, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
HDEVINFO_USBClassDevs = SetupDiGetClassDevs(USBStackGUID, 0, 0, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
' If either is invalid, give up
If HDEVINFO_USBPrinterClassDevs = INVALID_HANDLE_VALUE Then Exit Do
If HDEVINFO_USBClassDevs = INVALID_HANDLE_VALUE Then Exit Do
' Count how many USB printer devices we have
Do While SetupDiEnumDeviceInterfaces(HDEVINFO_USBPrinterClassDevs, PSP_DEVINFO_DATA_NULL, PrinterGUID, lngDeviceInterfaceCount, typChildDeviceInterfaceData)
lngDeviceInterfaceCount = lngDeviceInterfaceCount + 1
Loop
' If we have zero, or we had an error, give up
If Err.LastDllError <> ERROR_NO_MORE_ITEMS Or lngDeviceInterfaceCount = 0 Then Exit Do
ReDim strHoldingArray(0 To lngDeviceInterfaceCount - 1, 0 To 1) As String
' Interate over each device
Do While SetupDiEnumDeviceInterfaces(HDEVINFO_USBPrinterClassDevs, PSP_DEVINFO_DATA_NULL, PrinterGUID, lngDeviceInterfaceIndex, typChildDeviceInterfaceData)
' You must setup this structure to the fixed size, not the total size. The fixed size is 5 bytes.
typChildDeviceInterfaceDetailData.cbSize = MIN_SIZE
ZeroMemory typChildDeviceInterfaceDetailData.DataPath(0), DP_SIZE
' Get the child's interface detail (path)
If False = SetupDiGetDeviceInterfaceDetail(HDEVINFO_USBPrinterClassDevs, typChildDeviceInterfaceData, typChildDeviceInterfaceDetailData, DP_SIZE, lngResultingSize, typChildDevInfoData) Then
Exit Do
End If
' Generate the Child's path
strChildPath = StrConv(typChildDeviceInterfaceDetailData.DataPath, vbUnicode)
strChildPath = Left(strChildPath, lngResultingSize - MIN_SIZE)
Dim varResult As Variant
Dim typSP As typSP_DEVINFO_DATA
CopyMemory typSP.lSize, typChildDevInfoData.cbSize, typChildDevInfoData.cbSize
If SetupDiGetDeviceRegistryProperty(HDEVINFO_USBPrinterClassDevs, typSP, SPDRP_DRIVER, varResult) = False Then
Exit Do
End If
' Get the parent's Device Instance Handle
If CR_SUCCESS <> CM_Get_Parent(PDEVINST_ParentDevInst, typChildDevInfoData.hDevInst, 0) Then
Exit Do
End If
' Clear the Parent's device ID
'ZeroMemory bytParentDeviceInstanceID(0), DP_SIZE
'lngResultingSize = 0
' Get the Parentn's Device Instance ID string
'If CR_SUCCESS <> CM_Get_Device_ID(PDEVINST_ParentDevInst, bytParentDeviceInstanceID(0), DP_SIZE, 0) Then
' Exit Do
'End If
' From that string get a Device Info Data structure, and add it to the USB Class device information list
' This will also generate us our parent's device information data
'If False = SetupDiOpenDeviceInfo(HDEVINFO_USBClassDevs, bytParentDeviceInstanceID(0), 0, 0, typParentDevInfoData) Then
' Exit Do
'End If
' Enumerate the device we just added to get the parent's device interface data
'If False = SetupDiEnumDeviceInterfacesWithInfoPtr(HDEVINFO_USBClassDevs, typParentDevInfoData, USBStackGUID, 0, typParentDeviceInterfaceData) Then
' Exit Do
'End If
' Clear the structure
'typParentDeviceInterfaceDetailData.cbSize = MIN_SIZE
'ZeroMemory typParentDeviceInterfaceDetailData.DataPath(0), DP_SIZE
' Get the parent's interface detail (path)
'If False = SetupDiGetDeviceInterfaceDetail(HDEVINFO_USBClassDevs, typParentDeviceInterfaceData, typParentDeviceInterfaceDetailData, DP_SIZE, lngResultingSize, typParentDevInfoData) Then
' Exit Do
'End If
' Generate the Parent's path
'strParentPath = StrConv(typParentDeviceInterfaceDetailData.DataPath, vbUnicode)
'strParentPath = Left(strParentPath, lngResultingSize - MIN_SIZE)
strHoldingArray(lngDeviceInterfaceIndex, 0) = strChildPath
strHoldingArray(lngDeviceInterfaceIndex, 1) = varResult
lngDeviceInterfaceIndex = lngDeviceInterfaceIndex + 1
Loop
Exit Do
Loop
lngErrorResult = Err.LastDllError
SetupDiDestroyDeviceInfoList HDEVINFO_USBClassDevs
SetupDiDestroyDeviceInfoList HDEVINFO_USBPrinterClassDevs
CreateListOfUSBPrinters = strHoldingArray
Exit Function
LocalErrorHandler:
If GlobalError("clsCommuncation:CreateListOfUSBPrinters", Err.Number, Err.Source, Err.Description) = 1 Then Resume Next
End Function
Hey interactii, thanks for your post. This project is my first attempt at working with USB devices and it truly does seem more complicated then it needs to be, dilettante was quite accurate in his remark
This isn't trivial but it may be possible. It has nothing to do with VB6's age and C# coders struggle just as hard.
At the start of the project I assumed I would be working with a single interface, specifically one supporting HID devices. Going in that direction my largest hurdle was in how to specify the Endpoint I wanted to use. Reading through USB documentation, I came across a method used to select the desired Endpoint by tacking the Pipe ID to the end of the device name. I never could get it to work and started to think it might be outdated information. That was until I read your post and saw something that caught my eye.
{36fc9e60-c465-11cf-8056-444553540000}\0152
In the above quote, is the "\0152" specifying an Endpoint? If so, I might go back and take another look at using the HID routines to access the device.
I've completed the C++ DLL which I now use in the VB project, so everything is pretty much done in regards to the USB interface, but the simpler you can make things, the better success there tends to be.
Something got me to thinking about this USB project. Coding in VB I made use of the WinUsb.DLL to access the appropriate routines. I then coded the same project over in C++ but this time I made use of the WinUsb.lib, calling the routines directly or so I'm guessing. Does that mean that the WinUsb Library was also written in C++?
.lib files are also created for VB6 ActiveX DLLs. It just lets the linker know how to call the dll. I'm guessing it was written in C++, but the lib file has nothing to do with it.
When my VB program calls a routine like WinUsb_Initialize does it use WinUsb.DLL alone, or does it also use WinUsb.lib?
The information in the .Lib is compiled into your executable. So... it depends what you mean. You'll never need the .lib again, after your program is compiled. the .lib is IN your program It's no longer a dependancy.
The WinUSB.DLL is (sort of, you definitely don't want to redistribute that dependancy)
Well actually i always use USB to VB when connecting to my microcontroller, i have a source code library from Jan Axelson from the book "USB Complete 4th edition" http://janaxelson.com/usbc.htm
here is the simple code that covers the basic usb communication, coded in PURE VB with only Windows API. Hope it helps!
Last edited by si_the_geek; Feb 10th, 2016 at 06:44 AM.
Reason: removed executable
I have removed the executable file your attachment to protect our members, because we have no way of knowing what an executable file actually does - which could include something malicious, such as if there is a virus on your computer.
One of the reasons I was asking about the libraries is that in my C++ program there is an option to use shared DLL's or static libraries. When I have the option set to use DLL's the program doesn't run properly. Changing the option to use static libraries and everything works. This made me wonder if I have a bad DLL file which is not only causing a problem with the C++ program, but perhaps it's the gremlin I've been chasing in my VB program.
By using the static library option in C++, could I be bypassing a corrupt file? From what everybody is saying, there is no reason why WinUsb_Initialize should be failing in VB but working just fine in C++.
static linking only applies to runtime files, not OS DLLs. If you don't static link, you have to have the redistributable runtime DLLs installed for whatever version of MSVC you are using... although that should be installed when you installed VC??
Oh well, so much for the theory of having a bad DLL file on the VB side of things. Having the guts of the project done in C++, the VB WinUsb_Initialize Mystery might just go unsolved.
astralist, thanks for the code example. I have a question about one of the SetupDi calls made using VarPtr and a byte array. Couldn't the same thing be accomplished by passing the parameter DetailDataBuffer by reference to have it's address placed on the call stack?
happy to say that I'm done with the C++ part of the code and back to VB. With all of the USB I/O stuff done I have been cleaning up working on the user interface when I came across some confusion with Device Names.
Using the SetupDi API routines to retrieve the DevicePathName to hand off to CreateFile, I've found that it doesn't quite work the way the documentation says is should. The problem is with the GUID tacked onto the end of the USB device name and whether it's a Class or Interface GUID.
From what I can tell, CreateFile wants a Class GUID on the end of the device name, but the SetupDi routines, specifically, SetupDiGetDeviceInterfaceDetail, returns an Interface GUID. Using this Inteface GUID from the SP_DEVICE_INTERFACE_DETAIL_DATA structure fails when used with CreateFile.
This is only true when using a 'wild card' search with SetupDiGetClassDevs to return all devices. If passing a Class GUID to SetupDiGetClassDevs, then that's what will be returned in the DevicePathName.
So is there anyway to use an interface GUID to find the class GUID it belongs to?
Thanks.
Last edited by stuck-n-past; Feb 13th, 2016 at 05:54 PM.
I surrender! From time to time I have gone gone back to work on the USB code. For standard devices you can use known GUID's such as {4d1e55b2-f16f-11cf-88cb-001111000030} for a HID interface, for non standard devices I have only found the proper GUID hiding in the associated ini file. I've tried using so many different API calls, DeviceIoControl, SetupDi, CM_, CreateFile... and a ton of others, I've ended up with enough test code to have a VB6 version of USBView. Even with all that code I Still cant find a better way to locate the proper GUID other then using the ini file. Either the ini file is the way to go or a better way doesn't exist. Then again there is a good chance I've just been barking up the wrong tree.
I've spent a fairly tremendous amount of time working with USB in VB6, specifically with USB printers.
Here's a snippet that has to do with generating a list of all the USB printers currently on the system using only built-in system APIs. It may be missing a few declares since its a snippet of a much much larger tool, but it should give the idea.
The output of the function is a 2d array containing a magic path you can use with createfile, readfile and writefile in the 0 element, and the driver key (aka software key) in the 1 element. The output would be something like:
for the first string (which has the vid, the pid, the serial number and the port's GUID), and
{36fc9e60-c465-11cf-8056-444553540000}\0152
for the second.
This particular code also does other things, like determine which USB stack is responsible for managing the printer device (in windows you can have more than one USB stack installed), and also who is the parent device (the hub) of the printer (though some of that is commented out).
If it would be useful I could boil this down into an example but it seems you are already pretty close to a working solution.
Code:
Private Const DIGCF_PRESENT = &H2
Private Const DIGCF_DEVICEINTERFACE = &H10
Const DIGCF_ALLCLASSES As Long = &H4
Const OVERLAPPED_IO_PENDING = 997
Const STATUS_PENDING = 259
Private Type OVERLAPPED
lpInternal As Long
lpInternalHigh As Long
offset As Long
OffsetHigh As Long
hEvent As Long
End Type
Private Type SP_DEVICE_INTERFACE_DETAIL_DATA
cbSize As Long
DataPath(256) As Byte
End Type
Private Type SP_DEVICE_INTERFACE_DATA
cbSize As Long
InterfaceClassGuid As GUID
Flags As Long
ReservedPtr As Long
End Type
Private Type SP_DEVINFO_DATA
cbSize As Long
InterfaceClassGuid As GUID
hDevInst As Long
ReservedPtr As Long
End Type
Private Declare Function SetupDiGetClassDevs Lib "setupapi.dll" Alias "SetupDiGetClassDevsA" (HidGuid As GUID, ByVal EnumPtr As Long, ByVal hwndParent As Long, ByVal Flags As Long) As Long
Private Declare Function SetupDiDestroyDeviceInfoList Lib "setupapi.dll" (ByVal DeviceInfoSet As Long) As Boolean
Private Declare Function SetupDiEnumDeviceInterfaces Lib "setupapi.dll" (ByVal Handle As Long, ByVal InfoPtr As Long, HidGuid As GUID, ByVal MemberIndex As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
Private Declare Function SetupDiEnumDeviceInterfacesWithInfoPtr Lib "setupapi.dll" Alias "SetupDiEnumDeviceInterfaces" (ByVal Handle As Long, ByRef InfoPtr As SP_DEVINFO_DATA, HidGuid As GUID, ByVal MemberIndex As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
Private Declare Function SetupDiGetDeviceInterfaceDetail Lib "setupapi.dll" Alias "SetupDiGetDeviceInterfaceDetailA" (ByVal Handle As Long, DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, FunctionClassDeviceData As SP_DEVICE_INTERFACE_DETAIL_DATA, ByVal DetailLength As Long, lpReturnedLength As Long, deviceInfoData As SP_DEVINFO_DATA) As Boolean
Private Declare Function SetupDiOpenDeviceInfo Lib "setupapi.dll" Alias "SetupDiOpenDeviceInfoA" (ByVal DeviceInfoSet As Long, ByRef DeviceInstanceId As Any, ByVal hwndParent As Long, ByVal OpenFlags As Long, ByRef deviceInfoData As SP_DEVINFO_DATA) As Long
Private Declare Function CM_Get_Device_ID_Size Lib "cfgmgr32.dll" (ByRef pulLen As Long, ByVal dnDevInst As Long, ByVal ulFlags As Long) As Long
Private Declare Function CM_Get_Device_ID Lib "cfgmgr32.dll" Alias "CM_Get_Device_IDA" (ByVal lDnDevInst As Long, pBuffer As Any, lLength As Long, ByVal lFlags As Long) As Long
Private Declare Function CM_Get_Parent Lib "cfgmgr32.dll" (lDnDevInst As Long, ByVal lDnDevInst As Long, ByVal lFlags As Long) As Long
Private Declare Function CM_Get_Sibling Lib "cfgmgr32.dll" (lDnDevInst As Long, ByVal lDnDevInst As Long, ByVal lFlags As Long) As Long
Private PrinterGUID As GUID 'Used for enumerating USB Devices
Private USBStackGUID As GUID 'Used for enumerating USB Devices
Function Init()
PrinterGUID.DataL = &H28D78FAD
PrinterGUID.data1 = &H5A12
PrinterGUID.data2 = &H11D1
PrinterGUID.DataB(0) = &HAE
PrinterGUID.DataB(1) = &H5B
PrinterGUID.DataB(2) = 0
PrinterGUID.DataB(3) = 0
PrinterGUID.DataB(4) = &HF8
PrinterGUID.DataB(5) = 3
PrinterGUID.DataB(6) = &HA8
PrinterGUID.DataB(7) = &HC2
USBStackGUID.DataL = &HF18A0E88
USBStackGUID.data1 = &HC30C
USBStackGUID.data2 = &H11D0
USBStackGUID.DataB(0) = &H88
USBStackGUID.DataB(1) = &H15
USBStackGUID.DataB(2) = 0
USBStackGUID.DataB(3) = &HA0
USBStackGUID.DataB(4) = &HC9
USBStackGUID.DataB(5) = &H6
USBStackGUID.DataB(6) = &HBE
USBStackGUID.DataB(7) = &HD8
End Function
Private Function CreateListOfUSBPrinters(Optional lngErrorResult As Long = 0) As String()
Dim HDEVINFO_USBPrinterClassDevs As Long
Dim HDEVINFO_USBClassDevs As Long
Dim PSP_DEVINFO_DATA_NULL As Long
Dim PDEVINST_ParentDevInst As Long
Dim typChildDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
Dim typChildDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA
Dim typChildDevInfoData As SP_DEVINFO_DATA
Dim strChildPath As String
Dim typParentDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
'Dim typParentDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA
Dim typParentDevInfoData As SP_DEVINFO_DATA
Dim bytParentDeviceInstanceID() As Byte
'Dim strParentPath As String
Dim strHoldingArray() As String
Dim lngResultingSize As Long
Dim DP_SIZE As Long
If Globals.InIde Then On Error GoTo 0 Else On Error GoTo LocalErrorHandler
DP_SIZE = UBound(typChildDeviceInterfaceDetailData.DataPath)
ReDim bytParentDeviceInstanceID(DP_SIZE) As Byte
Const MIN_SIZE = 5
Const CR_SUCCESS = 0
Const ERROR_NO_MORE_ITEMS = 259
Dim lngDeviceInterfaceIndex As Long
Dim lngDeviceInterfaceCount As Long
' Fill in initial sizes for structures
typChildDeviceInterfaceData.cbSize = Len(typChildDeviceInterfaceData)
typChildDevInfoData.cbSize = Len(typChildDevInfoData)
' Fill in initial sizes for structures
typParentDeviceInterfaceData.cbSize = Len(typParentDeviceInterfaceData)
typParentDevInfoData.cbSize = Len(typParentDevInfoData)
lngErrorResult = 0
Do
' Create initial device information lists - one for USB Printers, one for generic USB devices
HDEVINFO_USBPrinterClassDevs = SetupDiGetClassDevs(PrinterGUID, 0, 0, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
HDEVINFO_USBClassDevs = SetupDiGetClassDevs(USBStackGUID, 0, 0, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
' If either is invalid, give up
If HDEVINFO_USBPrinterClassDevs = INVALID_HANDLE_VALUE Then Exit Do
If HDEVINFO_USBClassDevs = INVALID_HANDLE_VALUE Then Exit Do
' Count how many USB printer devices we have
Do While SetupDiEnumDeviceInterfaces(HDEVINFO_USBPrinterClassDevs, PSP_DEVINFO_DATA_NULL, PrinterGUID, lngDeviceInterfaceCount, typChildDeviceInterfaceData)
lngDeviceInterfaceCount = lngDeviceInterfaceCount + 1
Loop
' If we have zero, or we had an error, give up
If Err.LastDllError <> ERROR_NO_MORE_ITEMS Or lngDeviceInterfaceCount = 0 Then Exit Do
ReDim strHoldingArray(0 To lngDeviceInterfaceCount - 1, 0 To 1) As String
' Interate over each device
Do While SetupDiEnumDeviceInterfaces(HDEVINFO_USBPrinterClassDevs, PSP_DEVINFO_DATA_NULL, PrinterGUID, lngDeviceInterfaceIndex, typChildDeviceInterfaceData)
' You must setup this structure to the fixed size, not the total size. The fixed size is 5 bytes.
typChildDeviceInterfaceDetailData.cbSize = MIN_SIZE
ZeroMemory typChildDeviceInterfaceDetailData.DataPath(0), DP_SIZE
' Get the child's interface detail (path)
If False = SetupDiGetDeviceInterfaceDetail(HDEVINFO_USBPrinterClassDevs, typChildDeviceInterfaceData, typChildDeviceInterfaceDetailData, DP_SIZE, lngResultingSize, typChildDevInfoData) Then
Exit Do
End If
' Generate the Child's path
strChildPath = StrConv(typChildDeviceInterfaceDetailData.DataPath, vbUnicode)
strChildPath = Left(strChildPath, lngResultingSize - MIN_SIZE)
Dim varResult As Variant
Dim typSP As typSP_DEVINFO_DATA
CopyMemory typSP.lSize, typChildDevInfoData.cbSize, typChildDevInfoData.cbSize
If SetupDiGetDeviceRegistryProperty(HDEVINFO_USBPrinterClassDevs, typSP, SPDRP_DRIVER, varResult) = False Then
Exit Do
End If
' Get the parent's Device Instance Handle
If CR_SUCCESS <> CM_Get_Parent(PDEVINST_ParentDevInst, typChildDevInfoData.hDevInst, 0) Then
Exit Do
End If
' Clear the Parent's device ID
'ZeroMemory bytParentDeviceInstanceID(0), DP_SIZE
'lngResultingSize = 0
' Get the Parentn's Device Instance ID string
'If CR_SUCCESS <> CM_Get_Device_ID(PDEVINST_ParentDevInst, bytParentDeviceInstanceID(0), DP_SIZE, 0) Then
' Exit Do
'End If
' From that string get a Device Info Data structure, and add it to the USB Class device information list
' This will also generate us our parent's device information data
'If False = SetupDiOpenDeviceInfo(HDEVINFO_USBClassDevs, bytParentDeviceInstanceID(0), 0, 0, typParentDevInfoData) Then
' Exit Do
'End If
' Enumerate the device we just added to get the parent's device interface data
'If False = SetupDiEnumDeviceInterfacesWithInfoPtr(HDEVINFO_USBClassDevs, typParentDevInfoData, USBStackGUID, 0, typParentDeviceInterfaceData) Then
' Exit Do
'End If
' Clear the structure
'typParentDeviceInterfaceDetailData.cbSize = MIN_SIZE
'ZeroMemory typParentDeviceInterfaceDetailData.DataPath(0), DP_SIZE
' Get the parent's interface detail (path)
'If False = SetupDiGetDeviceInterfaceDetail(HDEVINFO_USBClassDevs, typParentDeviceInterfaceData, typParentDeviceInterfaceDetailData, DP_SIZE, lngResultingSize, typParentDevInfoData) Then
' Exit Do
'End If
' Generate the Parent's path
'strParentPath = StrConv(typParentDeviceInterfaceDetailData.DataPath, vbUnicode)
'strParentPath = Left(strParentPath, lngResultingSize - MIN_SIZE)
strHoldingArray(lngDeviceInterfaceIndex, 0) = strChildPath
strHoldingArray(lngDeviceInterfaceIndex, 1) = varResult
lngDeviceInterfaceIndex = lngDeviceInterfaceIndex + 1
Loop
Exit Do
Loop
lngErrorResult = Err.LastDllError
SetupDiDestroyDeviceInfoList HDEVINFO_USBClassDevs
SetupDiDestroyDeviceInfoList HDEVINFO_USBPrinterClassDevs
CreateListOfUSBPrinters = strHoldingArray
Exit Function
LocalErrorHandler:
If GlobalError("clsCommuncation:CreateListOfUSBPrinters", Err.Number, Err.Source, Err.Description) = 1 Then Resume Next
End Function
This mdUsbPrinters.bas is a complete and independent reimplementation of the ideas of the code above (mosting fiddling with setupapi.dll and cfgmgr32.dll APIs) but the whole gist can be compiled to a useful USB printers dumping utility out of the box.
The code is much more succint and successfully maps a printer name from Printers collection (or direct USB001 port name) to the so called Win32 Device Path like \\?\usb#vid_20d1&pid_0700#2024120147#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}. This path can be used with CreateFile API to "open" a file handle to the USB device/printer and much like COM serial ports to send and receive data in similar fashion.
Upon device path discovery the sample sends "~HS" status command for Zebra printers (and most other models) and reads/receives correct info using standard ReadFile API call.
Edit: USB printer's device path from GetUsbDevicePath function can also be used w/ VB's standard I/O statements just fine i.e. Open, Put, Get, Input, Close, etc. work.
cheers,
</wqw>
Last edited by wqweto; May 23rd, 2026 at 08:59 AM.