preface

In the cashier desk project recently developed, a printer is needed to print receipts. The sequence diagram of the printing process is shown as follows:

Screenshot. PNG

In the use of the customer, there is a problem. If the machine is equipped with a printer driver, invoking the SDK provided by the manufacturer for printing will lead to the printing of half of the receipt. In this case, we need to bypass the SDK of the manufacturer and use the system to print to solve the problem.

In web printing, you need to call the browser printing API to print web pages. This means that the ESC/POS written by the back end cannot be reused, and the front end has to spend energy to write HTML and CSS to complete the typesetting of the printed content, which undoubtedly increases the complexity and workload. I was just about to get started.

You can print using the Windows API

See this document for details

As a result, I began my research in this area. All my efforts paid off. I used Windows API to complete the system printing, so I wrote this article to record the pits I had stepped on.

First look at how to print:

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;
}
Copy the code

As mentioned in the documentation, “OpenPrinter” can be passed null to use the local printing service when opening the printer. Since the printer name is not known, null is passed, and the result is that StartDocPrinter keeps failing. Later, I learned that the error code could be checked by using GetLastError, and when I got the error code, I found that handle was invalid, which means that the required printer was not opened in the step of OpenPrinter. Then try to use the name of the printer in the device and printer, really connected, successfully call the print service.

However, the printer names on the customer’s computer are not fixed and you cannot use fixed printer names. Therefore, you need to get a list of printers that are already connected. We searched EnumPrinters API.

void getPrinterList(a) {
    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 << "Network Printer" << printName << endl;
        }
        else {
            cout << "Local Printer" << printName << endl; }}cout << "number " << pcReturned << endl;

}
Copy the code

Through this way, did get the available printer in the system, but after getting the available printer there is still a problem: “how to know which is the receipt printer”?

A search for GetDefaultPrinter found an API that uses the following:

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

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

Through this method to get the system default printer, the customer only need to set the default printer as the receipt printer to solve the problem perfectly.

Here is the complete code:

#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 << "network printer" << printName << endl; } else {cout << "local printer" << 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)Copy the code

Reference:

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…