当前位置: 首页 > 工具软件 > PDFCreator > 使用案例 >

PDFCreator源码分析

翟曦
2023-12-01

PDFCreator是一个半开源的PDF虚拟打印机,其核心使用Ghostscript程序将转化为PDF或图片。
一、工程介绍:
工程分得很细,光文件夹就有Conversion、Core、Edisions、Startup

分类工程说明
ConversionITextProcessingiTextSharp生成PDF的接口
ConversionPdfProcessingIterface接口
ConversionActions
ConversionActionsInterface接口
ConversionDropbox
ConversionGhostscript
ConversionJobs
ConversionSettings
CoreComplementation
CoreCommunication
CoreController
CoreDirectConversion
CoreGpoAdapter
CoreJobInfoQueue打印任务对列
CorePrinting
CoreServiceLocator
CoreServices
CoreSettingsManagement
CoreUsagStatistics
CoreWorkflow
EditionsEditionBase
EditionsPDFCreator
StartupStartup
StartupStartupInterface
UICOM
UIComWrapper
UIIneractions
UIPresentation
UIPrismHelper
UIRssFeed
SetupSetupHelper
一、 使用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;
        }

二、打印的过程(以记事本打印为例)

  1. Windows系统使用PScript5.dll将记事本转化为ps文件(可能是由打印驱指定的? 已证实)
    生成目录:
    C:\Users\xxUser\AppData\Local\Temp\PDFCreator\Spool
    目录下会生成两个文件,一个info扩展名,一个是扩展名,前者只存生成的信息,后者是打印的数据
[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

  1. 执行ps命令
  2. 复志文件到打印指定的目录
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;
        }
 类似资料: