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

时间: 2020-06-28

C++软件添加dump调试打印日志(推荐)

C++软件添加dump调试打印日志(推荐) #include <DbgHelp.h> #pragma comment(lib, "dbghelp.lib") LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *pExceptionInfo) { //cout << "Enter TopLevelExceptionFilter Function" << en

C++中int类型按字节打印输出的方法

前言 今天在项目编程中,遇到一个问题,u32类型的参数,要赋值给一个u8 array[3],想用memcpy()函数进行赋值,由于类型大小不一致,一时不知道怎么做,经过查找,得以解决.说明如下; 项目是在内网中做,在查找过程中用自己笔记本做了一下实验,vs2013版本. 类似主题是int类型按字节打印数据,sizeof(int)实验验证后为4字节,就分别打印出这4个字节中的数值. 先贴上代码 #include<iostream> using namespace std; int main()

使用VC++实现打印乘法口诀表

关于乘法口诀表,这里就不多废话了,大家都明白,下面说下用vc++实现的思路代码: 乘法口诀表.cpp 复制代码 代码如下: #include<stdio.h> int main () {  int i,j;    for(i=1;i<=9;i++)    {   for(j=1;j<=i;j++)           printf("%d ¡Á %d =%2d   ",j,i,i*j);        putchar('   \n');    }    getc

C++实现打印两个有序链表公共部分的方法

本文实例讲述了C++实现打印两个有序链表公共部分的方法.分享给大家供大家参考,具体如下: 题目: 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分. 解题思路及代码: 1.head1的值小于head2,则head1往下移动 2.head1的值小于head2,则head2往下移动 3.相等则打印任何一个链表节点的值,head1和head2都往下移动. 4.当head1或head2移动到NULL,终止. 算法C++代码: typedef struct Node { int da

C++实现打印1到最大的n位数

本文以实例形式讲述了C++实现打印1到最大的n位数的方法.分享给大家供大家参考.具体方法如下: 题目要求: 输入数字n,按顺序打印出从1最大的n位十进制数,比如输入3,则打印出1,2 ,3一直到最大的3位数999 实现代码如下: #include <iostream> using namespace std; void printArray(char *array, int size) { if (array == NULL || size <= 0) { return; } int i

C++中实现把表的数据导出到EXCEL并打印实例代码

实现把表的数据导出到EXCEL并打印实例代码 首先加入这两句: #include "utilcls.h" #include "comobj.hpp" 下面正式开始: void __fastcall TMainForm::ToExcel(TADOQuery *TT,AnsiString str) {//TT为被导出数据的表,str为命令(具体看代码底部的if语句) #define PG OlePropertyGet #define PS OlePropertySet

asp中把数据导出为excel的2种方法

我们在做项目的时候经常要将数据库的数据导出到excel中,很多asp用户并不知道怎么写. 这里明凯总结了两种方法来导出excel,希望能帮到大家. 方法一:用excel组件 < % set rs=server.createobject("adodb.recordset") sql="select * from mkusers" rs.open sql,objconn,1,1 Set ExcelApp =CreateObject("Excel.Appl

Laravel 将数据表的数据导出,并生成seeds种子文件的方法

用过laravel的都知道,我们表里面的数据通常是保存到seeder文件中,但是有些时候需要将表里已有的数据导出到seed文件中,那么怎么导出呢,其实这里有个扩展包叫iseed,我们可以利用它来把数据表里的数据导出到seed中. 安装isseed 安装isseed,我这里是laravel 5.4,安装的iseed是2.1版本的,你们看情况,随意 composer require "orangehill/iseed": "2.1" 将iseed加入到composer.

使用python将大量数据导出到Excel中的小技巧分享

(1) 问题描述:为了更好地展示数据,Excel格式的数据文件往往比文本文件更具有优势,但是具体到python中,该如何导出数据到Excel呢?如果碰到需要导出大量数据又该如何操作呢? 本文主要解决以上两个问题. (2)具体步骤如下: 1.第一步,安装openpyxl, 使用pip install openpyxl即可,但是在windows下安装的是2.2.6版本,但是centos自动安装的是4.1版本,(多谢海哥的提醒). 写的代码在windows下运行没问题,但centos上却报错了,说是e

通过剪贴板实现将DataGridView中的数据导出到Excel

将DataGridView中的数据导出到Excel中有许多方法,常见的方法是使用Office COM组件将DataGridView中的数据循环复制到Excel Cell对象中,然后再保存整个Excel Workbook.但是如果数据量太大,例如上万行数据或者有多个Excel Sheet需要同时导出,效率会比较低.可以尝试使用异步操作或多线程的方式来解决UI死锁的问题. 这里介绍一种直接通过Windows剪贴板将数据从DataGridView导出到Excel的方法.代码如下: 复制代码 代码如下:

Grid或者DataTable中数据导出为Excel原来这么简单

以前一直认为,将Grid 或者DataTable中的数据导出到Excel功能实现会非常复杂,可能会想用什么类库什么的或者实在太难就用csv算了. 看了FineUI中的将Grid导出为Excel的实现方法,实际上是可以非常简单.看来很难的问题,变换一种思路就可以非常简单. 1. Aspx后台代码输出Content Type信息 复制代码 代码如下: Response.ClearContent(); Response.AddHeader("content-disposition", &qu

java web将数据导出为Excel格式文件代码片段

本文实例为大家分享了java web将数据导出为Excel格式文件的具体代码,供大家参考,具体内容如下 1.jsp代码 <input type="button" class="btn btn-info" onclick="getVerExcel();" value="导出为Excel文件" /> 2.js代码 function getVerExcel() { window.location.href = '/pms

PHP将Excel导入数据库及数据库数据导出至Excel的方法

本文实例讲述了PHP将Excel导入数据库及数据库数据导出至Excel的方法.分享给大家供大家参考.具体实现方法如下: 一.导入 导入需要使用能读取Excel的组件,网上也有比较好的组件,这里分享我使用的:下载  提取码:vxyn.(注意两个文件有引用关系) <?php //传入要导入的Excel的文件名 function import_to_DB($filename) { require_once'reader.php'; $data = new Spreadsheet_Excel_Reade

C#开发教程之利用特性自定义数据导出到Excel

网上C#导出Excel的方法有很多.但用来用去感觉不够自动化.于是花了点时间,利用特性做了个比较通用的导出方法.只需要根据实体类,自动导出想要的数据 1.在NuGet上安装Aspose.Cells或者用微软自带类库也可以 2.需要导出的数据的实例类: using System.ComponentModel; using System.Reflection; using System.Runtime.Serialization; public class OrderReport { [Displa

python实现数据导出到excel的示例--普通格式

此文是在django框架下编写,从数据库中获取数据使用的是django-orm 用python导出数据到excel,简单到爆!(普通的excel格式) 安装xlwt pip install xlwt 编写py文件 from xlwt import * import StringIO from apps.song.models import Song def excel_ktvsong(request):
 """
导出excel表格
"""
 _