Skip to content

Hacker’s Corner: Driving the MS Daisy-wheel

Vector 12:1, page 114

system interfaces user interfaces

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 114

This article contains the listing of all the Dyalog APL/W code needed to:

  • Set the orientation of the default printer. (Global to all Windows applications) Function SetGlobalOrientation;
  • Change the orientation within a single print job. (Local to the current printer) Function SetLocalOrientation;
  • Examine the capabilities of the selected printer driver. Function ShowDeviceMode.

All of these tasks are performed by calls (via ⎕NA) to Windows DLLs. The main function called is ExtDeviceMode. Calling Dynamic Link Libraries from Dyalog APL/W has been well documented in these pages in the past [1][2][3][4][5], so I will not go into much detail.

To standardize the interface between Windows and any printer attached, each printer manufacturer has to provide a piece of software (called a printer driver) which converts the Windows device-independent printer controls into the specific commands needed for their printer. Because capabilities of printers vary, a secondary task of the driver is to (on request) supply Windows with information about the capabilities of the printer. The structure used to pass this "control" data (as against "print" data, e.g. text or bitmap) between Windows and the driver is termed a DeviceMode. The function in the printer driver which utilises the DeviceMode is called ExtDeviceMode. At the Windows end, the information is used by the Device Context created for the printer.

A device context (DC) is a data structure created and maintained by the windows GUI in support of device-independent drawing operations on displays, printers, metafiles, and bitmaps. A DC is not only a toolbox containing a set of drawing tools, it’s also the permission slip to use a device and provides the connection over which "print" data (as against control data) is passed.

The Windows call to create a DC for a printer requires the DeviceMode supplied by the printer driver.

The three things ExtDeviceMode is called on to do in the APL function SetGlobalOrientation are:

  1. Return the size of the DeviceMode structure required by the printer driver. This is then used to allocate memory for a DeviceMode structure.
  2. Read DeviceMode information from the printer driver. The contents of DeviceMode are then modified as required to change the orientation.
  3. Write DeviceMode to the printer driver. The printer is now set up for the amended orientation.

Since a change is made to the Printer Driver, the change can be "seen" by any and all applications using the driver. That is, the change is Global to Windows. Also Windows will remember the change even over a re-boot of the PC.

In SetLocalOrientation, steps 1 and 2 are the same, but in step 3 rather than writing the DeviceMode to the printer driver, it is used to replace the DC being used by the named printer object (via ResetDC). Thus the global environment is unchanged, but the orientation is changed for the named printer.

The function ShowDeviceMode displays the contents of the DeviceMode obtained from the printer driver. It shows which capabilities the printer driver supports (and which ones it does not).

For documentation on the (Windows) functions called, can I refer you to Microsoft’s Windows 3.1 System Developers Kit [6] (available from many sources in many guises, e.g. books, on CD, via Compuserve, on disc with language packages such as Visual Basic or Borland C++). The documentation I needed to get these functions working ran to over 20 A4 pages so cannot be reproduced within the limited pages of Vector. A copy of the workspace has been lodged in the APL section of the Portable Programming Library of Compuserve, and an earlier form of the WS is being distributed with the latest version of Dyalog APL/W (version 7.1).

Disclaimer. Making calls to Windows DLLs from APL with incorrect arguments or under the wrong conditions could crash Dyalog APL or Windows (or other applications running under Windows) with a subsequent loss of data. Neither I, nor Vector, the BAA, or Dyadic Systems, can be held responsible for any such loss or damage however caused through the use of this code.

Not withstanding the above disclaimer, during the development of this code I made many incorrect calls to DLLs and did not crash Windows (or APL) once. So, just don’t test your implementation of this code while running other critical software, and don’t forget to )SAVE often.

Appendix: The Functions

 ShowDeviceMode;gmh;gps;str;etxt;name;device;port;s;edm;args;fm;hand1;hand2;
   lpdmin;lpdmout;rc;sdm
