C++创建虚拟打印机

最近有个需求需要对报告打印进行统一的管理,最终实现方案如下:

1、安装Microsoft Print To PDF虚拟打印机,该打印机可以将所有打印数据转换为PDF

2、通过Microsoft Print To PDF虚拟机参数复制一台新的虚拟打印机

3、创建打印输出端口,指定输出路径

4、设置新虚拟打印机的端口为新创建的端口。

 

安装Microsoft Print To PDF

注意:仅支持Windows 10 及以上系统

Microsoft Print To PDF属于Windows可选功能,可以借助 dism.exe进行安装布署。如下:

1 dism /Online /Enable-Feature /FeatureName:"Printing-PrintToPDFServices-Features" /NoRestart /Quiet

 

使用CreateProcess函数执行dism.exe

 1 #include<Windows.h>
 2 #include<tchar.h>
 3 
 4 BOOL InstallMicrosoftPrintToPDF()
 5 {
 6     LPWSTR szCmd = _tcsdup(LR"(dism /Online /Enable-Feature /FeatureName:"Printing-PrintToPDFServices-Features" /NoRestart /Quiet)");
 7     STARTUPINFO si{};
 8     PROCESS_INFORMATION pi{};
 9     si.cb = sizeof(si);
10     auto nRet = CreateProcess(NULL, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
11 
12     if (!nRet)
13     {
14         if (pi.hThread)
15         {
16             CloseHandle(pi.hThread);
17         }
18 
19         if (pi.hProcess)
20         {
21             CloseHandle(pi.hProcess);
22         }
23     }
24 
25     free(szCmd);
26     return nRet;
27 }

 

创建本地打印机端口

 1 /// <summary>
 2 /// 创建本地打印机端口
 3 /// </summary>
 4 /// <returns></returns>
 5 BOOL CreateLocalPort()
 6 {
 7     LPWSTR szPortName = _tcsdup(L"C:\\test.pdf");
 8 
 9     LPPORT_INFO_2 pPrtInfo2 = NULL;
10     DWORD pcbNeeded = 0;
11     DWORD pcReturned = 0;
12 
13     //枚举本地打印机端口
14     EnumPorts(NULL, 2, NULL, 0, &pcbNeeded, &pcReturned);
15 
16 
17     //枚举本地打印机端口
18     pPrtInfo2 = (LPPORT_INFO_2)LocalAlloc(LPTR, pcbNeeded);
19     auto result = EnumPorts(NULL, 2, (LPBYTE)pPrtInfo2, pcbNeeded, &pcbNeeded, &pcReturned);
20 
21     if (!result || pPrtInfo2 == NULL)
22         return FALSE;
23 
24     for (int i = 0; i < pcReturned; i++)
25     {
26         if (wcscmp((pPrtInfo2 + i)->pPortName, szPortName) == 0)
27             return TRUE;
28     }
29 
30     HANDLE hPrinter = NULL;
31     PRINTER_DEFAULTS printerDefaults{};
32     printerDefaults.pDatatype = NULL;
33     printerDefaults.pDevMode = NULL;
34     printerDefaults.DesiredAccess = SERVER_ACCESS_ADMINISTER;
35 
36     LPWSTR szPrinterName = _tcsdup(L",XcvMonitor Local Port");
37 
38     result = OpenPrinter(szPrinterName, &hPrinter, &printerDefaults);
39 
40     if (!result || hPrinter == NULL)
41     {
42         //查看错误
43         //GetLastError();
44         return FALSE;
45     }
46 
47     DWORD dwPcbNeeded = 0;
48     DWORD dwStatus = 0;
49 
50     result = XcvData(hPrinter, L"AddPort", (PBYTE)szPortName, (lstrlenW(szPortName) + 1) * sizeof(TCHAR), NULL, 0, &dwPcbNeeded, &dwStatus);
51 
52     if (!result)
53     {
54         //GetLastError();
55         return FALSE;
56     }
57 
58     ClosePrinter(hPrinter);
59 
60     free(szPortName);
61     free(szPrinterName);
62 }

 

创建新虚拟打印机

 

创建端口后,调用EnumPrinters函数枚举打印机,找到Microsoft Print to PDF打印机。

拿到Microsoft Print to PDF的打印机参数后,其它参数不变,只更改打印机名称,调用AddPrinter创建一个新打印机。

再调用SetPrinter设置端口为刚创建的端口

 1 /// <summary>
 2 /// 根据Microsoft Print To PDF创建新虚拟打印机
 3 /// </summary>
 4 /// <returns></returns>
 5 BOOL CreateVirtualPrinter()
 6 {
 7     LPTSTR szPrinterName = _tcsdup(L"虚拟打印机");
 8     LPTSTR szPortName = _tcsdup(L"C:\\test.pdf");
 9 
10     DWORD pcbNeeded = 0;
11     DWORD pcReturned = 0;
12 
13     auto result = EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 2, NULL, 0, &pcbNeeded, &pcReturned);
14     LPPRINTER_INFO_2 pPrtInfo2 = (LPPRINTER_INFO_2)LocalAlloc(LPTR, pcbNeeded);
15     result = EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 2, (LPBYTE)pPrtInfo2, pcbNeeded, &pcbNeeded, &pcReturned);
16 
17     if (!result || pPrtInfo2 == NULL)
18         return FALSE;
19 
20     for (int i = 0; i < pcReturned; i++)
21     {
22         LPPRINTER_INFO_2 p_curPrtInfo = pPrtInfo2 + i;
23         if (wcscmp(p_curPrtInfo->pPrinterName, L"Microsoft Print to PDF") == 0)
24         {
25             p_curPrtInfo->pPrinterName = szPrinterName;
26             HANDLE hPrinter = AddPrinter(NULL, 2, (LPBYTE)p_curPrtInfo);
27             LPPRINTER_INFO_2 p_tmpPrtInfo = NULL;
28             DWORD dw_tmpNeeded = 0;
29             GetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, dw_tmpNeeded, &dw_tmpNeeded);
30             p_tmpPrtInfo = (LPPRINTER_INFO_2)LocalAlloc(LPTR, dw_tmpNeeded);
31             result = GetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, dw_tmpNeeded, &dw_tmpNeeded);
32 
33             if (p_tmpPrtInfo == NULL || result == FALSE)
34                 return FALSE;
35 
36             p_tmpPrtInfo->pPortName = szPortName;
37             result = SetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, 0);
38             ClosePrinter(hPrinter);
39             SetDefaultPrinter(szPrinterName);
40             break;
41         }
42     }
43 
44     free(szPrinterName);
45     free(szPortName);
46 
47     return TRUE;
48 }

 

创建完成后,可以在设备和打印机里看到新创建出来 的打印机。

 

 

可以看到打印机默认输出位置为 C:\test.pdf(注意:不支持在系统盘根目录创建,正式使用时,请使用其它路径)

 

 此时我们再进行打印时,会默认将打印内容转换为PDF并输出到 C:\test.pdf

 

 示例代码

 

参考:

https://stackoverflow.com/questions/1325485/how-to-create-a-new-port-and-assign-it-to-a-printer

https://learn.microsoft.com/en-us/windows/win32/printdocs/printdocs-printing

posted @ 2023-03-30 09:45  zhaotianff  阅读(587)  评论(0编辑  收藏  举报