使用 c++ 調用 windows 打印 api 進行打印

前言

在近期開發的收銀臺項目中,須要使用打印機進行小票打印,打印流程的時序圖以下所示:css

輸入圖片說明
屏幕截圖.png

在客戶的使用過程當中,遇到一個問題,若是機器安裝了打印機驅動,那麼調用廠商提供的 sdk 進行打印的話,會致使出現小票只打印一半的狀況,對此,須要繞過廠商 sdk 使用系統的打印纔可以解決這一問題。html

在 web 端打印中,須要調用瀏覽器打印 api 進行網頁打印。這意味着,以前後端編寫的esc/pos沒法複用到,同時,前端還得花費精力來編寫 html 以及css 來完成打印內容的排版,這無疑增長了複雜度以及工做量。正打算開始時,獲得高人指點。前端

可使用 windows api 進行打印node

具體參見這篇文檔ios

因而開始這方面的研究,功夫不負有心人,使用 windows api 完成了系統的打印,因而編寫這篇文章記錄踩過的坑。c++

首先看看如何進行打印:web

BOOL RawDataToPrinter(LPSTR szPrinterName, LPBYTE lpData, DWORD dwCount) {
    HANDLE     hPrinter;
    DOC_INFO_1 DocInfo;
    DWORD      dwJob;
    DWORD      dwBytesWritten;

    // Need a handle to the printer.
    if (!OpenPrinter(szPrinterName, &hPrinter, NULL)) {
        int y = GetLastError();
        cout << "openFail" << y << endl;
        return FALSE;
    }

    // Fill in the structure with info about this "document."

    DocInfo.pDocName = LPSTR("My Document\0");
    DocInfo.pOutputFile = NULL;
    DocInfo.pDatatype = NULL; // LPWSTR("RAW\0");
    // Inform the spooler the document is beginning.
    if ((dwJob = StartDocPrinter(hPrinter, 1, (LPBYTE)&DocInfo)) == 0)
    {
        int x = GetLastError();
        cout << "StartDocPrinter Fail" << x << endl;
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Start a page.
    if (!StartPagePrinter(hPrinter))
    {
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Send the data to the printer.
    if (!WritePrinter(hPrinter, lpData, dwCount, &dwBytesWritten))
    {
        EndPagePrinter(hPrinter);
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // End the page.
    if (!EndPagePrinter(hPrinter))
    {
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Inform the spooler that the document is ending.
    if (!EndDocPrinter(hPrinter))
    {
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Tidy up the printer handle.
    ClosePrinter(hPrinter);
    // Check to see if correct number of bytes were written.
    if (dwBytesWritten != dwCount)
        return FALSE;
    return TRUE;
}
複製代碼

文檔中提到,打開打印機時"OpenPrinter"能夠傳入 null 以使用本地打印服務,由於不知道打印機名稱,因而就傳入了 null,結果在 StartDocPrinter 時一直提示失敗,後來瞭解到使用 GetLastError 能夠查看 error code,獲得錯誤碼後一對照,發現是 handle 是無效的,也就意味這 OpenPrinter 這一步驟沒有打開須要的打印機。因而嘗試使用 設備與打印機中的打印機名稱,還真就連上了,成功調用打印服務。windows

但客戶電腦上的打印機名稱是不固定的,不能使用固定打印機名稱,因此得拿到已經鏈接了的打印機列表,因而搜索到了 EnumPrinters 這一api,具體用法以下:後端

void getPrinterList() {
    PRINTER_INFO_2* printerList;
    unsigned char size;
    unsigned long pcbNeeded;
    unsigned long pcReturned;

    EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 2, NULL, 0, &pcbNeeded, &pcReturned);

    if ((printerList = (PRINTER_INFO_2*)malloc(pcbNeeded)) == 0) {
        return;
    }

    if (!EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 2, (LPBYTE)printerList, pcbNeeded, &pcbNeeded, &pcReturned)) {
        free(printerList);
        return;
    }

    for (int i = 0; i < (int)pcReturned; i++) {

        string printName(printerList[i].pPrinterName);
        if (printerList[i].Attributes & PRINTER_ATTRIBUTE_NETWORK) {
            cout << "網絡打印機" << printName << endl;
        }
        else {
            cout << "本地打印機" << printName << endl;
        }
    }

    cout << "number " << pcReturned << endl;

}
複製代碼

經過這一方式,的確獲取到了系統中可用的打印機,但是拿到可用的打印機後仍是有一個問題:「如何知道哪個是小票打印機」?api

爲此又進行了搜索,又找到了一個 api GetDefaultPrinter,用法以下:

string getDefaultPrinterName() {
    DWORD size = 0;
    GetDefaultPrinter(NULL, &size);

    if (size) {
        TCHAR* buffer = new TCHAR[size];
        GetDefaultPrinter(buffer, &size);
        string printerName(buffer);
        return printerName;
    }
    else {
        return "";
    }
}
複製代碼

經過此方法獲取到系統默認打印機,客戶只須要設置默認的打印機爲小票打印機就完美解決問題了。

如下是完整代碼:

#include <iostream>
#include <windows.h>
#include "node.h"
#include "base64.h"

using namespace std;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
using v8::Integer;
using v8::Int8Array;

BOOL RawDataToPrinter(LPSTR szPrinterName, LPBYTE lpData, DWORD dwCount);
string getDefaultPrinterName();

void localPrintRawData(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    Local<v8::Context> context = isolate->GetCurrentContext();
    v8::String::Utf8Value portString(isolate, args[0]);
    std::string base64Str(*portString);

    vector<BYTE> bytes = base64_decode(base64Str);
    char* buffer = new char[bytes.size()];
    copy(bytes.begin(), bytes.end(), buffer);
    string printerName = getDefaultPrinterName();
    if (printerName.size() > 0) {
        printerName += "\0";
        wstring ws(printerName.begin(), printerName.end());
        RawDataToPrinter(const_cast<char*>(printerName.c_str()), &bytes[0], bytes.size());
    }
    else {
        cout << "no printer" << endl;
    }
}

BOOL RawDataToPrinter(LPSTR szPrinterName, LPBYTE lpData, DWORD dwCount)
{
    HANDLE     hPrinter;
    DOC_INFO_1 DocInfo;
    DWORD      dwJob;
    DWORD      dwBytesWritten;

    // Need a handle to the printer.
    if (!OpenPrinter(szPrinterName, &hPrinter, NULL)) {
        int y = GetLastError();
        cout << "openFial" << y << endl;
        return FALSE;
    }

    // Fill in the structure with info about this "document."

    DocInfo.pDocName = LPSTR("My Document\0");
    DocInfo.pOutputFile = NULL;
    DocInfo.pDatatype = NULL; // LPWSTR("RAW\0");
    // Inform the spooler the document is beginning.
    if ((dwJob = StartDocPrinter(hPrinter, 1, (LPBYTE)&DocInfo)) == 0)
    {
        int x = GetLastError();
        cout << "StartDocPrinter Fial" << x << endl;
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Start a page.
    if (!StartPagePrinter(hPrinter))
    {
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Send the data to the printer.
    if (!WritePrinter(hPrinter, lpData, dwCount, &dwBytesWritten))
    {
        EndPagePrinter(hPrinter);
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // End the page.
    if (!EndPagePrinter(hPrinter))
    {
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Inform the spooler that the document is ending.
    if (!EndDocPrinter(hPrinter))
    {
        ClosePrinter(hPrinter);
        return FALSE;
    }
    // Tidy up the printer handle.
    ClosePrinter(hPrinter);
    // Check to see if correct number of bytes were written.
    if (dwBytesWritten != dwCount)
        return FALSE;
    return TRUE;
}

void getPrinterList() {
    PRINTER_INFO_2* printerList;
    unsigned char size;
    unsigned long pcbNeeded;
    unsigned long pcReturned;

    EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 2, NULL, 0, &pcbNeeded, &pcReturned);

    if ((printerList = (PRINTER_INFO_2*)malloc(pcbNeeded)) == 0) {
        return;
    }

    if (!EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 2, (LPBYTE)printerList, pcbNeeded, &pcbNeeded, &pcReturned)) {
        free(printerList);
        return;
    }

    for (int i = 0; i < (int)pcReturned; i++) {

        string printName(printerList[i].pPrinterName);
        if (printerList[i].Attributes & PRINTER_ATTRIBUTE_NETWORK) {
            cout << "網絡打印機" << printName << endl;
        }
        else {
            cout << "本地打印機" << printName << endl;
        }
    }

    cout << "number " << pcReturned << endl;

}

string getDefaultPrinterName() {
    DWORD size = 0;
    GetDefaultPrinter(NULL, &size);

    if (size) {
        TCHAR* buffer = new TCHAR[size];
        GetDefaultPrinter(buffer, &size);
        string printerName(buffer);
        return printerName;
    }
    else {
        return "";
    }
}

void Initialize(Local<Object> exports) {
    NODE_SET_METHOD(exports, "localPrintRawData", localPrintRawData);
}

NODE_MODULE(zq_device, Initialize)
複製代碼

參考:

support.microsoft.com/zh-cn/help/…

docs.microsoft.com/en-us/windo…

stackoverflow.com/questions/6…

social.msdn.microsoft.com/Forums/wind…

social.msdn.microsoft.com/Forums/vstu…

docs.microsoft.com/en-us/windo…

docs.microsoft.com/zh-cn/windo…

相關文章
相關標籤/搜索