Programming Dynamic Job Properties Under OS/2
Written by Jason Koeninger
Since writing my first OS/2 application that required printing in late 1996, I have been fielding questions about how to change print job properties in an OS/2 application without displaying the job properties dialog. The reason I watch the news groups for these questions is that I once asked the question and was told it was not possible. IBM (Solution Developer Program Membership required for link) even states in no uncertain terms in their OS/2 FAQs and HATs that "Because the orientation job properties are kept in the printer driver's data areas and because all printer drivers do not conform to a standard data structure for maintaining this information, this property is not accessible for modification by OS/2 APIs without a user dialog." What IBM doesn't say on their sites or in their API documentation is that there is a whole set of printer escapes built to do exactly what IBM says you cannot do. They are called Dynamic Job Properties, and they are so important that IBM gave them their own header file, pmdjp.h.
The only documented way to change the job properties without a user dialog is to use Open32. After failing to find a solution in OS/2's APIs, I consulted the Open32 documentation for a solution. What I found out was that the Win32 APIs required to change job properties the way you have always been able to on Windows were implemented in Open32 and no deviations from the Win32 behavior were identified. Though I have not done this myself, you should be able to write a normal Windows-like print routine using Open32 and change job properties on a per page basis.
Open32 may be fine now, but a little over a year ago when I was looking into this, Open32 was much slower and much more crash prone. So, I wanted an OS/2 solution. Since all documentation of Open32 that I've read states that it uses the OS/2 API to implement the Open32 functions, I figured the ability to change properties without the dialog had to be in the API.
Eventually, I tracked down mentions of Dynamic Job Properties within the device driver developer's kit. They are documented in the Presentation Driver Reference, not the Printer Driver Reference. The documentation, however, is geared to device driver developers, not application developers. This makes the task of using Dynamic Job Properties somewhat difficult but at least manageable. With the most current Warp 4.0 developer's toolkit and the Presentation Device Driver Reference, I was able to implement a simple application that prints 3 pages, alternating landscape and portrait between pages.
Note: This is not meant as an introductory text on programming printing for OS/2. It is assumed that the reader is familiar with OS/2 print programming.
Dynamic Job Properties Programming
The great thing about the way IBM implemented Dynamic Job Properties (DJP) is that you don't have to learn any new APIs. You access the functions of DJP through the existing DevEscape API. DJP adds the following escapes:
This document will not discuss DEVESC_DEFAULTJOBPROPERTIES or DEVESC_QUERYJOBPROPERTIES, but it will discuss the other escapes. DEVESC_QUERYJOBPROPERTIES is, however, called in the sample program for reference.
The anatomy of a program that implements a print job with DJP is fairly simple:
- SplQueryQueue - Get printer information including job properties.
- DevOpenDC - Open a device context for the printer.
- GpiCreatePS - Associate a presentation space with the device context.
- DJP_ITEM - Set up a list of DJP_ITEM structures that contains one DJP_ITEM structure for each property you need to change.
- DEVESC_SETJOBPROPERTIES - Call DevEscape with the set job properties escape, the DJP_ITEM list, and the job properties (often referred to as driver data) to take properties from the DJP_ITEM list and store them in the job properties buffer.
- DEVESC_STARTDOC_WPROP - Call DevEscape with the start document with properties escape to start the document and change the job properties that were set in the previous call.
- DEVESC_SETJOBPROPERTIES - Make changes for the next page.
- DEVESC_NEWFRAME_WPROP - Create a new page and change the job properties.
- Repeat the previous two steps until the document is complete.
- GpiDestroyPS - Destory the presentation space.
- DevCloseDC - Close the printer device context.
The DJP_ITEM structure makes everything work. Whereas the job properties (driver data) have always been stored in a vendor specific way under OS/2, the DJP_ITEM structure and its contents are standardized. The basic idea is that the programmer creates DJP_ITEM structures that have the job properties desired, and the presentation driver changes the vendor specific driver data appropriately. Once the driver data has been changed, it can be passed to the _WPROP escapes to change the job properties.
The sample program prints 3 pages using these methods. It prints page 1 landscape, page 2 portrait, and page 3 landscape. The first thing required to change page orientation is to set up the appropriate list of DJP_ITEM structures. The following code creates the DJP_ITEM list and initializes it for a call to DEVESC_SETJOBPROPERTIES:
DJP_ITEM djp[ 2 ]; //one for landscape, one to terminate //Standard OS/2 sizeof in structure's first element djp[ 0 ].cb = sizeof( DJP_ITEM ); //Property id for page orientation. Find others in pmdjp.h djp[ 0 ].ulProperty = DJP_SJ_ORIENTATION; //Not sure about the lType member. May be to tell the driver to change //for the current job only or for all jobs. Current would seem to be //what most any application would use. djp[ 0 ].lType = DJP_CURRENT; //Used when querying job properties, not 100% sure of its value. djp[ 0 ].ulNumReturned = 0; //Value for a landscape page orientation djp[ 0 ].ulValue = DJP_ORI_LANDSCAPE; djp[ 1 ].cb = sizeof( DJP_ITEM ); //Driver reads these as a list. DJP_NONE property terminates the list. djp[ 1 ].ulProperty = DJP_NONE; djp[ 1 ].lType = DJP_CURRENT; djp[ 1 ].ulNumReturned = 0; djp[ 1 ].ulValue = 0;
As the code shows, the DJP_ITEM structure is fairly simple for page orientation. Just fill in the appropriate property and value, and you can change the page orientation. Make sure you do not forget the extra DJP_ITEM structure at the end of the list with DJP_NONE for the property. Without this, your application will likely get stuck in an infinite loop.
Some of the other properties of the DJP_ITEM structure have complex data types. ulValue, however, is an unsigned long. Based on reading from the device driver development kit and the header files, I am not positive how these structures are to be allocated and what value is to be put in ulValue.
Once you have the DJP_ITEM structures in place, it's time to make the changes to the driver data. Do this by calling DEVESC_SETJOBPROPERTIES as follows:
rc = DevEscape( hdc, DEVESC_SETJOBPROPERTIES, 2*sizeof( DJP_ITEM ), (PBYTE)djp, //It's possible this call could change the size of pDriverData. If so, you could //get an error for the buffer not being large enough on some drivers. &jobpropSize, (PBYTE)prq6->pDriverData );
As with any other call, you have input and output buffers. For setting the job properties, the input buffer is the list of DJP_ITEM structures and the output is the driver data. In the sample program, I assumed that this call would not change the size of the driver data. That may or (most likely) may not be accurate. You can catch too small a buffer by looking for a DEVESC_ERROR return and checking WinGetLastError for the appropriate "buffer too small" error message.
So far, you haven't changed anything except buffers within your own program. Once you get the driver data changed the way you want, you have to call one of the _WPROP functions to activate it. At the start of a print job, you call DEVESC_STARTDOC_WPROP if you want to change the job properties. This call looks like:
DevEscape( hdc, DEVESC_STARTDOC_WPROP, strlen( jobName ), jobName, &jobpropSize, (PBYTE)prq6->pDriverData );
The strange thing about this call is that the driver data, which is really sent to the driver, is passed as the output buffer. Most likely, IBM was trying to maintain some backwards compatibility by keeping the job name in the input buffer. Whatever the case may be, it works fine the way they have done it as long as you do not get confused about it being sent in through the output buffer.
If, during a document, you need to change the orientation or some other property, the application should call DevEscape with the DEVESC_SETJOBPROPERTIES escape and appropriate DJP_ITEM list as shown above and call DevEscape with DEVESC_NEWFRAME_WPROP to implement those changes. For example:
DevEscape( hdc, DEVESC_NEWFRAME_WPROP, 0L, NULL, &jobpropSize, (PBYTE)prq6->pDriverData );
That's all there is to it. A call to DevEscape with DEVESC_ENDDOC closes the print job and sends it on to the printer. You can download the example program to see Dynamic Job Properties work. A command file to build the source is included for VisualAge.
For the simple case of changing the paper orientation, using these undocumented features makes programming a lot easier than rotating the text manually or including a section of Open32 code with OS/2 code. Unfortunately, there is a small problem that I encountered during writing the example that may prevent more expanded use of the DJP features.
Typically, I use TWIPS as units when doing any printing. The main reason is that they are big (1440 TWIPS/inch) and do not cause as much rounding error, and they are easily converted to points. As you may notice in the example, the presentation space is created with TWIPS as coordinates, but what you will not see is that after calling any _WPROP escape, the units are changed to PELS. The result is that to use DJP in its current form, I will have to adjust my printing class library to work with PELS in addition to TWIPS.
There may be a simple solution for this problem, but I did not find one. If you know of a solution to this problem or if you write a DJP program and find out more information on the API, please let us know.
Good luck with Dynamic Job Properties and feel free to let us know how the example program works with your printer. You will likely need OS/2 Warp 4.0 fixpak 1 and a printer driver that implements Dynamic Job Properties to run the example.