最近有个需求需要对报告打印进行统一的管理,最终实现方案如下:
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
参考:
c++ - How to create a new port and assign it to a printer - Stack Overflow
Printing (Documents and Printing) - Win32 apps | Microsoft Learn