BCL easyPDF SDK
easyPDF SDK Usermanual
PDF Creator Programming API  |  Download Free Trial  |  Contact Us to Purchase

Native .NET Printer API

Usage

The native .NET API is very similar to the COM API, with only a few distinct differences. .NET Framework 3.5 or above is required.

First, you need to add using BCL.easyPDF.Printer; to your code. Then you need to add reference to the appropriate .NET DLL.

If you are targeting .NET 4.x, you need to add a reference to the BCL.easyPDF.Printer.dll assembly, which is under C:\Program Files\Common Files\BCL Technologies\easyPDF 8.

If you are targeting .NET 3.5, you need to add a reference to the BCL.easyPDF.Printer-NET35.dll assembly, which is under C:\Program Files\Common Files\BCL Technologies\easyPDF 8.

If you are targeting .NET Core, you need to add a reference to the BCL.easyPDF.Printer.NetCore.dll assembly, which is under C:\Program Files\Common Files\BCL Technologies\easyPDF 8.

Currently there are no differences between the .NET 3.5 and 4.x APIs, other than the target version. They both implement the same features, classes, methods and properties. It is strongly recommended to use the .NET 4.x or the .NET Core version, unless your app must target .NET 3.5.

The .NET Core API does not support PrinterMonitor, please use PrintJobMonitor instead. Also, the .NET Core API does not support Impersonation, please use the Loader Service instead.

COM exceptions were replaced by the new PrinterException.

The Printer class is inherited from IDisposable, which means it really needs to be deterministically disposed. Relying on the garbage collector is not recommended, because each Printer object launches a separate worker process. Even though these worker processes are sleeping while not executing a function, they are still in the memory, and only really quit when the Printer object is disposed.

The Printer object should be treated as if it were an expensive resource, such as a file, mutex, or a database connection. If you would like to know what really is inside Printer, it is just a named pipe. However, the worker process is programmed to only quit when the pipe is closed.

If the customer's application crashes, the system automatically closes all pipes belonging to the process, which means all related worker processes automatically quit as well.

The minimal C# sample code looks like this:
(Note: all Native .NET sample code and declarations have a light yellow background color)

using(Printer printer = new Printer())
{
   printer.PrintJob.PrintOut(@"c:\test\input.doc", @"c:\test\output.pdf");
}

The key here is the using keyword, which gives Printer a deterministic behavior.

An alternative solution, especially if you are catching exceptions, is the try/finally idiom:

Printer printer = new Printer();
try
{
   printer.PrintJob.PrintOut(@"c:\temp\input.doc", @"c:\temp\output.pdf");
}
catch(PrinterException ex)
{
   Console.WriteLine(ex.Message);
}
finally
{
   printer.Dispose();
}

The key here is the finally block, which calls Dispose().

Printer's constructor is designed to never throw exceptions. That's because it does not launch a worker process and does not create a named pipe, it merely initializes a few variables to their default values. In other words, creating a Printer object is extremely lightweight, like creating a Color object.

However, as soon as you do anything else, even getting a property, it instantly launches a worker process.

Notification Events

Notification events in the native .NET API are slightly different than in the COM object. Here is an example that catches OnPageStart, which is called before printing each page:

using(Printer printer = new Printer())
{
   printer.PrintJobMonitor.OnPageStart += new PrintJobMonitor.OnPageStartEventHandler(OnPageStart);
   printer.PrintJob.PrintOut(@"c:\temp\input.doc", @"c:\temp\output.pdf");
}

You would then create your own handler method as follows:

prnMonitorResponse OnPageStart(int id, int pageNumber)
{
   Console.WriteLine(pageNumber);
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}

What's different here from COM is that event handlers return prnMonitorResponse, instead of int.

Launch Timeout

Launching the worker process and connecting the named pipe should happen very fast, in milliseconds. However, under an abnormally heavy load, the computer may not have enough cycles to perform this task in a timely fashion.

The Native .NET API has an internal timeout built-in. The default value is 1 minute. This is because a server may momentarily slow down so much that it is not responding for seconds, but it usually recovers after a while.

The default timeout value can be changed via the LaunchTimeout member (in milliseconds). This must be performed immediately after the creation of the Printer object.

using(Printer printer = new Printer())
{
   printer.LaunchTimeout = 30000; // 30 seconds
   printer.PrintJob.PrintOut(@"c:\temp\input.doc", @"c:\temp\output.pdf");
}

Upon timeout an exception is thrown, and the SDK cannot be used. Another attempt may be made later.

Server-Side Operation

Printing from the server side has always required easyPDF Loader, a special service that helps execute functions under a different user account.

The Native .NET SDK offers two possible solutions for server-side printing. One is called the Loader Service, and the other one is called Impersonation.

Customers who are printing from a web server or a system service should study both alternatives, and decide which one they prefer.

PrinterMonitor

PrinterMonitor is a desktop-only feature designed to enable traditional File > Print functionality in a desktop environment. It also supports notification callback events, and the output PDF files can be named programmatically.

