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

Native C Printer API

Getting Help

The C API is inherently very large and obscure. However, the objects and the methods are all similar to the C# and COM APIs.

If you are just starting with easyPDF SDK, consider using C#, VB, PHP or Python for your initial testing and prototyping. You will be up to speed much quicker.

If you are ready to dive into C/C++, we recommend that you study the header file located at c:\Program Files\Common Files\BCL Technologies\easyPDF 8\include\easyPDFPrinter.h.

If you have any questions, please don't hesitate to submit a support ticket.

Usage

The native C API is very similar to the COM API, with a few distinct differences.

Objects are implemented as C structures, and member functions and properties are achieved via function pointers inside each structure. This allows for a syntax that is very close to C++ classes.

According to our naming convention, all easyPDF macros and enumerations begin with BCL_, and all global functions and types begin with Bcl. Therefore the Printer class is really called BclPrinter.

easyPDFPrinter.dll exports only a single function called BclNewPrinter, which is responsible for creating a new BclPrinter object. All other functions and properties are called via the BclPrinter object.

The BclNewPrinter function has the following prototype:

struct BclPrinter* BclNewPrinter(const BclLoaderSettings* init);

The input argument is a pointer to the Loader settings. Feel free to pass a NULL pointer for the default settings, unless your application is a server or a service, which we will discuss separately.

The pointer returned by BclNewPrinter must be explicitly deleted using the Dispose method.

We prefer to wrap BclNewPrinter into CreatePrinter, like this:

// Create a new BclPrinter object
// hDll: [in] handle returned by LoadEasyPDFPrinterDLL
// returns a new BclPrinter pointer. Result must be deleted using pPrinter->Dispose(pPrinter);
static BclPrinter* CreatePrinter(HMODULE hDll)
{
   if(hDll)
   {
      typedef BclPrinter* (*BclNewPrinterPtr)(const BclLoaderSettings*);
      BclNewPrinterPtr ptrBclNewPrinter = (BclNewPrinterPtr)GetProcAddress(hDll, "BclNewPrinter");
      return ptrBclNewPrinter(NULL);
   }
   else
      return NULL;
}

It is now very easy to use the Printer SDK:

BclPrinter* pPrinter = CreatePrinter(hPrinterDll);
if(!pPrinter)
   goto Error;
...
pPrinter->Dispose(pPrinter);

You may call a method just like in C++, except the first argument is always the pPrinter pointer itself. pPrinter->Dispose is a function pointer to the method, and the first parameter in nearly every method is the BclPrinter pointer. This is similar to self in Python, and this in PHP, C# and C++. Even in C++, you must explicitly pass this parameter. In other words, a method that has no parameters still has a single argument, the pPrinter pointer.

Properties are accessed via getter and setter functions. For example, here is how to get the LibraryVersionMajor property:

int versionMajor;
BclPrnResult error = pPrinter->GetLibraryVersionMajor(pPrinter, &versionMajor);
if(error)
   goto Error;

Almost every method returns an error result type called BclPrnResult. The actual return value of the function is retrieved via a pointer, just like in COM.

Error handling must be implemented for each individual method call. If there is no error, the return value is BCL_PRN_R_SUCCESS, which has a value of 0. All errors have a negative value.

Note that a few properties are implemented as member variables, as opposed to member functions. Most importantly, all the PrintJob pointers are plain old pointers, so are BclPrinter::PrinterSetting, BclPrintJob::PDFSetting and BclPrinter::PrintJobMonitor.

For example, here is how to obtain the PrintJob from Printer:

BclPrintJob* pPrintJob = pPrinter->PrintJob;

In other words, these member classes are always available, because they are automatically allocated by BclNewPrinter.

Now that we have a PrintJob pointer, we can set the FileConversionTimeout property:

error = pPrintJob->SetFileConversionTimeout(pPrinter, 60000); // 60 seconds
if(error)
   goto Error;

Note how we are passing the pPrinter even to PrintJob methods.

And finally, we are ready to perform the actual printing:

error = pPrintJob->PrintOut(pPrinter, L"c:\\test\\input.doc", L"c:\\test\\output.pdf");
if(error)
   goto Error;

If you are programming in C++, feel free to translate errors into your own exceptions:

if(error)
   throw std::runtime_error(pPrinter->PrnResultToAscii(error));

