使用c++調(diào)用windows打印api進(jìn)行打印的示例代碼
前言
在近期開發(fā)的收銀臺項(xiàng)目中,需要使用打印機(jī)進(jìn)行小票打印,打印流程的時序圖如下所示:

在客戶的使用過程中,遇到一個問題,如果機(jī)器安裝了打印機(jī)驅(qū)動,那么調(diào)用廠商提供的 sdk 進(jìn)行打印的話,會導(dǎo)致出現(xiàn)小票只打印一半的情況,對此,需要繞過廠商 sdk 使用系統(tǒng)的打印才能夠解決這一問題。
在 web 端打印中,需要調(diào)用瀏覽器打印 api 進(jìn)行網(wǎng)頁打印。這意味著,之前后端編寫的esc/pos無法復(fù)用到,同時,前端還得花費(fèi)精力來編寫 html 以及css 來完成打印內(nèi)容的排版,這無疑增加了復(fù)雜度以及工作量。正打算開始時,得到高人指點(diǎn)。
可以使用 windows api 進(jìn)行打印
具體參見這篇文檔
于是開始這方面的研究,功夫不負(fù)有心人,使用 windows api 完成了系統(tǒng)的打印,于是編寫這篇文章記錄踩過的坑。
首先看看如何進(jìn)行打?。?br />
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;
}
在對照,發(fā)現(xiàn)是 handle 是無效的,也就意味這 OpenPrinter 這一步驟沒有打開需要的打印機(jī)。于是嘗試使用 設(shè)備與打印機(jī)中的打印機(jī)名稱,還真就連上了,成功調(diào)用打印服務(wù)。
但客戶電腦上的打印機(jī)名稱是不固定的,不能使用固定打印機(jī)名稱,所以得拿到已經(jīng)連接了的打印機(jī)列表,于是搜索到了 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 << "網(wǎng)絡(luò)打印機(jī)" << printName << endl;
}
else {
cout << "本地打印機(jī)" << printName << endl;
}
}
cout << "number " << pcReturned << endl;
}
通過這一方式,的確獲取到了系統(tǒng)中可用的打印機(jī),可是拿到可用的打印機(jī)后還是有一個問題:“如何知道哪一個是小票打印機(jī)”?
為此又進(jìn)行了搜索,又找到了一個 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 "";
}
}
通過此方法獲取到系統(tǒng)默認(rèn)打印機(jī),客戶只需要設(shè)置默認(rèn)的打印機(jī)為小票打印機(jī)就完美解決問題了。
以下是完整代碼:
#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 << "網(wǎng)絡(luò)打印機(jī)" << printName << endl;
}
else {
cout << "本地打印機(jī)" << 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)
參考:
https://docs.microsoft.com/en-us/windows/win32/printdocs/openprinter
https://stackoverflow.com/questions/6682286/understanding-a-c-sample-printers-handles-strings
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
到此這篇關(guān)于使用c++調(diào)用windows打印api進(jìn)行打印的示例代碼的文章就介紹到這了,更多相關(guān)c++ 調(diào)用windows打印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)商品管理系統(tǒng)開發(fā)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)商品管理系統(tǒng)開發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08
用pybind11封裝C++實(shí)現(xiàn)的函數(shù)庫的方法示例
這篇文章主要介紹了用pybind11封裝C++實(shí)現(xiàn)的函數(shù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
C++實(shí)現(xiàn)LeetCode(68.文本左右對齊)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(68.文本左右對齊),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C語言編程數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)詳解小白篇
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),非常適合初學(xué)數(shù)據(jù)結(jié)構(gòu)的小白,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-09-09