⍝function to Show the current (default) printer driver settings
⍝fix fns to get other info
 'gmh'⎕NA'I4 kernel.exe.P16|GetModuleHandle <0T'
 'gps'⎕NA'I kernel.P16|GetProfileString <0T <0T <0T >0T I'
⍝get name of (printer) device from WIN.INI
 str←',',⊃1↓gps'windows' 'device' '(undefined)' 256 255
 name device port←3↑1↓¨(',' =str)⊂str
 etxt←'No default printer defined'
 →(name≡'(undefined)')/err
⍝set up arg string for ExtDeviceMode
 s←''
 s,←' I2'⍝  hwnd        ←→2 byte integer
 s,←' I4'⍝  hdriver     ←→4 byte integer
 s,←' I4'⍝  lpdmout     ←→4 byte integer (long pointer to output buffer)
 s,←' <0T'⍝ lpszdevice ←→pointer to char string
 s,←' <0T'⍝ lpszport   ←→pointer to char string
 s,←' I4'⍝  lpdmin      ←→4 byte integer (long pointer to input buffer)
 s,←' I4'⍝  lpszprofile←→pointer to char string or NULL (win.ini)
 s,←' I2'⍝  fwmode      ←→2 byte integer (word)
⍝fix external function call to ExtDeviceMode
 'edm'⎕NA'I2 ',device,'.drv.P16|ExtDeviceMode ',s
⍝set up arguments to ExtDeviceMode
 args←8⍴0
 args[1]←0 ⍝  ⊂gethandle'fm' ⍝Should be the handle of the parent form
 args[2]←⊂gmh⊂device
 args[3]←⊂0 ⍝     lpdmout
 args[4]←⊂name  ⍝ device name
 args[5]←⊂port  ⍝ port name
 args[6]←⊂0 ⍝     lpdmin
 args[7]←⊂0 ⍝ 'win.ini'
 args[8]←⊂0 ⍝ fwMode ≡ read size of buffers

⍝Read size of buffer required by printer driver
 sdm←edm args

⍝allocate space for buffer
 lpdmout←GlobalLock hand1←GlobalAlloc sdm

⍝set up output buffer  and request to read data into it
 args[3]←⊂lpdmout
 args[8]←⊂2 ⍝ fwmode ≡ DM_OUT_BUFFER ≡2
 rc←edm args

⍝show contents of buffers
 'Current printer driver settings (Global)'showdevmode lpdmout
 GlobalUnlock hand1
 GlobalFree hand1
 →0
err:etxt
 {R}←SetGlobalOrientation lorp;prname;putd;puti;alloc;free;lock;unlock;rdc;
  hpr;gmh;gps;str;name;device;port;s;edm;args;fm;hand1;hand2;lpdmin;lpdmout;
  sdm;rc;old;bin;geti
⍝set printer orientation (GLOBAL)
⍝lorp←→1≡Portrait 2≡Landscape
⍝returns old orientation

⍝Fix external functions (calls to DLLs)
 'gmh'⎕NA'I4 kernel.exe.P16|GetModuleHandle <0T'
 'gps'⎕NA'I kernel.P16|GetProfileString <0T <0T <0T >0T I'
 'rdc'⎕NA'I2 gdi.dll.P16|ResetDC  I2 I4'
 'alloc'⎕NA'U2 kernel.exe.P16|GlobalAlloc U2 U4'
 'lock'⎕NA'I4 kernel.exe.P16|GlobalLock U2'
 'free'⎕NA'U2 kernel.exe.P16|GlobalFree U2'
 'unlock'⎕NA'U2 kernel.exe.P16|GlobalUnlock U2'
 'puti'⎕NA'kernel.exe.P16|hmemcpy I4 <I2[] I4'
 'putd'⎕NA'kernel.exe.P16|hmemcpy I4 <I4 I4'
 'geti'⎕NA'kernel.exe.P16|hmemcpy >I2[] I4 I4 '