PrnResultToAscii and PrnResultToString are two of the few functions that don't follow the regular method calling convention. They take a single BclPrnResult input, and return a human-readable error message. PrnResultToAscii returns const char*, while PrnResultToString returns const wchar_t*. Never deallocate these strings, because they are static constants!

Memory Management

With the exception of the BclPrinter pointer returned by BclNewPrinter, memory management is completely automatic. You are not required to manually deallocate any pointer returned by the SDK, with the exception of pPrinter itself, whose Dispose method must be called explicitly. Failure to properly dispose Printer will cause the SDK to stay in memory indefinitely, until your application quits. This can be a huge burden, especially when BclNewPrinter is called repeatedly without properly disposing of the pointer.

The good news is that easyPDF internally keeps track of all pointers that it allocates, and Dispose automatically takes care of all the deallocations. Most people will never have to worry about deallocating individual pointers other than pPrinter.

There are two notable exceptions:

  1. If you are calling a very large number of functions that return strings or memory buffers, without calling Dispose in between.
  2. If you are calling PrintOut2 or PrintOut3, which return potentially very large memory buffers, without calling Dispose shortly thereafter.

Strings and memory streams allocated by the SDK may be explicitly deallocated using the BclPrinter::ReleasePtr method, which has the following prototype:

void ReleasePtr(struct BclPrinter* pPrinter, void* p);

The first parameter is the pPrinter that was used to allocate the string or buffer. The second parameter is the pointer to be deallocated. If you have multiple pPrinter pointers alive, it is essential to pass the one which did the allocation, because each BclPrinter object has a completely independent memory manager. Whatever memory was not deallocated via ReleasePtr will be deallocated by Dispose. Obviously, after the Dispose method is called, all related SDK pointers become invalid and unusable.

Never call ReleasePtr on pPrinter itself! pPrinter must be deallocated using Dispose, all other strings and buffer must be deallocated using ReleasePtr, or not deallocated at all. Never call free(), delete, VirtualFree or other operating system functions on SDK pointers!

Also note that all pointers returned by the SDK point to constant memory and must not be modified. For example, strings returned are constant. If you need to modify them, make your own copy, and apply the modifications there.

C++ Exception Safety

If you are using the C API from C++, it is wise to wrap BclPrinter pointers into your own C++ smart pointer class. We recommend the following implementation:

class SafePrinter
{
public:
   explicit SafePrinter(BclPrinter* a_pPrinter) : pPrinter(a_pPrinter) { }
   ~SafePrinter() { if(pPrinter) pPrinter->Dispose(pPrinter); }
   operator BclPrinter*() const { return pPrinter; }
   BclPrinter* operator->() const { return pPrinter; }
private:
   BclPrinter* pPrinter;
   SafePrinter(const SafePrinter&); // copy disabled!
   SafePrinter& operator=(const SafePrinter&); // copy disabled!
};

Here is how you would use this:

SafePrinter pPrinter(CreatePrinter(hPrinterDll));
if(pPrinter == NULL)
   throw std::runtime_error("Could not create easyPDF Printer");
BclPrnResult error = pPrinter->PrintJob->PrintOut(pPrinter, L"c:\\test\\input.doc", L"c:\\test\\output.pdf");

Using SafePrinter means you are not calling Dispose manually. It will be called automatically when your local variable goes out of scope, even if an exception occurs.

This implementation has no reference counting, therefore it can only be used in a local scope. Do not use standard smart pointers, such as std::auto_ptr or std::shared_ptr, because they would attempt to call delete instead of Dispose on the pointer, which would cause an instant crash. Similarly, do not use standard smart pointers for any other easyPDF pointer.

Pointer Casting

The Printer SDK heavily uses inheritance. For example, BclPrintJob is the base class for all other print jobs, such as BclWordPrintJob, BclIEPrintJob, BclImagePrintJob, etc. Also, BclPrinterSetting is inherited from BclPDFSetting.

Although C does not support inheritance and virtual functions on the language level, it is pretty easy to get the same effect using function pointers. The only problem is when you need to cast from a specific print job to the base BclPrintJob. Technically you could use C-style casting, but we cannot guarantee that it will work in all situations.

Instead, we have introduced special members that you can use to safely cast from a specific print job into the base BclPrintJob. It is called CastToPrintJob. Here is how to use it:

BclPrintJob* pPrintJob = NULL;
BclPrnPrintJobType printJobType = BCL_PRN_PRNJOB_NONE;
pPrinter->GetPrintJobTypeOf(pPrinter, inputFileName, &printJobType);

