Zebra TTP82 サーマルプリンターで実験しています。私のアプリケーションでは、ユーザーが停止ボタンを押すまで、プロッタタイプのトレースを継続的に印刷する必要があります。 ZPL言語を試してみましたが、ZPLを生データとして出力することにより、ビットマップデータを正常に生成し、一度に1行(または数行)ずつビットマップをダンプできます。
私はいくつかの Microsoftデモコード を使用して生データをプリンターに出力していますが、これは問題なく機能します。 MS rawprn.exeコードを使用してデータを出力するたびに、実際には印刷ジョブとしてスプールされ、プリンターに送信されることがわかりました。これはスプーラーを通過するのに最大10秒かかりますが、明らかに遅すぎます。ドライバーでスプーリングを無効にしても効果はありません。ジョブがスプーラーを通過して印刷が完了するまでプログラムがハングするだけです。
スプーラーをバイパスし、このUSBプリンターにデータを直接出力する方法はありますか?私のこれまでの調査では、Windows APIで見られる可能性のあるものは何も見つかりませんでした。理想的には、シリアルプリンターのようにプリンターを使用できるようにしたい-ポートを開いてデータを押し込む。
ヒントを事前に感謝します!
コメントをありがとう。
さらに調べてみたところ、usbprint.sysが提供するWindowsプリンター機能の使用について この興味深い記事 が見つかりました。サンプルコードを少しハッキングすると、動作するように見えました。私はこのルートを取ると思います。
記事に記載されている最終的なコードがあります:
/* 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);
}
提案をありがとう。
USBプリンターがCOMポートとして使用できる場合は、COMポートに書き込むことができます。このように、DOSプロンプトから:
dir > com1
前の例では、dir
コマンドの結果をプリンターに出力します。
または、ここに別の例があります:
copy file.txt com1
前の例では、file.txt
の内容をプリンターに出力します。
適切にフォーマットされたZPLデータの出力は、プレーンテキストよりも困難になります。ただし、Ruby(およびEpson/ESCコマンド))を使用してLinuxで動作するようになりました。
スプーラーをバイパスし、このUSBプリンターにデータを直接出力する方法はありますか?
そのとおり。ほとんどのOSに組み込まれており、USBを介してrawで印刷することは、イーサネットやCOM/LPTに比べて少しわかりにくいだけです。メモ帳などの多くのアプリケーションはrawを印刷できないため、アプリケーションもこれをサポートする必要があります。
生の印刷アプリケーションで、新しく作成したプリンターを使用します。
追伸これらの手順は、Javaオープンソースのraw印刷チュートリアルの一部として、スクリーンショットとともにここでも利用できます。このプロジェクトは、他のプラットフォーム(Ubuntu、OS X)のチュートリアルも提供します。
http://qzindustries.com/TutorialRawWin
-トレス
以下の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;
}