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.
/* 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
Nice job. I went through the same process before finding this recipe.
Microsoft doesn’t want users to “play” with usbprint.sys. This is why the documentation is so hard to find. In the Microsoft Printer Architecture, usbmon.dll is the only one allowed to “talk” to usbprint.sys. You have to play safe because if your printer has a driver installer, the printer spooler via usbmon.dll/usbprint.sys could interact with your application which also uses usbprint.sys. The safest way (I think) if you want to communicate with a USB printer is to make sure there is no driver installer for this printer. If so, usbmon.dll will not open any handles to this printer.
Great Logic !
I have gone through the same problem but Jan Axelson’s “USB Complete” helped me a lot.Now I am able to get handle to usbprint.sys and also I can write to Printer(WriteFile returns non zero).
But big problem is printer is not printing my job I am using HP Deskjet 3745 printer.I don’t know what is happening.This is strange situation, WriteFile is successful but printer is not prining the job.
Since the printer is not printing your job, my first guess would be that you are sending it data that is not in the required format so the printer is just ignoring it. When you use usbprint.sys you have to send data that is in the format required by the printer. Usbprint.sys totally bypasses the printer driver, it only provides a transport for data between the computer and the printer, your program will have to do the job of the printer driver.
Good blog Peter. I have some problems compiling your code in vc++ 6.0. The first to line after include section is getting me a missing semicolon error. I have replace this two line by
static const GUID GUID_DEVINTERFACE_USBPRINT = { 0x28d78fad, 0x5a12, 0x11D1, {0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2} };
but SetupDiEnumDeviceInterfaces don´t retunr any interface.
Your changes look fine to me. In order for the call to return some interfaces, you will need to have a usb printer plugged in. Check under computer manangement->system information->usb to see if your printer has an interface instantiated for it.
You can always search through your registry, to see if an interface with that GUID exists on your machine.
Hi Peter,
Great article! I am new in this driver world want help from u.
I m using Hp printer connected through USB , it has 3 end points.
What my thinking is that To get handle of a particular end point I have to concatenate the device path with the pipe name ( device path I can find by using the method u have described)
Correct me if I am wrong..
In case I am writing to a particular end point & if there is some error ( eg out of paper, paper jam) then how to know the type of error.
That would be the theory, but you would have to try it. From my experience with Windows, I am a unix guy, anything that seems straightforward never turns out to be. My guess would be that usbprint.sys has grabbed hold of the printer itself and the only way to talk to it is using the device interface as outlined in the post. If you do try it and it works please post a comment here for others to have access to.
Is it possible to set up port mode, control output lines’ states (nSTROBE, nAUTOFEED, etc.), and read printer status (PE, SELECT, nERROR, …) via usbprint? Does it allow such low-level controlling of the port, or it allows only reading/writing data in SPP/ECP mode?
I want to use USB-to-LPT adapter in EPP mode with my own parallel port device that supports EPP.
USBPrint allows you to send and receive data from the device using the standard CreateFile, ReadFile and WriteFile interface.
If you want such low level control you will have to write your own device driver or use some other method to send commands to the control pipe of the device. The manufacturer of the device should already be providing tools for doing this.
Hi, thanks for your article. I used this info and now I have a tiny test program in VB. I can send and receive PJL from Lexmark printers.
But, I was unable to do the same with the parallel port. If you have any idea, I will appreciate it. Now, if you think this VB prog. can be useful, let me know and I send it to you
Hi all, I’m new here. Great article.
I have the same ‘problem’ as Konstantin. I have an LC-Display which runs well on an old standard parallel port. Now I would like to use it with a USB-to-Parallel cable. the cable uses a prolific PL-2305 chip and Prolific doesn’t provide a driver for XP. I have to control the strope, init, autofeed and selectIn and read back data from the display. Does anyone know, how to realize this? And where can I get a Windows SDK and DDK for VC++6? Microsoft ships only SDK and DDK for .NET, VC6 isn’t supported any more since, ummm, 02/2003 (?).
In order to control the strobe, init autofeed etc. you will have to somehow send commands to the control pipe of the device. The commands that you send through the control pipe are all very device specific so you would require some information from the manufacturer for that.
Usbprint.sys only allows you to read/write to the device as if it were a file using the CreateFile / ReadFile / WriteFile interface. In order to access the control pipe you would have to write a kernel driver for your specific device.
If you were accessing the device on Linux or Mac OS X you could simply read and write to the control pipe from user mode, but unfortunately Windows does not offer such functionality.
I have worried about that… 🙁
Hi,
i need help, i have made some project to output signal through parallel,
i had some difficaulty to output directly because microsoft disable it in XP.
so i tried to write small driver send signal to parallel port. and it was fine.
now i need to run this project in my labtop which it has not parallel port, i tried to buy Usb-parallel which it used for printer. but really my code isn’t work because it will send to address 0378 .
i saw you artical and i enjoy it, but really i need some more information. and help from you, to understand how i can solve my problem.
thanx alot for your effort
Afshar
Sorry this post is provided as is. It is somethinng I discovered after days of intense work and I just wanted to share it with the world.
If you have a usb to parallel box you can use the code in this article to send data to it, but if you wish to do more than that, you will have to get infromation about the chipset used in the adaptor and send commands to it via the control pipe with a kernel driver. This has been covered previously in a comment above and is beyond what this code achieves.
thanx alot for your response.
really i want to compile this code but i don’t know how to compile it, can you provide me with complete source code?
and small explanation for compiling this code. coz i think that usb.h is only provided in DDk.
thanx again.
Afshar
if you do not know how to compile this code you shouldn’t be trying to do what you are attempting.
the usb include files are part of the ddk which can be found on any miscrosoft msdn distribution. Search through the MSDN documentation to find what you need
I know to compile driver with ddk but it first time i will compile normal program but it use ddk header files. what i need, is to tell me how to compile non driver with ddk.
do i need to change in SOURCES FILE (TARGETTYPE) to anther thing?
really i don’t need more detail only few help
thanx
I do not use the Microsoft Developer studio, but rather I always just handwrite my own Makefiles.
You probably need to add the ddk include and lib directories to your include and library paths on the command line.
I am really surprised that there are programmers out there that cannot perform something as simple as compiling and linking a program. The IDEs that people use these days have a lot to answer for.
I am on you own I am afraid, I am a rather busy man and do not have the time to perform handholding.
dood. thanks. this is exactly what I needed to do before I found out the usb printing support on my XP computer was broken and I spent 3 days trying to figure it out and finally just wiped and reloaded windows. You have saved me at least 3 days of figuring all that stuff out!
Nice job. I’m printing now. I’m currently printing to the first device that gets enumerated. Do you know how I would determine what is connected to the port (in case there is more than 1)?
Thanks
I normally first go through the registry and get a list of available usb printers including human readable strings. I then use the strings found there to match the required device when searching for the interface to use in CreateFile.
Thanks for your help. One last question if you have time. Do you know if the usbprint supports bi-directional communication? When I do ReadFile() I get access denied. The pjlmon sample code supports bidi but I don’t know if it requires a custom usb driver. I apoligize if this is too much. Just ignore if it is.
I’m afraid I have no idea if it does, but my guess would be that yes it does, but you would need to use the DeviceControl interface with the same ioctlls as the parallel port to achieve this. The MSDN documentation mentions this somewhere. You should also check that you had GENERIC_READ as part of the flags in the CreateFile call. The one I have in my sample code only has GENERIC_WRITE as I never read from the device.
Thank you for your article!
hi, Peter!
I use your code very well,but now i find a bug:when the usb is IEEE 1384.4(DOT4), SetupDiGetDeviceRegistryProperty will return FALSE! Do you know why? and how to resolve it?
Thanks!
My code only works with devices that are handled by usbprint. If your device is not then it will not be found.
Another thing that might be causing trouble is that your device does not actually have the property you are trying to get. Look through the registry using regedit to see if it does.
Hi Peter!
Thanks a lot for sharing this piece of information. I’m using it to talk to my PIC18F2455 (great little chip btw, lots of fun for AU$10). I wrote printer class firmware for it and I actually managed to get 800Kb/s in both directions. I also tried IOCTL_USBPRINT_GET_LPT_STATUS and IOCTL_USBPRINT_SOFT_RESET codes and they come through just fine, giving me an out-of-band channel to my device.
I have one problem to do with ReadFile() buffer size parameter.
In my setup the PIC is constantly sending packets of the maximum size
as defined in endpoint descriptor (it is always ready to send a packet when PC asks for one to be exact).
First, there seems to be a limit around 3500-3800 bytes on the size of the buffer. If I try to read more in one go, ReadFile() hangs, USB port goes to IDLE state, after killing the process this particular USB port is unusable until reboot.
Second, reading only seems to work if ReadFile() buffer size is an integer multiple of the endpoint buffer size. If not, ReadFile() returns with error 0x1F (“A device attached to the system is not functioning.”).
Howevr it does not happen it the device stops transmitting (i.e. responds with NAK to an IN token) before the buffer is exhausted.
If looks as if ReadFile() keeps asking the device for more packets as long as the device keeps sending then and there is at least one free byte left in the buffer. If the next packet it receives does not fit wholly into what’s left of the buffer it fails with 0x1F. Weird. How and why am I supposed to know about the endpoint buffer size?
Oh, yes, btw, while playing with this stuff I’ve accidentally created a new type of USB devce: “USB instant bluescreen dongle”(c). Just plug it in and Windows XP bluescreens. How? Just implement printer class firmware and politely refuse to provide your 1284 device id. Works 100%. Took me nearly a week’s worth of evenings to figure that out.
I’ve also come through this lately. Now, using Prolific PL2305 interface, I can do WriteFile and get status of PaperOut (0x20), Select (0x10) and Error (0x08) signals via IOCTL_USBPRINT_GET_LPT_STATUS. I found that Busy and Ack signals are processed internally (data are sent according to their state), but they don’t seem to be readable by a program. As for other control and status signals, I also didn’t find a way to read or write them. The PL2305 datasheet says that other bits (except the mentioned three) of the status register are zeros (confirming the above assumptions).
Unlike Dmitri, I can’t use IOCTL_USBPRINT_SOFT_RESET because it returns with error 1 (function not supported). It looks strange: the datasheet says that such a function exists. Maybe another IOCTL is needed? Note that this signal seems to be the only way to clear buffers, or I’m not right?
Correction to the previous message: Soft Reset is not supported under Win2000 only. It works well in WinXP. I don’t know about WinME, but I think it works there too. I still wonder if there is another way to reset device (or at least to clear buffers) under Win2000…
I’m still trying to figure out how in the hell to compile this thing…
Never mind! The code works great now. The way I got it to compile was by removing all references to DDK headers and compiling it totally using the Visual C++ compiler. No problems yet and I can open a handle and read/write fine. Great job figuring all this stuff out!!
Hi! I am totally new in driver development field. So, please bear with me if my questions seem stupid.
1. Can the code function perfectly if I use usb-to-parallel to connect to my printer?
2. I have one parallel device (not a printer), which uses parallel port to work. Now, I want to use usb-to-parallel cable. Can the code used to read/write to my device?
Thanks.
If the USB to parallel device is of class USBPrinter and as such is handled by usbprint.sys then the code here will work with it correctly. You can use the computer management tool to check whether your device is being handled by usbprint.sys.
I went to DeviceManager and found that, when I plugged-in my usb-to-parallel cable, under ‘Universal Serial Bus controllers’ appeared one ‘USB Printing Support’. I went to its property and the driver files was shown as ‘usbprint.sys’. So, I guess my usb-to-parallel cable is of class USBPrinter.
I am going to try if the piece of code can be used. Thanks!
By the way, how you found the GUID from the registry?
The GUID is the one for usbprint.sys and that is all handled by the code in this post. Once you’ve enumerated all the devices, you will have to setup your own method for choosing which one you wish to open. I normally pop up a list and let the user choose.
I am using Visual Studio 6.0 with no DDK. I changed GUID_DEVINTERFACE_USBPRINT to GUID_DEVCLASS_PRINTER because I have linking error with GUID_DEVINTERFACE_USBPRINT.
Without connecting the cable to my device, I run the code in debug mode. I found that it return 0 in SetupDiEnumDeviceInterfaces(). I used GetLastError() to get the error code, and it is 259 (decimal).
Why is it so? Must I need DDK to make it work?
I do have SDK.
These lines
#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);
are used to define GUID_DEVINTERFACE_USBPRINT so you should not be getting a link error as long as you include them in your source code. It has to be in the same file as were you call it from because it is defined as static. If you want it to be visible from other files then change it fro static to something else. Up to you.
Anyway, I am not here to teach people how to program under Windows. I am just providing information that is not available anywhere else.
As far as I’ve tried, the default Write buffer size of usbprint.sys is 4096 bytes.
I wonder if it is possible to extend the size.
I am not sure if you can change it as when writing to a USB device the maximum write size is controlled by the pipe characterstics as published by the USB device itself. Most devices advertise a bulk pipe which is probably what usbprint.sys uses to write to the device. If you use a usbviewer and check out the characteristics of the bulk pipe you should be able to see the maximum allowable write size for your device and then you can possibly use DeviceIOControl to change it.
Did Konstantin or Marc solve there problem of controlling the Strobe line of the PL2305. I apologize if I ma posting it here but saw some reference of the Prolific PL2305 so wanted to see if there is something already available.
Thanks
Has anyone found a way to link the USBPrint device back to the Windows printer? Basically what I want to do is take the Windows print queue offline before I take over the USBPrint device and start sending PJL commands to the printer – this is to stop anything interferring with my application while it uses the printer. I can take a printer offline OK, I just want a slightly more clever way of doing it than finding out which printer has PRINTER_ATTRIBUTE_LOCAL set and taking that printer offline. In my situation I should never have more than one locally attached printer, but better to do it properly just in case.
Hi Peter this is brilliant i would just like to know with the createfile() and then deviceiocontrol can i then use a ioctl to do bulk transfers back and forth from my device i have a hp 1300 thanks
No idea. After resolving this problem I have not had to do any more windows programming as we have our own operating system independent layer that we use in our products. Just check out the windows developer documentation or just try it and see what happens. I would imagine that the call to deviceiocontrol will not work as usbprint.sys is handling the communication with the device and probably does not support that ioctl.
HI, Thanx for this information.
But what about using usbmon.dll ie, Port monitor for usbprintsys. Usbmon maps the spooler or languagemonitor call to kernel mode driver usbprint.sys. I would like to know about the operations of usbmon.dll and the usb specific operations in OpenPort, StartDocPort, EndDocPort, etc.
Thanx in advance
Well now you’re pushing your luck. How about you work it out and post it on the Internet for the rest of us to use. It would be really useful.
Hi, I spent a lot of time trying to access the printer in this way (opening the devicepath) It worked ok in winxp/usb2.0, but I found a lot of problems with old computers (win2k/usb1.1) about the block size required in ReadFile and WriteFile, as the system doesn’t provide any buffering. Now I’m trying to use the usbmon.dll but I didn’t succeeded, it always closes my app when calling either StartDocPort or WritePort. Anyone have an idea?
Thanks in advance
HI, Thanx for your helpful artical.
I have a question to ask you.How can I set the Timeout of the printer USB port for reading and writing operation? I have tried set it by calling
SetCommTimeouts(HANDLE hflie,
PCOMMTIMEOUTS lpCommTimeouts
)
Unluckly, it is a invalid function for the USB device.
Can you give me some advices about how to get and set the USB communication status by the APIs of SDK or DDK.
Waiting for your help!
Thank you very much!
Just use overlapped IO instead of non overlapped and set the timeout in the call to WaitForSingleObject. This is the same method used for any I/O on Windows.