⍝get name of (printer) device from WIN.INI
 str←',',⊃1↓gps'windows' 'device' '(undefined)' 256 255
 name device port←3↑1↓¨(','=str)⊂str
 →(name≢'(undefined)')/found
 'No print driver currently defined as default'
 →0

found:hpr←0 ⍝ We dont need a handle to the parent form for this  call

⍝set up arg string for ExtDeviceMode
 s←''
 s,←' I2'⍝  hwnd        ←→2 byte integer
 s,←' I4'⍝  hdriver     ←→4 byte integer
 s,←' I4'⍝  lpdmout     ←→4 byte integer (long pointer to output buffer)
 s,←' <0T'⍝ lpszdevice ←→pointer to char string
 s,←' <0T'⍝ lpszport   ←→pointer to char string
 s,←' I4'⍝  lpdmin      ←→4 byte integer (long pointer to input buffer)
 s,←' I4'⍝  lpszprofile←→pointer to char string or NULL (win.ini)
 s,←' I2'⍝  fwmode      ←→2 byte integer (word)
⍝fix external function
 'edm'⎕NA'I2 ',device,'.drv.P16|ExtDeviceMode ',s

⍝Build argument string for ExtDeviceMode
 args←8⍴0
 args[1]←hpr ⍝            Handle to parent
 args[2]←gmh⊂device ⍝     Handle to device driver
 args[3]←0 ⍝              NULL or Pointer to lpdmout (output buffer)
 args[4]←⊂name  ⍝         Device name
 args[5]←⊂port  ⍝         Port name
 args[6]←0 ⍝              NULL or Pointer to lpdmin (input buffer)
 args[7]←0 ⍝              NULL or Pointer to initialisation filepointer
 args[8]←0 ⍝              Request code fwMode←→0 ≡ read size of buffers
⍝Note arg[7] is NULL if initialisation file is WIN.INI

⍝allocate and lock space for DeviceMode structure
 lpdmout←lock hand1←alloc(0)(edm args)

⍝change args and read current values into DeviceMode structure
 args[3]←lpdmout
 args[8]←2 ⍝ fwmode ≡ DM_OUT_BUFFER ≡2
 rc←edm args

⍝remember old orientation
 R←geti 1(lpdmout+44)2

⍝ make changes in DeviceMode structure
 rc←putd(lpdmout+40)(1)(4) ⍝ set dmFields for orientation
 rc←puti(lpdmout+44)(lorp)(2) ⍝ set orientation

⍝set request to change DEFAULT settings
 args[8]←⊂9 ⍝ fwmode≡9  DM_OUT_DEFAULT+DM_IN_BUFFER (1+8)
 args[6]←⊂lpdmout
 rc←edm args
 →(rc=1)/ok
 'error, update failed'
 →end
ok:

⍝clean up
 rc←unlock hand1
 rc←free hand1
 prname SetLocalOrientation lorp;putd;puti;alloc;free;lock;unlock;rdc;hpr;
  gmh;gps;str;name;device;port;s;edm;args;fm;hand1;hand2;lpdmin;lpdmout;
  sdm;rc;old
⍝set printer orientation (local to printer <pname>)
⍝lorp←→1≡Portrait 2≡Landscape
 1 ⎕NQ prname 100 ⍝ Send any output on the current DC to the printer

⍝Fix external functions
 'gmh'⎕NA'I4 kernel.exe.P16|GetModuleHandle <0T'
 'gps'⎕NA'I kernel.P16|GetProfileString <0T <0T <0T >0T I'
 'rdc'⎕NA'I2 gdi.dll.P_6|ResetDC  I2 I4'
 'alloc'⎕NA'U2 kernel.exe.P16|GlobalAlloc U2 U4'
 'lock'⎕NA'I4 kernel.exe.P16|GlobalLock U2'
 'free'⎕NA'U2 kernel.exe.P16|GlobalFree U2'
 'unlock'⎕NA'U2 kernel.exe.P16|GlobalUnlock U2'
 'puti'⎕NA'kernel.exe.P16|hmemcpy I4 <I2[] I4'
 'putd'⎕NA'kernel.exe.P16|hmemcpy I4 <I4 I4'