Use PrintJobMonitor, instead of PrinterMonitor, to monitor a PrintJob.PrintOut function. PrinterMonitor is only for enabling interactive prints.

PrinterMonitor should never be used on the server side, or from a service. It is not compatible with Impersonation or the Loader Service.

Note that the .NET Core API does not support PrinterMonitor right now. Please use .NET 4.x instead.

You can start the printer monitor by referencing the printer.PrinterMonitor property. The printer monitor will only stop when the printer object is disposed. PrinterMonitor is not blocking, because it is running in another thread in an external process.

It is a good idea to specify the output file path before you begin printing:

using(Printer printer = new Printer())
{
   printer.PrinterMonitor.OutputFileName = @"c:\test\output.pdf";
   Console.WriteLine("You can begin printing now. When finished, press Enter to stop the printer monitor.")
   Console.ReadKey();
}

The same file name is used for all subsequent operations. If you don't explicitly set the file name, it is automatically chosen, usually based on the input file name. Be careful — even if you normally have access to a directory, the printing application may not.

You may also print programmatically after the printer monitor is started. However, it is preferred to use GenericPrintJob.PrintOut for all non-interactive printing.

You may choose to listen on print progress notification events. It takes a few extra lines to set that up:

static void Main(string[] args)
{
   using(Printer printer = new Printer())
   {
      printer.PrinterMonitor.OnPrinterInit += OnPrinterInit;
      printer.PrinterMonitor.OnPrinterUpdate += OnPrinterUpdate;
      printer.PrinterMonitor.OnPrinterStart += OnPrinterStart;
      printer.PrinterMonitor.OnPageStart += OnPageStart;
      printer.PrinterMonitor.OnPrinterEnd += OnPrinterEnd;
      Console.WriteLine("You can begin printing now. When finished, press Enter to stop the printer monitor.")
      Console.ReadKey();
   }
}
prnMonitorResponse OnPrinterInit(int uID, PrintJobInfo jobInfo)
{
   Console.WriteLine("Init");
   jobInfo.OutputFileName = @"c:\test\output.pdf";
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}
prnMonitorResponse OnPrinterUpdate(int uID, PrintJobInfo jobInfo)
{
   Console.WriteLine("Update");
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}
prnMonitorResponse OnPrinterStart(int uID)
{
   Console.WriteLine("Start");
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}
prnMonitorResponse OnPageStart(int id, int pageNumber)
{
   Console.Write("Page ");
   Console.WriteLine(pageNumber + 1);
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}
prnMonitorResponse OnPrinterEnd(int uID, int errCode)
{
   Console.WriteLine("End");
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}

The example above shows an alternative way of setting up the output file name.

If you want to pop up the Save dialog and let the user choose the output file name, you may add the following lines:

prnMonitorResponse OnPrinterInit(int uID, PrintJobInfo jobInfo)
{
   Console.WriteLine("Init");
   jobInfo.OutputFileName = @"c:\test\output.pdf";
   jobInfo.PDFSetting.UiAlerts = true;
   jobInfo.PDFSetting.UiFileDialog = true;
   jobInfo.PDFSetting.UiPropertiesDialog = true;
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}

If you specify OutputFileName, that's going to be the default. If you do not, the default file name is automatically generated based on the print job's name. Either way, the user will get a chance to override it.

Please note that callback notifications are coming from a worker thread, which is listening in the background. This worker thread is generally sleeping while there are no notifications. The thread starts when printer.PrinterMonitor is called, and it only stops when the printer object is explicitly disposed. Please do not rely on garbage collection alone; easyPDF requires deterministic destruction.

It is not wise to update the program's main GUI from those event handlers without proper thread synchronization. In Windows Forms, this means Form.Invoke(). In WPF, it means Window.Dispatcher.Invoke(). Writing to the console is natively synchronized.

Here is a Windows Forms example:

delegate void UpdateUIFromThreadDelegate(string status, string fname);
void UpdateUIFromThread(string status, string fname) // call this from thread to update the UI in a safe way
{
   if(this.InvokeRequired)
      this.Invoke(new UpdateUIFromThreadDelegate(UpdateUI), new object[] { status, fname });
   else
      UpdateUI(status, fname);
}
void UpdateUI(string status, string fname) // synchronized event handler to update the UI in a safe way
{
   if(status != null)
      labelStatus.Text = status;
   if(fname != null)
      labelFileName.Text = fname;
}
private prnMonitorResponse printerMonitor_OnPrinterStart(int uID)
{
   UpdateUIFromThread(uID.ToString() + " : Printing started", null);
   return prnMonitorResponse.PRN_MON_CONTINUE_CONVERSION;
}

If you tried to update the label directly from printerMonitor_OnPrinterStart, your application would crash. Using UpdateUIFromThread ensures that the UI update is synchronized via Invoke.

The WPF implementation is almost identical, except replace if(this.InvokeRequired) with if(!this.CheckAccess()), and replace this.Invoke with this.Dispatcher.Invoke.