PDFCreator是一个半开源的PDF虚拟打印机,其核心使用Ghostscript程序将转化为PDF或图片。
一、工程介绍:
工程分得很细,光文件夹就有Conversion、Core、Edisions、Startup
分类 | 工程 | 说明 |
---|---|---|
Conversion | ITextProcessing | iTextSharp生成PDF的接口 |
Conversion | PdfProcessingIterface | 接口 |
Conversion | Actions | |
Conversion | ActionsInterface | 接口 |
Conversion | Dropbox | |
Conversion | Ghostscript | |
Conversion | Jobs | |
Conversion | Settings | |
Core | Complementation | |
Core | Communication | |
Core | Controller | |
Core | DirectConversion | |
Core | GpoAdapter | |
Core | JobInfoQueue | 打印任务对列 |
Core | Printing | |
Core | ServiceLocator | |
Core | Services | |
Core | SettingsManagement | |
Core | UsagStatistics | |
Core | Workflow | |
Editions | EditionBase | |
Editions | PDFCreator | |
Startup | Startup | |
Startup | StartupInterface | |
UI | COM | |
UI | ComWrapper | |
UI | Ineractions | |
UI | Presentation | |
UI | PrismHelper | |
UI | RssFeed | |
Setup | SetupHelper | |
一、 使用postscript打印的地方GhostScript:Run |
private bool Run(IList<string> parameters, string tempOutputFolder)
{
var parametersWithoutPassword = parameters.Select(param => param.StartsWith(PrintingDevice.PasswordParameter) ?
PrintingDevice.PasswordParameter + "***" : param);
_logger.Debug("Ghostscript Parameters:\r\n" + string.Join("\r\n", parametersWithoutPassword));
// Start the child process.
var p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = GhostscriptVersion.ExePath;
p.StartInfo.CreateNoWindow = true;
var isFirst = true;
var sb = new StringBuilder();
foreach (var s in parameters)
{
var tmp = s;
if (isFirst)
{
isFirst = false;
continue;
}
if (tmp.Contains(" ") && !tmp.Contains("\""))
{
tmp = tmp.Replace("\"", "\\\"");
tmp = "\"" + tmp + "\"";
}
sb.AppendLine(tmp);
}
var parameterFile = Path.Combine(tempOutputFolder, "parameters.txt");
File.WriteAllText(parameterFile, sb.ToString());
p.StartInfo.Arguments = string.Format("@\"{0}\"", parameterFile);
var gsThread = new Thread(() => RunAndReadStdOut(p));
gsThread.Start();
if (!gsThread.Join(Timeout))
{
_logger.Error($"The ghostscript did not finish within {Timeout.TotalMinutes} minutes");
p.Kill();
return false;
}
return p.ExitCode == 0;
}
二、打印的过程(以记事本打印为例)
[0]
DocumentTitle=E:\QQFile\DHCBPCom.txt
OriginalFilePath=
WinStation=Console
UserName=dengtijin
ClientComputer=\\DESKTOP-ID08QLT
SpoolFileName=C:\Users\DENGTI~1\AppData\Local\Temp\PDFCreator\spool\21-4B3C24C9F7E046E2AAE848B4316045D0.PS
PrinterName=PDFCreator
PrinterParameter=
ProfileParameter=
OutputFileParameter=
SessionId=2
JobCounter=26
JobId=21
SourceFileType=ps
Copies=1
TotalPages=38
UserTokenEvaluated=False
C:\Users\DENGTI~1\AppData\Local\Temp\PDFCRE~1\Temp\JOB_QH~1
GhostScript:RunAndReadStdOut
不断读取另一进程的输出以显示打印进度
while (!p.StandardOutput.EndOfStream)
{
var c = (char)p.StandardOutput.Read();
sbout.Append(c);
if ((c == ']') || (c == '\n'))
{
RaiseOutputEvent(sbout.ToString());
sbout.Length = 0;
}
}
=>RaiseOutputEvent
=>GhostscriptConverter:Ghostscript_Output
多页打印
三、PDFCreator是怎么监听到打印的?
ClawPDF使用了(clawmon,来至mfilemon开源项目)来实现对打印的监听
PipeServer是在Pdfforge.Communication中,该代码未公开
PipeServerManager.cs
public bool StartServer()
{
_logger.Debug("Starting pipe server thread");
if (!_pipeServer.Start())
return false;
_pipeServer.OnNewMessage += (sender, args) => _newPipeJobHandler.HandlePipeMessage(args.Message);
return true;
}
NewPipeJobHandler.cs:HandlePipeMessage
public void HandlePipeMessage(string message)
{
_logger.Debug("New Message received: " + message);
if (message.StartsWith("NewJob|", StringComparison.OrdinalIgnoreCase))
{
HandleNewJobMessage(message);
}
else if (message.StartsWith("DragAndDrop|", StringComparison.OrdinalIgnoreCase))
{
HandleDroppedFileMessage(message, false);
}
else if (message.StartsWith("DragAndDrop+ManagePrintJobs|", StringComparison.OrdinalIgnoreCase))
{
HandleDroppedFileMessage(message, true);
}
else if (message.StartsWith("ShowMain|", StringComparison.OrdinalIgnoreCase))
{
_mainWindowThreadLauncher.LaunchMainWindow();
}
else if (message.StartsWith("ReloadSettings|", StringComparison.OrdinalIgnoreCase))
{
_logger.Info("Pipe Command: Reloading settings");
_settingsManager.LoadAllSettings();
}
}
PrintJobViewModel
jobInfoQueue.OnNewJobInfo += (sender, args) => UpdateNumberOfPrintJobsHint(jobInfoQueue.Count);
_jobInfoQueue = jobInfoQueue;
UpdateNumberOfPrintJobsHint(jobInfoQueue.Count);
四、虚拟打印程序怎么注册的?
又,双,是没有公开的代码,在PrinterHelper.exe中
public ActionResult InstallPrinter(IInstallingCommand options)
{
string str = "InstallPrinter";
this._logger.Trace(str + ": Started");
ActionResult actionResult = this._iPrinterValidation.CheckInstallPrinter(options.Printernames, options.PortApplication);
if (actionResult.Code != 0)
{
return this.LogAndReturn(actionResult);
}
this._iPrinterValidation.InstallDriverIfMissing(options.PackageAware, options.PpdFile);
if (options.Server)
{
this._logger.Info("Server detected in environment" + PrinterSetup.PRINTER_ENVIRONMENT);
string text;
if (PrinterSetup.PRINTER_ENVIRONMENT == PrinterSetup.WINDOWS32_ENVIRONMENT)
{
text = PrinterSetup.WINDOWS64_ENVIRONMENT;
}
else
{
text = PrinterSetup.WINDOWS32_ENVIRONMENT;
}
try
{
if (options.PackageAware)
{
this._win32Printer.AddPdfcreatorPrinterDriverFromPackage(text);
}
else
{
this._win32Printer.AddPdfcreatorPrinterDriverGdi(text, PrinterDescriptionFileLanguage.CurrentCulture, options.PpdFile);
}
}
catch (Exception exception)
{
this._logger.Warn(exception, string.Format("Could not install printer driver for additional environment {0}, package-aware: {1}", text, options.PackageAware));
}
}
if (!this._win32Printer.IsMonitorInstalled())
{
if (this._win32Printer.AddPdfcreatorPrinterMonitor().Code != 0)
{
this._logger.Info("Monitor not installed successfully!");
return this.LogAndReturn(new ActionResult(3, string.Format("Cannot install the printer monitor '{0}'.", PrinterSetup.MONITOR_NAME), null));
}
this._logger.Info("Monitor installed successfully");
}
this._win32Printer.AddDefaultPort(options.PortApplication, options.Server);
string text2 = PrinterSetup.PORT_NAME;
if (!options.SinglePort && !this._win32Printer.IsPortInstalled(text2))
{
this._win32Printer.AddPort(text2);
}
foreach (string printerName in options.Printernames)
{
if (options.SinglePort)
{
text2 = this.GetFreePort();
if (string.IsNullOrEmpty(text2))
{
return new ActionResult(3, "Cannot find a free port!", null);
}
this._win32Printer.AddPort(text2);
}
ActionResult actionResult2 = this.AddOnePrinter(printerName, text2);
if (actionResult2.Code != 0)
{
return this.LogAndReturn(actionResult2);
}
this.Log(actionResult2);
}
this._logger.Trace(str + ": Finished");
return this.LogAndReturn(new ActionResult(0, "Printer installed successfully", null));
}
最终在Win32Printer.cs中掉用winspool.drv中的API实现
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr AddPrinter(string pName, uint level, [In] ref PrinterInfo2 pPrinter);
// Token: 0x060000D8 RID: 216
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int AddPrinterDriver(string pName, uint level, [In] ref DriverInfo3 pDriverInfo);
// Token: 0x060000D9 RID: 217
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int ClosePrinter(IntPtr hPrinter);
// Token: 0x060000DA RID: 218
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetPrinterDriverDirectory(string pName, string pEnvironment, uint level, [Out] StringBuilder pDriverDirectory, uint cbBuf, [In] ref uint pcbNeened);
// Token: 0x060000DB RID: 219
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int AddMonitor(string pName, uint Level, ref MonitorInfo2 pMonitors);
// Token: 0x060000DC RID: 220
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DeletePrinterDriverEx(string pName, string pEnvironment, string pDriverName, int dwDeleteFlag, int dwVersionFlag);
// Token: 0x060000DD RID: 221
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DeleteMonitor(string pName, string pEnvironment, string pMonitorName);
// Token: 0x060000DE RID: 222
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DeletePrinter(IntPtr hPrinter);
// Token: 0x060000DF RID: 223
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int OpenPrinter(string pPrinterName, ref IntPtr phPrinter, PrinterDefaults pDefault);
// Token: 0x060000E0 RID: 224
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPorts(string pName, uint level, IntPtr lpbPorts, uint cbBuf, ref uint pcbNeeded, ref uint pcReturned);
// Token: 0x060000E1 RID: 225
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool EnumMonitors(string pName, uint level, IntPtr pMonitors, uint cbBuf, ref uint pcbNeeded, ref uint pcReturned);
// Token: 0x060000E2 RID: 226
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrinters(PrinterEnumFlags flags, string Name, uint Level, IntPtr pPrinterEnum, uint cbBuf, ref uint pcbNeeded, ref uint pcReturned);
// Token: 0x060000E3 RID: 227
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrinterDrivers(string pName, string pEnvironment, uint level, IntPtr pDriverInfo, uint cdBuf, ref uint pcbNeeded, ref uint pcRetruned);
// Token: 0x060000E4 RID: 228
[DllImport("winspool.drv", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetPrinter(IntPtr hPrinter, uint Level, IntPtr pPrinter, uint command);
// Token: 0x060000E5 RID: 229
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetPrinter(IntPtr hPrinter, uint Level, IntPtr pPrinter, uint cbBuf, ref uint pcbNeeded);
// Token: 0x060000E6 RID: 230
[DllImport("Winspool.drv")]
private static extern int UploadPrinterDriverPackage(string pszServer, string pszInfPath, string pszEnvironment, uint dwFlags, IntPtr hwnd, StringBuilder pszDestInfPath, out ulong pcchDestInfPath);
// Token: 0x060000E7 RID: 231
[DllImport("Winspool.drv")]
private static extern int InstallPrinterDriverFromPackage(string pszServer, string pszInfPath, string pszDriverName, string pszEnvironment, uint dwFlags);
// Token: 0x060000E8 RID: 232
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
// Token: 0x060000E9 RID: 233
[DllImport("advapi32")]
public static extern int RegRenameKey(SafeRegistryHandle hKey, [MarshalAs(UnmanagedType.LPWStr)] string oldname, [MarshalAs(UnmanagedType.LPWStr)] string newname);
其他可以借鉴的地方
FTP:
FtpClientWrap: FluentFTP.dll
SftpClientWrap : Renci.SshNet.dll
依赖注入
SimpleInjector
.net MVC也使用SimpleInjector实现依赖注入哦
全局异常捕捉
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
public bool Print(TimeSpan processTimeout)
{
if (CommandType == PrintType.Unprintable)
throw new InvalidOperationException("File is not printable");
if (CommandType == PrintType.Print && Printer != _printerHelper.GetDefaultPrinter())
throw new InvalidOperationException("The default printer needs to be set in order to print this file");
var p = ProcessWrapperFactory.BuildProcessWrapper(new ProcessStartInfo());
var verb = SupportsPrintTo()
? "printto"
: "print";
var command = GetCommand(verb);
var arguments = SupportsPrintTo()
? command.GetReplacedCommandArgs(Filename, Printer)
: command.GetReplacedCommandArgs(Filename);
p.StartInfo.FileName = command.Command;
p.StartInfo.Arguments = arguments;
Logger.Debug($"Launching {verb} for \"{Filename}\": {command.Command} {arguments}");
try
{
p.Start();
p.WaitForExit(processTimeout);
if (!p.HasExited)
{
Logger.Warn("Process was not finishing after {0} seconds, killing it now...", processTimeout.TotalSeconds);
p.Kill();
}
else
{
Successful = true;
}
}
catch (Exception ex)
{
Logger.Error(ex, "Exception during printing");
return false;
}
return Successful;
}