Необработанная печать напрямую на USB-принтер, минуя спулер Windows - PullRequest
14 голосов
/ 09 июня 2011

Я экспериментирую с термопринтером Zebra TTP8200 .Для моего приложения мне нужно непрерывно печатать трассировки типа плоттера, пока пользователь не нажмет кнопку остановки.У меня была игра с языком ZPL, и я могу успешно генерировать растровые данные и выгружать из моего растрового изображения строку (или несколько строк) за один раз, выводя ZPL в виде необработанных данных.

Я используюкакой-то демонстрационный код Microsoft для вывода необработанных данных на принтер, и это прекрасно работает, за исключением одной проблемы: спулера.Получается, что каждый раз, когда я выводлю некоторые данные с использованием кода MS rawprn.exe, они на самом деле помещаются в очередь для печати и затем передаются на принтер.Это займет до 10 секунд, чтобы пройти через спулер, очевидно, слишком медленно.Отключение буферизации в драйвере не помогает, это просто означает, что программа зависает, когда задание проходит через спулер и печать завершается.

Есть ли способ обойти спулер и выводить данные прямо на этот USBпринтер?Мои исследования до сих пор не обнаружили ничего похожего на Windows API.В идеале я хотел бы иметь возможность использовать принтер, как если бы он был последовательным принтером - откройте порт и вставьте данные.

Большое спасибо заранее за любые подсказки!

Ответы [ 4 ]

2 голосов
/ 14 января 2014

Есть ли способ обойти спулер и выводить данные прямо на этот USB-принтер?

Да, абсолютно.Он встроен в большинство операционных систем, поэтому печать через USB не так очевидна, как в Ethernet и COM / LPT.Обратите внимание, что многие приложения, такие как блокнот, не способны печатать в сыром виде, поэтому ваше приложение также должно это поддерживать.

  1. Установите соответствующий драйвер для вашего USB-принтера.Проверьте свойства принтера, чтобы увидеть, какой порт USB он использует.Это может быть USB001 и т. Д.
  2. Используя устройства и принтеры, добавьте второй принтер.Локальный порт, выберите только что созданный порт (т. Е. USB001). Примечание. В некоторых версиях Windows установлен флажок для автоматического определения, если он есть, снимите этот флажок.
  3. Производитель: универсальный, принтер: универсальный / только текст
  4. Используйте драйвер, который в данный момент установлен
  5. Дайте принтеру имя, которое отличает его от уже созданного, то есть Zebra TTP8200 Raw.
  6. Не делитесь
  7. Не печатать тестовую страницу, Готово

Теперь с вашим приложением для печати необработанных данных, используйте только что созданный принтер.

PS Эти инструкции также доступны здесь, со скриншотами, как частьУчебное пособие по печати с открытым исходным кодом на Java.Проект также предоставляет учебные пособия для других платформ (Ubuntu, OS X).

http://qzindustries.com/TutorialRawWin

-Tres

2 голосов
/ 09 июня 2011

Если USB-принтер доступен в качестве COM-порта, вы можете просто записать в COM-порт.Например, из командной строки DOS:

dir > com1

В первом примере результаты команды dir выводятся на принтер.

Или вот еще один пример:

copy file.txt com1

В предыдущем примере на принтер выводится содержимое file.txt.

Вывести правильно отформатированные данные ZPL будет сложнее, чем просто текст.Однако я получил это для работы с Linux, используя Ruby (и команды Epson / ESC).

0 голосов
/ 20 июня 2011

Спасибо за комментарии.

После еще нескольких копаний я нашел эту интересную статью об использовании функций принтера Windows, предоставляемых usbprint.sys. Немного взломав пример кода, похоже, сработало. Я думаю, что я пойду по этому маршруту.

В статье указан окончательный код:

/* 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);
}

Еще раз спасибо за предложения.

0 голосов
/ 15 июня 2011

Нижеприведенный класс в 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;
    }
...