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
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; }
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)
BindPrinter( THIS_ HANDLE hPrinter,
INT version,
PSHIMOPTS pOptions,
DWORD *pDevModeFlags,
INT *pcNamespaces,
BSTR **ppNamespaces)
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: http://blogs.msdn.com/benkuhn/archive/2005/12/29/508056.aspx
A typical implementation might look something like this:
STDMETHODIMPCPrintTicketProvider::QueryDeviceNamespace(BSTR *pDefaultNamespace){ *pDefaultNamespace = SysAllocString(TEXT("http://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.
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?
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,
Mridu