if(printJobType == BCL_PRN_PRNJOB_WORD)
{
   /* Word-specific settings */
   BclWordPrintJob* pWordPrintJob = pPrinter->WordPrintJob;
   pWordPrintJob->SetConvertHyperlinks(pPrinter, BCL_TRUE);
   pPrintJob = pWordPrintJob->CastToPrintJob;
}
else
{
   /* Fall back to the basic PrintJob */
   pPrintJob = pPrinter->PrintJob;
}

/* Regardless of which print job it is, PrintOut can be called on it */
BclPrnResult prnResult = pPrintJob->PrintOut(pPrinter, inputFileName, outputFileName);

Note that CastToPrintJob is not a function, but a pointer to a variable, which happens to be the base class.

Always use GetPrintJobTypeOf in order to figure out which print job is best for your specific file type. Sometimes you can override the automatic decision, but it gives you a good idea about the file type. This function not only checks the file extension, but sometimes it even reads the first few bytes of the file in order to determine its real type. For example, an .xml file may be a Word document, an Excel document, a PowerPoint document, or just a plain simple .xml file. From the extension alone you wouldn't be able to tell.

If the type is BCL_PRN_PRNJOB_WORD, you may use WordPrintJob or WordPrintJobEx. If the type is BCL_PRN_PRNJOB_IE, you may use IEPrintJob or HTMLPrintJob, it is up to you.

Always use the basic PrintJob as a fallback mechanism, because it has smart logic built in that can handle every file that is printable. Note that some files, such as .zip, .exe, .dll, should never be printed.

Once you cast from your specific print jobs into a base pointer, you can call any of the base functions, such as PrintOut. Rest assued that always the correct PrintOut is going to be called, as if C had virtual functions.

You would get the same behavior if you used C-style casts, but CastToPrintJob is more future-proof.

It is worth mentioning that BclPrinterSetting has a CastToPDFSetting member. If you want to write a common function that can set both your printer settings and your PDF settings, you can use casting, instead of code duplication.

Avoid casting from the base pointer to a specific pointer. If you need to have a pointer that encapsulates all print jobs in one, that is called BclPrinter*. There is no reason why you would want to cast from BclPrintJob to BclWordPrintJob. The proper way of doing it is simply pPrinter->WordPrintJob.

Notification Events

Notification callbacks are implemented via function pointers:

typedef BclPrnMonitorResponse (*BclPrintJobMonitor_OnPrinterInitPtr)(void* CallbackUser, int uID, PrintJobInfo* info);
typedef BclPrnMonitorResponse (*BclPrintJobMonitor_OnPrinterUpdatePtr)(void* CallbackUser, int uID, PrintJobInfo* info);
typedef BclPrnMonitorResponse (*BclPrintJobMonitor_OnPrinterStartPtr)(void* CallbackUser, int uID);
typedef BclPrnMonitorResponse (*BclPrintJobMonitor_OnPageStartPtr)(void* CallbackUser, int uID, int pageNumber);
typedef BclPrnMonitorResponse (*BclPrintJobMonitor_OnPrinterEndPtr)(void* CallbackUser, int uID, BclPrnPrinterResult result);

The first parameter is always a void* pointer, where you can pass your own data to the callback.

Here is how you would set up printing with callback:

pPrinter->PrintJobMonitor->CallbackUser = CallbackUser;
pPrinter->PrintJobMonitor->OnPageStart = UserPageStart;
pPrinter->PrintJob->PrintOut(pPrinter, L"c:\\test\\input.doc", L"c:\\test\\output.pdf");

Then you would create a global function to handle the callback:

// Per-page printer callback
BclPrnMonitorResponse UserPageStart(void* CallbackUser, int uID, int pageNumber)
{
        printf("Starting page %d...\n", pageNumber + 1);
        return BCL_PRN_MON_CONTINUE_CONVERSION;
}

CallbackUser is usually a pointer to the customer's own object. This is the only way for the callback function to reference the main application object in a thread-safe way. In extremely simple cases, like the example above, CallbackUser can be NULL.

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 C 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 BclLoaderSettings::LaunchTimeout (in milliseconds). This can be done by passing BclLoaderSettings to BclNewPrinter:

