我正在创建一个 C# 工具,用于将 PDF 文件发送到特定打印机。该工具将通过服务器上的任务计划程序每天运行一次。我找到了多种方法来实现此目的,但大多数需要使用
System.Drawing
和 System.Drawing.Printing
库。
这些库面临的挑战是它们依赖于 Windows 窗体应用程序或
System.Drawing.Common
库,这给我在任务计划程序环境中带来了问题。这是由于缺乏图形界面,如果找不到必要的图形环境,应用程序可能会进入等待状态或无限循环的风险。
具体来说,仅当我从服务器上的任务计划程序运行应用程序时才会出现该问题;在本地执行时它可以正常工作。
当应用程序处于“运行”状态时,它似乎进入了一个无限循环,它什么都不做,打印也不起作用。我在Sentry上没有看到任何日志,这会导致资源锁定,需要手动干预才能停止任务。
我还考虑过将打印命令发送到第三方应用程序(例如 Adobe)来处理打印,但这种方法对于服务器上的任务调度程序来说并不理想。
我回顾了这个问题,但它没有具体解决在任务调度程序环境中运行的功能,并且提供的答案没有提供针对我的具体问题量身定制的解决方案。
有人对如何使用开源解决方案在任务计划程序下运行的 C# 控制台应用程序中将 PDF 文件打印到特定打印机有建议吗? 是否有更“低级”的方法来执行 PDF 打印?例如,将文件直接发送到打印驱动程序?
using System.Drawing.Printing;
using System.IO;
using PdfiumViewer;
public bool QueueDocument(string printerPath, string filePath)
{
try
{
PrintDocument printDocument = new PrintDocument();
printDocument.PrinterSettings.PrinterName = printerPath;
using (PdfDocument document = PdfDocument.Load(filePath))
using (PrintDocument printDocumentPdf = document.CreatePrintDocument())
{
printDocumentPdf.PrinterSettings = printDocument.PrinterSettings;
printDocumentPdf.PrintController = new StandardPrintController();
printDocumentPdf.Print();
}
return true;
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
return false;
}
}
我通过彻底改变我的方法解决了这个问题。最初,我使用的是
System.Drawing.Printing
,但这在没有图形环境的服务器上通过任务计划程序运行应用程序时会出现问题。相反,我转而使用本机 Windows API 函数以 RAW 模式将文件直接发送到打印机,从而绕过了图形界面的需要:
[DllImport("winspool.drv", EntryPoint = "OpenPrinterA", SetLastError = true)]
public static extern bool OpenPrinter(string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.drv", EntryPoint = "ClosePrinter")]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "StartDocPrinterA", SetLastError = true)]
public static extern bool StartDocPrinter(IntPtr hPrinter, int level, ref DOCINFOA pDocInfo);
[DllImport("winspool.drv", EntryPoint = "EndDocPrinter")]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "StartPagePrinter")]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "EndPagePrinter")]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "WritePrinter", SetLastError = true)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, out int dwWritten);
[StructLayout(LayoutKind.Sequential)]
public struct DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
public static bool QueueDocument(string printerName, string filePath)
{
bool result = false;
IntPtr pBytes = IntPtr.Zero; // Pointer to the data to send to the printer
byte[] fileBytes = File.ReadAllBytes(filePath);
if (OpenPrinter(printerName, out var hPrinter, IntPtr.Zero))
{
var docInfo = new DOCINFOA
{
pDocName = Path.GetFileName(filePath),
pOutputFile = null,
pDataType = "RAW"
};
try
{
if (StartDocPrinter(hPrinter, 1, ref docInfo) && StartPagePrinter(hPrinter))
{
pBytes = Marshal.AllocHGlobal(fileBytes.Length);
Marshal.Copy(fileBytes, 0, pBytes, fileBytes.Length);
result = WritePrinter(hPrinter, pBytes, fileBytes.Length, out _);
}
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
}
finally
{
if (pBytes != IntPtr.Zero)
{
Marshal.FreeHGlobal(pBytes);
}
EndPagePrinter(hPrinter);
EndDocPrinter(hPrinter);
ClosePrinter(hPrinter);
}
}
else
{
SentrySdk.CaptureMessage("Printer connection error", SentryLevel.Error);
}
return result;
}
这种方法绕过了我遇到的问题
System.Drawing.Printing
,并且可以与任务计划程序顺利配合,因为它不依赖于图形界面。