Adding PrintTicket support to a monolithic driver
I’ve been getting a number of questions lately about various aspects of adding support for PrintTicket to a monolithic driver. In response, I’ve put together a short ‘how to article that I hope you’ll find helpful. This walkthrough is basically geared towards getting a minimal implementation up and running, though there will be much more work involved in making a complete implementation.
First, you're going to need to provide implementations for a couple of COM-style classes. One class is the class factory, and the other is the class that actually implements the PrintTicket support. The class that implements the PrintTicket support should implement the interface IPrintTicketProvider. The class factory should implement support for IClassFactory. Second, you need a DLL method called DllGetClassObject that returns your class factory. The DllGetClassObject routine should return an instance of your class factory object in response to a query for CLSID_PTPROVIDER. Definitions for both IPrintTicketProvider and CLSID_PTPROVIDER can both be found in the Vista DDK in a file called 'prdrvcom.h'.
Your driver should not register the class in the Windows Registry as a COM server. All drivers are implementing the same class ID. The OS support for PrintTicket will directly query the driver's config DLL to obtain the interface. It will never attempt to access your provider implementation via CoCreateInstance. In fact, you don't even need a DllRegisterServer method.
When implementing the provider, you're going to need to support a few routines just to get through the PTOpenProvider API:
- GetSupportedVersions
- BindPrinter
- QueryDeviceNamespace
- GetPrintCapabilities (releases prior to Beta 2)
GetSupportedVersions
STDMETHODIMP
CPrintTicketProvider::
GetSupportedVersions(THIS_ HANDLE hPrinter,
INT *ppVersions[],
INT *pcVersions)
{
if ( (*ppVersions = (INT*)CoTaskMemAlloc(sizeof(INT))) != NULL)
{
(*ppVersions)[0] = 1; // Version 1
*pcVersions = 1; // 1 supported version
return S_OK;
}
else
return E_OUTOFMEMORY;
}
That's it. Because v.1 is the only version that exists for now, this implementation should work for any PrintTicket provider being written at this time.
BindPrinter
BindPrinter is a bit more involved. There are a number of parameters that the driver needs to handle.
STDMETHODIMP
CPrintTicketProvider::
BindPrinter( THIS_ HANDLE hPrinter,
INT version,
PSHIMOPTS pOptions,
DWORD *pDevModeFlags,
INT *pcNamespaces,
BSTR **ppNamespaces)
- hPrinter is a standard printer handle given to the provider. The provider can use if for the lifetime of the provider object, but the plug-in does not need to release the handle. The system will take care of that.
- Version indicates the version of PrintSchema support that the system is requesting. This should be one of the versions that the provider indicated support for in GetSupportedVersions. If it’s not, the provider should fail the bind call. In practical terms, this means if it's not '1', it's time to return failure.
- pOptions allows the provider to control behavior that the system will provider on behalf of the PrintTicketProvider object. At the time of writing, the only allowed option is to enable / disable support for encoding a base64 representation of the DEVMODE in the PrintTicket. If you're just starting your implementation, I recommend you leave this on. It'll retain any DEVMODE settings that you haven't added PrintTicket support for. Only turn this off if you're confident that everything that your driver can do is fully represented and configurable from a PrintTicket.
- pDevModeFlags is an out parameter that indicates which features in the public DEVMODE your PrintTicket provider has support for. The values are the same as the values used in the dmFields member of the DEVMODE structure. If your provider indicates support for a field, the shim will not provide any default conversion handling. If you support all of your DEVMODE handling in your PrintTicket code, just set this value to 0xFFFFFFFF to disable all of the shim'd default conversions.
- pcNamespaces – out parameter, set value to 0
- ppNamespaces – out parameter, set value to NULL
QueryDeviceNamespace
This routine provides the default namespace that the shim handling will use if it needs to put a feature or option in a driver-private namespace (such as the base64 encoding of the DEVMODE). For a discussion of how to pick a namespace, see my previous post here: https://blogs.msdn.com/benkuhn/archive/2005/12/29/508056.aspx
A typical implementation might look something like this:
STDMETHODIMP
CPrintTicketProvider::QueryDeviceNamespace(BSTR *pDefaultNamespace)
{
*pDefaultNamespace = SysAllocString(TEXT("https://schemas.fabrikam.com/printers/seriesA/v.1.0"));
if (!(*pDefaultNamespace))
{
return E_OUTOFMEMORY;
}
return S_OK;
}
GetPrintCapabilities
This routine is a bit harder to implement. You're going to need to return valid PrintCapabilities content. To get your provider up & running, it doesn't need to have much, but you won't be able to support any features in your PrintTicket that aren't exposed in the PrintCapabilities, so you'll need to keep coming back to this routing to add features to the capabilities document as you add features to other routines.
The details of implementing this routine are best saved for another article, at least for now. If you're running on Vista Beta 2, you won't see this method called during PTOpenProvider. However, on releases prior to Beta 2, you will need to implement this to get past the PTOpenProviderCall.
One important thing to make note of here is that the system does not populate anything in the PrintCapabilities. This means that even for featuers that the system converts between DEVMODE PrintTicket, the driver needs to supply the corresponding PrintCapabilities. The reason for this is that the shim's method of populating this data is to call the win32 DeviceCapabilities API, and it needs to do this many times, which can cause performance issues. The driver can implement the support internally with much more efficiency.
Comments
Anonymous
May 11, 2006
hmm, I'm using a private namespace other than ns0000 which I return in QueryDeviceNamespace but the system still places the devmode snapshot in the ticket with the ns0000 namespace. I'll have to check to see if my implementation of QueryDeviceNamespace is incorrect.Anonymous
June 02, 2008
i also have the doubt about the private namespace, i really return the private namespace in the QueryDeviceNameSpace method, but the devmode snapshot in the Print Ticket that system produced is still "ns0000", would you do me a favor to tell why?Anonymous
June 15, 2010
Hi Ben,In the BindPrinter method of our XPS Printer Driver's IPrintticketProvider, we had not been setting the *pDevmodeFlags to DM_SCALE, even though our dmFields member of the DEVMODE structure was set to DM_SCALE.Till date from 2006 onwards we have never had problems due to this, but now with Office 2010 released, we have a bug in Office 2010 Excel. In Excel 2010, when user selects "Fit Sheet to Page", the output is a print job of 1 page for each cell of the sheet, instead of just 1 page for 1 sheet.The bug got fixed when we set the *pDevmodeFlags to DM_SCALE.We would like to know:Why is there a difference in behavior in MS Office 2007 and MS Office 2010 in the way *pDevmodeFlags in the BindPrinter Method is being interpreted?Thanks and regards,MriduAnonymous
December 06, 2012
Hi, We are facing an issue with XPS printing on Windows 8. The issue is Windows spooler is failed to call DllGetClassObject(). Please help. Thanks, Kamal