BclLoaderSettings loaderSettings = { 0 };
loaderSettings.LaunchTimeout = 60000; // 1 minute
BclPrinter* printer = BclNewPrinter(&loaderSettings);

loaderSettings cannot be changed after pPrinter is created.

Upon timeout, BclNewPrinter returns a NULL pointer, 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 C 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.

By default, the Loader Service is used automatically when easyPDF Printer deems necessary. However, the customer is still responsible for setting up and configuring the Loader Service properly.

To use Impersonation, create a BclLoaderSettings object, set UserName and Password, then pass it to BclNewPrinter:

BclLoaderSettings loaderSettings = { 0 };
loaderSettings.UserName = L"easyPDF8User";
loaderSettings.Password = L"your actual password here";
BclPrinter* pPrinter = BclNewPrinter(&loaderSettings);

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.

You can start the printer monitor by calling any one of PrinterMonitor's methods, such as SetOutputFileName or Listen. 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:

BclPrinter* pPrinter = CreatePrinter(hPrinterDll);
if(pPrinter)
{
   pPrinter->PrinterMonitor->SetOutputFileName(pPrinter, L"c:\\test\\output.pdf");
   printf("You can begin printing now. When finished, press Enter to stop the printer monitor.\n")
   _getch();
   pPrinter->Dispose(pPrinter);
}

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:

BclPrnMonitorResponse PrinterInit(void* CallbackUser, int uID, BclPrintJobInfo* info)
{
   printf("Init\n");
        wcscpy_s(info->OutputFileName, L"c:\\test\\output.pdf");
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}

BclPrnMonitorResponse PrinterUpdate(void* CallbackUser, int uID, BclPrintJobInfo* info)
{
   printf("Update\n");
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}

BclPrnMonitorResponse PrinterStart(void* CallbackUser, int uID)
{
   printf("Start\n");
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}

BclPrnMonitorResponse PrinterPageStart(void* CallbackUser, int uID, int pageNumber)
{
   printf("Page %d...\n", pageNumber + 1);
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}

BclPrnMonitorResponse PrinterEnd(void* CallbackUser, int uID, BclPrnPrinterResult result)
{
   printf("End\n");
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}

int main()
{
   HMODULE hPrinterDll = LoadLibraryW(L"C:\\Program Files\\Common Files\\BCL Technologies\\easyPDF 8\\easyPDFPrinter.dll");
   if(hPrinterDll)
   {
      BclPrinter* pPrinter = CreatePrinter(hPrinterDll);
      if(pPrinter)
      {
         pPrinter->PrinterMonitor->OnPrinterInit = PrinterInit;
         pPrinter->PrinterMonitor->OnPrinterUpdate = PrinterUpdate;
         pPrinter->PrinterMonitor->OnPrinterStart = PrinterStart;
         pPrinter->PrinterMonitor->OnPageStart = PrinterPageStart;
         pPrinter->PrinterMonitor->OnPrinterEnd = PrinterEnd;
         pPrinter->PrinterMonitor->Listen(pPrinter);
         printf("You can begin printing now. When finished, press Enter to stop the printer monitor.\n")
         _getch();
         pPrinter->Dispose(pPrinter);
      }
      FreeLibrary(hPrinterDll);
   }
   return 0;
}

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:

BclPrnMonitorResponse PrinterInit(void* CallbackUser, int uID, BclPrintJobInfo* info)
{
   printf("Init\n");
   info->OutputFileName = L"c:\\test\\output.pdf";
   info->PDFSetting->UiAlerts = BCL_TRUE;
   info->PDFSetting->UiFileDialog = BCL_TRUE;
   info->PDFSetting->UiPropertiesDialog = BCL_TRUE;
   return BCL_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 in order to get callback notifications, you must explicitly call the Listen method.

Also 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. You may use SendMessage from the callbacks to send window messages to the main application as a form of synchronization. Writing to the console is natively synchronized.

Here is an MFC example:

BclPrnMonitorResponse PageStart(void* CallbackUser, int uID, BclPrintJobInfo* info)
{
   CMyDialog* dialog = static_cast<CMyDialog*>(CallbackUser);
   CString msg;
   msg.Format(L"%u: Printing page %u", uID, (PageNumber + 1));
   dialog->GetDlgItem(IDC_STATIC_STATUS)->SetWindowText(msg); // update the label's text by sending it a WM_SETTEXT message
   return BCL_PRN_MON_CONTINUE_CONVERSION;
}