⍝get name of (printer) device from WIN.INI
 str←',',⊃1↓gps'windows' 'device' '(undefined)' 256 255
 name device port←3↑1↓¨(','=str)⊂str
 →(name≢'(undefined)')/ok
 'No print driver currently defined as default'
 →0

⍝ Get the handle of the current printer object
ok:hpr←prname ⎕WG'handle'

⍝set up arg string for ExtDeviceMode
 s←''
 s,←' I2'⍝  hwnd        ←→2 byte integer
 s,←' I4'⍝  hdriver     ←→4 byte integer
 s,←' I4'⍝  lpdmout     ←→4 byte integer (long pointer to output buffer)
 s,←' <0T'⍝ lpszdevice ←→pointer to char string
 s,←' <0T'⍝ lpszport   ←→pointer to char string
 s,←' I4'⍝  lpdmin      ←→4 byte integer (long pointer to input buffer)
 s,←' I4'⍝  lpszprofile←→pointer to char string or NULL (win.ini)
 s,←' I2'⍝  fwmode      ←→2 byte integer (word)

⍝fix external function
 'edm'⎕NA'I2 ',device,'.drv.P16|ExtDeviceMode ',s

⍝Build argument string for ExtDeviceMode
 args←8⍴0
 args[1]←hpr ⍝            Handle to parent
 args[2]←gmh⊂device ⍝     Handle to device driver
 args[3]←0 ⍝              NULL or Pointer to lpdmout (output buffer)
 args[4]←⊂name  ⍝         Device name
 args[5]←⊂port  ⍝         Port name
 args[6]←0 ⍝              NULL or Pointer to lpdmin (input buffer)
 args[7]←0 ⍝              NULL or Pointer to initialisation filepointer
 args[8]←0 ⍝              Request code fwMode←→0 ≡ read size of buffers
⍝Note arg[7] is NULL if initialisation file is WIN.INI

⍝allocate and lock space for DeviceMode structure (output buffer)
 lpdmout←lock hand1←alloc(0)(edm args)

⍝change args and read current values into DeviceMode structure
 args[3]←lpdmout ⍝        Pointer to the output buffer
 args[8]←2 ⍝              Request code fwmode←→2 ≡ Read current settings
 rc←edm args

⍝ make changes in DeviceMode structure
 putd(lpdmout+40)(1)(4) ⍝ set dmFields for orientation
 puti(lpdmout+44)(lorp)(2) ⍝ set orientation to Landscape

⍝ Reset the printers device context from the modified DeviceMode
 old←rdc hpr lpdmout

⍝clean up
 rc←unlock hand1
 rc←free hand1
 handle←GlobalAlloc bytes;alloc
⍝return a handle to global allocated memory of <bytes>.
 'alloc'⎕NA'U2 kernel.exe.P16|GlobalAlloc U2 U4'
 handle←alloc 0 bytes

 prt←GlobalLock handle;lock
⍝lock global memory
 'lock'⎕NA'I4 kernel.exe.P16|GlobalLock U2'
 prt←lock handle

… and similarly for GlobalFree and GlobalUnlock.

References

Some articles about calling DLLs from Dyalog APL/W:

[1] Pearson, Duncan: A Standard Font Dialogue Box from Dyalog APL/W using ⎕NA. Vector 10.1 pp 117-124

[2] Pearson, Duncan: A Windows Task-Killer for APL. Vector 10.2 pp 126-127

[3] Smith, Adrian: Minding your .INIs. Vector 10.3 pp 96-99

[4] Smith, Adrian: Native Files without Tears. Vector 11.1 pp 126-129

[5] Smith, Adrian: Making Noises in APL. Vector 11.2 pp 118-122

[6] Windows SDK on-line help