1. 程式人生 > 程式設計 >使用c++呼叫windows列印api進行列印的示例程式碼

使用c++呼叫windows列印api進行列印的示例程式碼

前言

在近期開發的收銀臺專案中,需要使用印表機進行小票列印,列印流程的時序圖如下所示:

使用c++呼叫windows列印api進行列印的示例程式碼

在客戶的使用過程中,遇到一個問題,如果機器安裝了印表機驅動,那麼呼叫廠商提供的 sdk 進行列印的話,會導致出現小票只打印一半的情況,對此,需要繞過廠商 sdk 使用系統的列印才能夠解決這一問題。

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

可以使用 windows api 進行列印

具體參見這篇文件

於是開始這方面的研究,功夫不負有心人,使用 windows api 完成了系統的列印,於是編寫這篇文章記錄踩過的坑。
首先看看如何進行列印:

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 這一步驟沒有開啟需要的印表機。於是嘗試使用 裝置與印表機中的印表機名稱,還真就連上了,成功呼叫列印服務。

但客戶電腦上的印表機名稱是不固定的,不能使用固定印表機名稱,所以得拿到已經連線了的印表機列表,於是搜尋到了 EnumPrinters 這一api,具體用法如下:

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

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

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

  if (!EnumPrinters(PRINTER_ENUM_LOCAL,(LPBYTE)printerList,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 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,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,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,(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,&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,&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);
    string printerName(buffer);
    return printerName;
  }
  else {
    return "";
  }
}

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

NODE_MODULE(zq_device,Initialize)

參考:

https://support.microsoft.com/zh-cn/help/138594/howto-send-raw-data-to-a-printer-by-using-the-win32-api

https://docs.microsoft.com/en-us/windows/win32/printdocs/openprinter

https://stackoverflow.com/questions/6682286/understanding-a-c-sample-printers-handles-strings

https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/a27c6615-9452-44b1-90fc-9b91b15f0e50/openprinter-returing-errorinvalidprintername1801-when-called-with?forum=windowsgeneraldevelopmentissues

https://social.msdn.microsoft.com/Forums/vstudio/en-US/de7c55a1-ae63-49c9-a87a-fe3bf32822e4/how-to-use-the-enumprinters-function-to-be-able-to-classify-installed-printers-into-quot-network?forum=vclanguage

https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-

https://docs.microsoft.com/zh-cn/windows/win32/debug/system-error-codes--1700-3999-?redirectedfrom=MSDN

到此這篇關於使用c++呼叫windows列印api進行列印的示例程式碼的文章就介紹到這了,更多相關c++ 呼叫windows列印內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!