乐于分享
好东西不私藏

Windows创建虚拟PDF打印机并指定输出文件名

Windows创建虚拟PDF打印机并指定输出文件名

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

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进行安装部署。如下:

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

使用CreateProcess函数执行dism.exe

#include<Windows.h>#include<tchar.h>BOOL InstallMicrosoftPrintToPDF(){    LPWSTR szCmd = _tcsdup(LR"(dism /Online /Enable-Feature /FeatureName:"Printing-PrintToPDFServices-Features" /NoRestart /Quiet)");    STARTUPINFO si{};    PROCESS_INFORMATION pi{};    si.cb = sizeof(si);    auto nRet = CreateProcess(NULL, szCmd, NULLNULLFALSE0NULLNULL, &si, &pi);    if (!nRet)    {        if (pi.hThread)        {            CloseHandle(pi.hThread);        }        if (pi.hProcess)        {            CloseHandle(pi.hProcess);        }    }    free(szCmd);    return nRet;}

创建本地打印机端口

/// <summary>/// 创建本地打印机端口/// </summary>/// <returns></returns>BOOL CreateLocalPort(){    LPWSTR szPortName = _tcsdup(L"C:\\test.pdf");    LPPORT_INFO_2 pPrtInfo2 = NULL;    DWORD pcbNeeded = 0;    DWORD pcReturned = 0;    //枚举本地打印机端口    EnumPorts(NULL2NULL0, &pcbNeeded, &pcReturned);    //枚举本地打印机端口    pPrtInfo2 = (LPPORT_INFO_2)LocalAlloc(LPTR, pcbNeeded);    auto result = EnumPorts(NULL2, (LPBYTE)pPrtInfo2, pcbNeeded, &pcbNeeded, &pcReturned);    if (!result || pPrtInfo2 == NULL)        return FALSE;    for (int i = 0; i < pcReturned; i++)    {        if (wcscmp((pPrtInfo2 + i)->pPortName, szPortName) == 0)            return TRUE;    }    HANDLE hPrinter = NULL;    PRINTER_DEFAULTS printerDefaults{};    printerDefaults.pDatatype = NULL;    printerDefaults.pDevMode = NULL;    printerDefaults.DesiredAccess = SERVER_ACCESS_ADMINISTER;    LPWSTR szPrinterName = _tcsdup(L",XcvMonitor Local Port");    result = OpenPrinter(szPrinterName, &hPrinter, &printerDefaults);    if (!result || hPrinter == NULL)    {        //查看错误        //GetLastError();        return FALSE;    }    DWORD dwPcbNeeded = 0;    DWORD dwStatus = 0;    result = XcvData(hPrinter, L"AddPort", (PBYTE)szPortName, (lstrlenW(szPortName) + 1) * sizeof(TCHAR), NULL0, &dwPcbNeeded, &dwStatus);    if (!result)    {        //GetLastError();        return FALSE;    }    ClosePrinter(hPrinter);    free(szPortName);    free(szPrinterName);}

创建新虚拟打印机

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

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

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

/// <summary>/// 根据Microsoft Print To PDF创建新虚拟打印机/// </summary>/// <returns></returns>BOOL CreateVirtualPrinter(){    LPTSTR szPrinterName = _tcsdup(L"虚拟打印机");    LPTSTR szPortName = _tcsdup(L"C:\\test.pdf");    DWORD pcbNeeded = 0;    DWORD pcReturned = 0;    auto result = EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL2NULL0, &pcbNeeded, &pcReturned);    LPPRINTER_INFO_2 pPrtInfo2 = (LPPRINTER_INFO_2)LocalAlloc(LPTR, pcbNeeded);    result = EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL2, (LPBYTE)pPrtInfo2, pcbNeeded, &pcbNeeded, &pcReturned);    if (!result || pPrtInfo2 == NULL)        return FALSE;    for (int i = 0; i < pcReturned; i++)    {        LPPRINTER_INFO_2 p_curPrtInfo = pPrtInfo2 + i;        if (wcscmp(p_curPrtInfo->pPrinterName, L"Microsoft Print to PDF") == 0)        {            p_curPrtInfo->pPrinterName = szPrinterName;            HANDLE hPrinter = AddPrinter(NULL2, (LPBYTE)p_curPrtInfo);            LPPRINTER_INFO_2 p_tmpPrtInfo = NULL;            DWORD dw_tmpNeeded = 0;            GetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, dw_tmpNeeded, &dw_tmpNeeded);            p_tmpPrtInfo = (LPPRINTER_INFO_2)LocalAlloc(LPTR, dw_tmpNeeded);            result = GetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, dw_tmpNeeded, &dw_tmpNeeded);            if (p_tmpPrtInfo == NULL || result == FALSE)                return FALSE;            p_tmpPrtInfo->pPortName = szPortName;            result = SetPrinter(hPrinter, 2, (LPBYTE)p_tmpPrtInfo, 0);            ClosePrinter(hPrinter);            SetDefaultPrinter(szPrinterName);            break;        }    }    free(szPrinterName);    free(szPortName);    return TRUE;}

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

可以看到打印机默认输出位置为 C:\test.pdf

注意:不支持在系统盘根目录创建,正式使用时,请使用其它路径

 

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

示例代码

https://github.com/zhaotianff/cnblog-demo-code/tree/main/CreateVirtualPrinterDemo

参考:

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

📚 往期干货推荐

你可能还想看:

01

Win32进程遍历的几种方法

02

如何调用win32 api获取文件缩略图

03

Win32绕过UAC弹窗获取管理员权限

04

Win32获取BIOS信息

05

Win32中的WMI编程

06

如何创建不需要Visual C++运行时的独立C++程序

07

C#与C++互操作

08

Windows 中的 I/O:你必须了解的核心函数与工作流程

09

Windows 远线程注入实战:原理剖析 + 代码实现

10

无需驱动!用户态 Windows API Hook 实战技巧

Happy Time

Win开发者

[

粉丝微信群

].

githubhttps://github.com/zhaotianff

Advertisement

欢迎转载

如需转载本文或申请长期白名单,请在公众号后台留言「转载」,并注明公众号名称、ID 及转载用途。
期待与更多优质账号交流合作,共同创作优质内容。