So its Thursday morning at work and I decide that since the Mac OS X version of our software can print directly to USB printers bypassing the operating system, its about time we added that functionality to the Linux and Windows versions.
Start on the Linux version
A quick scan through the /usr/src/linux/drivers/usb/devio.c source file reveals all about the usbdevfs file system and the ioctls required. Browse through the USB spec, a few calls to open() and ioctl(), and urb here, an urb there and I’m all done. Piece of cake.
Start on the Windows version
Shudder… Past experiences with Windows and anything to do with low level access to devices has taught me that I am in for the long haul. Each time I try to do something like this it always take 3 – 4 days or excruciating effort and unbearable pain. Oh well, I strap myself to my trusty Aeron chair and away I go.
Now from the MSDN documentation I manage to surmise that as soon as a USB printer is plugged in, the “plug and pray” subsystem loads up the usbprint.sys device driver and passes control over to it. In turn usbprint.sys creates a device node, initialises it and then sets up a device interface so user space programs can access it using the usual device semantics.
Great I think. I’ll just call CreateFile() to open the device and then use WriteFile() to send data to it. Couldn’t be easier. “clickety clack”, the code just flies from my fingers. CreateFile(name….). hmm.. what’s the name of the device. If it was Linux it would be /dev/usblp0 or something like that. Now what is it for Windows. A scan through the MSDN documentation reveals nothing. A search of google produces lots of hits to people asking the same question, but no answers. “Oh, oh this is going to be bad I think”. If there are no answers out there it means it is a closely guarded Microsoft secret and I am going to have to do some deep digging myself.
A more methodical scan of the MSDN docs and Google at least hints to what calls I need to make. In essence you do the following.
- Call SetupDiGetClassDevs(interface_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) to get a list of devices.
- Call SetupDiEnumDeviceInterfaces() to get a list of available device interfaces.
- Call SetupDiGetDeviceInterfaceDetail() to fetch information for the actual device interface we are going to use.
- Use the DevicePath member of the PSP_DEVICE_INTERFACE_DETAIL_DATA structure returned by the SetupDiGetDeviceInterfaceDetail() call as the devicename/filename in the CreateFile() call.
- Call WriteFile() repeatedly to send data to the device.
- Call CloseHandle() to close the device
So in order to be able to find the name of the device, I need to find the GUID of the interface that usbprint.sys creates when it adds the devicenode to the system. Search… search… search… nothing. Search some more, nothing. Look through the Windows SDK and DDK include files with grep. Still nothing. Oh well up comes trusty old regedit, everything windows does is in the registry, and I start to wade through the myriad of nodes and entries for the GUID. After a few hours of despair I find it. The magic number is 0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2. You’d think Microsoft would have included it in one of the .h files (devguid.h or usbprint.h) just like they did the interface GUID for keyboards, mice, hid usb devices, usbcam devices etc. etc. etc.
Oh well at least I can now call open CreateFile and Write to the printer. So here we go:
HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);
The WriteFile call fails and GetLastError() returns 1, ie. the operation is not supported.
More searching, reading, nothing turns up. It keeps failing. I remember that the DDK includes source code for the local portmonitor so I take a look to see how it calls CreateFile for the local port interface. I modify mine to be the same and try again.
HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);
Success, the printer starts outputting my job. What is different above? Well replacing OPEN_EXISTING with OPEN_ALWAYS seems to have forced the driver to start the device so that the WriteFile interface is available. Previously OPEN_EXISTING would not try and create a file so the device would not start. At least that is what I think.
4 days later and I am all done.
So here is a quick snippet of some of my code to help others out there that might have the same problem. You will need both the windows SDK and DDK installed and configured correctly for this code to compile. You will also need to link your executable with the setupapi library.
Below I have included the code as both an attachment and inline. It is not meant to compile out of the box. It is only there as a guide.
usbprint.c
/* Code to find the device path for a usbprint.sys controlled
* usb printer and print to it
*/
#include <devioctl.h>
#include <usb.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <usbprint.h>
#include <setupapi.h>
#include <devguid.h>
#include <wdmguid.h>
/* This define is required so that the GUID_DEVINTERFACE_USBPRINT variable is
* declared an initialised as a static locally, since windows does not include it
* in any of its libraries
*/
#define SS_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
static const GUID name \
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
SS_DEFINE_GUID(GUID_DEVINTERFACE_USBPRINT, 0x28d78fad, 0x5a12, 0x11D1, 0xae,
0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
void SomeFunctionToWriteToUSB()
{
HDEVINFO devs;
DWORD devcount;
SP_DEVINFO_DATA devinfo;
SP_DEVICE_INTERFACE_DATA devinterface;
DWORD size;
GUID intfce;
PSP_DEVICE_INTERFACE_DETAIL_DATA interface_detail;
HANDLE usbHandle;
DWORD dataType;
intfce = GUID_DEVINTERFACE_USBPRINT;
devs = SetupDiGetClassDevs(&intfce, 0, 0, DIGCF_PRESENT |
DIGCF_DEVICEINTERFACE);
if (devs == INVALID_HANDLE_VALUE) {
return;
}
devcount = 0;
devinterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
while (SetupDiEnumDeviceInterfaces(devs, 0, &intfce, devcount, &devinterface)) {
/* The following buffers would normally be malloced to he correct size
* but here we just declare them as large stack variables
* to make the code more readable
*/
char driverkey[2048];
char interfacename[2048];
char location[2048];
char description[2048];
/* If this is not the device we want, we would normally continue onto the
* next one or so something like
* if (!required_device) continue; would be added here
*/
devcount++;
size = 0;
/* See how large a buffer we require for the device interface details */
SetupDiGetDeviceInterfaceDetail(devs, &devinterface, 0, 0, &size, 0);
devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
interface_detail = calloc(1, size);
if (interface_detail) {
interface_detail->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetail(devs, &devinterface, interface_detail,
size, 0, &devinfo)) {
free(interface_detail);
SetupDiDestroyDeviceInfoList(devs);
return;
}
/* Make a copy of the device path for later use */
strcpy(interfacename, interface_detail->DevicePath);
free(interface_detail);
/* And now fetch some useful registry entries */
size = sizeof(driverkey);
driverkey[0] = 0;
if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo, SPDRP_DRIVER, &dataType,
(LPBYTE)driverkey, size, 0)) {
SetupDiDestroyDeviceInfoList(devs);
return;
}
size = sizeof(location);
location[0] = 0;
if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo,
SPDRP_LOCATION_INFORMATION, &dataType,
(LPBYTE)location, size, 0)) {
SetupDiDestroyDeviceInfoList(devs);
return;
}
usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (usbHandle != INVALID_HANDLE_VALUE) {
/* Now perform all the writing to the device ie.
* while (some condition) WriteFile(usbHandle, buf, size, &bytes_written);
*/
CloseHandle(usbHandle);
}
}
}
SetupDiDestroyDeviceInfoList(devs);
}
Technorati Tags: usbprint.sys