原始打印直接到 USB 打印机,绕过 Windows 后台处理程序
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6296139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Raw printing directly to a USB printer, bypassing Windows spooler
提问by Al Bennett
I'm experimenting with a Zebra TTP8200thermal printer. For my application I need to print plotter type traces continuously until the user hits a stop button. I've had a play with the ZPL language and I can successfully generate bitmap data and dump out my bitmap a line (or few lines) at a time by outputting the ZPL as raw data.
我正在试用Zebra TTP8200热敏打印机。对于我的应用程序,我需要连续打印绘图仪类型的轨迹,直到用户点击停止按钮。我玩过 ZPL 语言,我可以成功地生成位图数据,并通过将 ZPL 作为原始数据输出,一次将我的位图输出一行(或几行)。
I'm using some Microsoft demo codeto output the raw data to the printer and this works great, bar one issue: the spooler. It turns out that every time I output some data using the MS rawprn.exe code it is actually spooled as a print job and then transmitted to the printer. This takes up to 10 seconds to get through the spooler, obviously too slow. Disabling spooling in the driver doesn't help, it just means that the program hangs while the job is passed through the spooler and printing completes.
我正在使用一些Microsoft 演示代码将原始数据输出到打印机,这很好用,除了一个问题:假脱机程序。事实证明,每次我使用 MS rawprn.exe 代码输出一些数据时,它实际上都被假脱机作为打印作业,然后传输到打印机。这最多需要 10 秒才能通过假脱机程序,显然太慢了。在驱动程序中禁用假脱机无济于事,这只是意味着在作业通过假脱机程序和打印完成时程序挂起。
Is there a way to bypass the spooler and output data straight to this USB printer? My research so far hasn't turned up anything likely looking in the Windows API. Ideally, I'd like to be able to use the printer like it was a serial printer - open the port and shove data in.
有没有办法绕过假脱机程序并将数据直接输出到这台 USB 打印机?到目前为止,我的研究还没有发现任何可能在 Windows API 中寻找的东西。理想情况下,我希望能够像使用串行打印机一样使用打印机 - 打开端口并将数据推入。
Many thanks in advance for any hints!
非常感谢您提供任何提示!
采纳答案by Al Bennett
Thanks for the comments.
感谢您的评论。
After some more digging around, I found this interesting articleon using Windows printer functions provided by usbprint.sys. With a bit of hacking the sample code there seemed to work. I think I'll take this route.
经过更多的挖掘,我发现了这篇关于使用 usbprint.sys 提供的 Windows 打印机功能的有趣文章。通过对示例代码进行一些黑客攻击,似乎可以正常工作。我想我会走这条路。
There is the final code given in the article:
有文章中给出的最终代码:
/* Code to find the device path for a usbprint.sys controlled
* usb printer and print to it
*/
#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;
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);
}
Thanks again for the suggestions.
再次感谢您的建议。
回答by Teddy
If the USB printer is available as a COM port, you can just write to the COM port. Like this, from the DOS prompt:
如果 USB 打印机可用作 COM 端口,您只需写入 COM 端口即可。像这样,从 DOS 提示符:
dir > com1
The former example will output the results of the dir
command to the printer.
前一个示例将dir
命令的结果输出到打印机。
Or, here is another example:
或者,这是另一个例子:
copy file.txt com1
The former example will output the contents of file.txt
to the printer.
前面的例子将输出file.txt
到打印机的内容。
Outputting properly formatted ZPL data will be harder than just plain text. I have gotten this to work from Linux using Ruby (and Epson/ESC commands), however.
输出格式正确的 ZPL 数据将比纯文本更难。但是,我已经使用 Ruby(和 Epson/ESC 命令)在 Linux 上实现了它。
回答by Tres Finocchiaro
Is there a way to bypass the spooler and output data straight to this USB printer?
有没有办法绕过假脱机程序并将数据直接输出到这台 USB 打印机?
Yes, absolutely. It's built into most OSs, printing raw via USB is just a bit less obvious than Ethernet and COM/LPT. Note, many applications, such as notepad are incapable of printing raw, so your application needs to support this as well.
是的,一点没错。它内置于大多数操作系统中,通过 USB 打印原始数据不如以太网和 COM/LPT 明显。请注意,许多应用程序(例如记事本)无法打印原始数据,因此您的应用程序也需要支持此功能。
- Install the appropriate driver for your USB printer. Check the printer properties to see which USB port it is using. It may be USB001, etc.
- Using the Devices and Printers, add a second printer. Local Port, Select the port just created (i.e. USB001) Note: Some versions of Windows have a checkbox to autodetect, if it's there, uncheck this.
- Manufacturer: Generic, Printer: Generic / Text Only
- Use the driver that is currently installed
- Give the printer a name that differentiates it from the one already created, i.e. Zebra TTP8200 Raw.
- Do not share
- Do not print test page, Finish
- 为您的 USB 打印机安装适当的驱动程序。检查打印机属性以查看它使用的是哪个 USB 端口。它可能是 USB001 等。
- 使用设备和打印机,添加第二台打印机。本地端口,选择刚刚创建的端口(即 USB001) 注意:某些版本的 Windows 有一个自动检测的复选框,如果它在那里,取消选中这个。
- 制造商:通用,打印机:通用/纯文本
- 使用当前安装的驱动
- 为打印机指定一个名称,以区别于已创建的名称,即 Zebra TTP8200 Raw。
- 不分享
- 不打印测试页,完成
Now with your raw printing application, use the newly created printer.
现在使用您的原始打印应用程序,使用新创建的打印机。
P.S. These instructions are also available here, with screenshots, as part of a Java open source raw printing tutorial. The project provides tutorials for other platforms (Ubuntu, OS X) as well.
PS 这些说明也可在此处获得,并附有屏幕截图,作为 Java 开源原始打印教程的一部分。该项目还为其他平台(Ubuntu、OS X)提供教程。
http://qzindustries.com/TutorialRawWin
http://qzindustries.com/TutorialRawWin
-Tres
-特雷斯
回答by Cole W
The below class in C# is something I've adapted from a Microsoft Knowledgebase article. There are methods in this class to send the print job as a string
and byte[]
. Please note there are some references to log4net in there that can be removed/replaced with the logging framework of your choice. :
下面的 C# 类是我改编自 Microsoft 知识库文章的内容。此类中有一些方法可以将打印作业作为string
和发送byte[]
。请注意,那里有一些对 log4net 的引用,可以使用您选择的日志记录框架删除/替换。:
/// <summary>
/// Class used to aid in sending raw printer data (PS, PRN, etc) directly to the printer.
/// This class was taken from http://support.microsoft.com/kb/322091
/// </summary>
public class PrintQueueUtility
{
private static ILog log = LogManager.GetLogger(typeof(PrintQueueUtility));
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
/// <summary>Method which sends a <see langword="byte"/> array to a printer queue with a specific document name.</summary>
/// <param name="bytes">Byte array to send to the printer.</param>
/// <param name="printerName">Name of the printer to send the <paramref name="bytes"/> to.</param>
/// <param name="documentName">The document Name.</param>
/// <returns><see cref="bool"/> indicating whether or not the method succeeded at adding something to the print queue.</returns>
public static bool SendBytesToPrinter(byte[] bytes, string printerName, string documentName)
{
bool success;
// Allocate some unmanaged memory for those bytes into an unmanaged pointer.
IntPtr unmanagedBytes = Marshal.AllocCoTaskMem(bytes.Length);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, unmanagedBytes, bytes.Length);
// Send the unmanaged bytes to the printer.
success = SendUnmanagedBytesToPrinter(unmanagedBytes, printerName, documentName, bytes.Length);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(unmanagedBytes);
return success;
}
/// <summary>Method which sends a string to the printer queue with a specific document name.</summary>
/// <param name="data"><see cref="String"/> data to send to the printer.</param>
/// <param name="printerName">Name of the printer to send the data to.</param>
/// <param name="documentName">Name of the document in the printer queue.</param>
/// <returns><see cref="bool"/> indicating whether or not the method succeeded at adding something to the print queue.</returns>
public static bool SendStringToPrinter(string data, string printerName, string documentName)
{
bool success;
IntPtr unmanagedBytes;
// How many characters are in the string?
var characterCount = data.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
unmanagedBytes = Marshal.StringToCoTaskMemAnsi(data);
// Send the converted ANSI string to the printer.
success = SendUnmanagedBytesToPrinter(unmanagedBytes, printerName, documentName, characterCount);
Marshal.FreeCoTaskMem(unmanagedBytes);
return success;
}
private static bool SendUnmanagedBytesToPrinter(IntPtr unmanagedBytes, string printerName, string documentName, int count)
{
int error;
int written;
IntPtr printer;
var di = new DOCINFOA();
var success = false;
di.pDocName = documentName;
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(printerName.Normalize(), out printer, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(printer, 1, di))
{
// Start a page.
if (StartPagePrinter(printer))
{
// Write the bytes.
success = WritePrinter(printer, unmanagedBytes, count, out written);
EndPagePrinter(printer);
}
EndDocPrinter(printer);
}
ClosePrinter(printer);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (!success)
{
error = Marshal.GetLastWin32Error();
log.ErrorFormat("Sending bytes to printer {0} failed. Last Win32 error = {1}", printerName, error);
}
return success;
}
// Structure and API declarations:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}