使用c++调用windows打印api进行打印的示例代码

 更新时间:2020年06月28日 09:27:24   作者:子物  
这篇文章主要介绍了使用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, 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 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)

参考:

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打印内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言入门篇--变量[定义,初始化赋值,外部声明]

    C语言入门篇--变量[定义,初始化赋值,外部声明]

    本篇文章是c语言基础篇,本文对初识c语言的变量、变量的定义、初始化与赋值、变量的分类、含义、外部声明做了简要的描述,帮助大家快速入门c语言的世界,更好的理解c语言
    2021-08-08
  • C++模拟实现string的示例代码

    C++模拟实现string的示例代码

    这篇文章主要为大家详细介绍了C++模拟实现string的相关资料,文中的示例代码讲解详细,对我们学习C++有一定的帮助,需要的可以参考一下
    2022-11-11
  • C语言实现商品管理系统开发

    C语言实现商品管理系统开发

    这篇文章主要为大家详细介绍了C语言实现商品管理系统开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 深入理解二叉树的非递归遍历

    深入理解二叉树的非递归遍历

    本篇文章是对二叉树的非递归遍历进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++动态数组类的封装实例

    C++动态数组类的封装实例

    这篇文章主要介绍了C++动态数组类的封装,很重要的概念,需要的朋友可以参考下
    2014-08-08
  • 用pybind11封装C++实现的函数库的方法示例

    用pybind11封装C++实现的函数库的方法示例

    这篇文章主要介绍了用pybind11封装C++实现的函数库,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C++实现LeetCode(68.文本左右对齐)

    C++实现LeetCode(68.文本左右对齐)

    这篇文章主要介绍了C++实现LeetCode(68.文本左右对齐),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C语言编程数据结构基础详解小白篇

    C语言编程数据结构基础详解小白篇

    这篇文章主要介绍了数据结构的基础,非常适合初学数据结构的小白,有需要的朋友可以借鉴参考下,希望可以有所帮助,祝大家多多进步,早日升职加薪
    2021-09-09
  • C++如何去掉字符串首尾的空格

    C++如何去掉字符串首尾的空格

    这篇文章主要介绍了C++如何去掉字符串首尾的空格问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • C语言 结构体和指针详解及简单示例

    C语言 结构体和指针详解及简单示例

    本文主要介绍C语言 结构体和指针,这里整理了相关资料,并附示例代码和实现结果,以便大家学习参考,希望能帮助学习C语言的朋友
    2016-08-08

最新评论