TweetFollow Us on Twitter

PostScript
Volume Number:2
Issue Number:9
Column Tag:Networking

A PostScript Driver in LightSpeed C

By Bob Denny, Alisa Systems, Editorial Board

[This article by Bob Denny, a distinguished member of the MacTutor Editorial Board of Directors, presents a driver that allows simple writeln statements from a high level language to output directly to a LaserWriter in Postscript over AppleTalk, which makes the laser a quick and convenient printer device the same way you might use an ImageWriter! To do this, we "steal" Apple's code from the LaserWriter file that already knows how to talk to the Laser and simply provide a machine language interface to it. Then we write a device driver in LightSpeed C, and finally, our Pascal application can simply open the driver and print to it. An example of how to open and write to a device driver is given in MacPascal and TML Pascal. Finally, calling drivers is explained by presenting examples using standard file I/O, the high level device manager calls, and the low level device manager calls. This article is a significant contribution to both device drivers in general, and the LaserWriter in particular. Thank you , Bob! -Ed.]

A PAP Driver for the LaserWriter

Alan Wootton played a dirty trick on me for my birthday last month. He bought me a copy of SmallTalk-80 for the Macintosh. I had made some comment about being interested in it, trying to learn about it from the Addison-Wesley books (and failing miserably), and just getting a Mac+ so I could run it, etc. Well, I ended up getting totally sucked in.

I soon became frustrated at my inability to print to our LaserWriter from SmallTalk-80. I wanted to catalog the message protocols for some of the most common classes (“numbers”, for instance). I thought, “Here is a reason to learn SmallTalk-80 and PostScript all at once!" Alan knows how my mind works, because he called me up moaning about not being able to print to his LaserWriter. That did it. My aging brain needed a good workout, so

It seemed hopeless to try to interface to the Print Manager from SmallTalk-80. Rather, I decided to build a driver which would support direct communication with the LaserWriter via the Appletalk Printer Access Protocol (PAP). Once the driver was done, I could then go into SmallTalk-80 and modify it to print using PostScript. The Mac implementation of SmallTalk-80 has an interface to the device manager and it could be used to send PostScript to the “chosen” LaserWriter via the driver. In fact the driver could be used from other interpretive languages such as MS Basic or MacPascal.

This article reviews the PAP manager, including an enhancement in the newest version, and describes the “PAP Driver” along with an example using Mac Pascal. It does not describe PAP itself (that will be the subject of a future article).

The PAP Manager Revisited

Refer to the "Laser Print DA for Postscript" by Mike Schuster, in the March 1986 issue of Mac Tutor. In that article, Mike describes the PAP manager and how to build a desk accessory that will transmit the contents of a file to the LaserWriter.

PAP, or Printer Access Protocol, is a combined application and session level protocol which is used to communicate with the LaserWriter over Appletalk. It uses ATP (Appletalk Transaction Protocol) for error and flow control.

On the Macintosh, PAP is implemented in a “code” resource, not as a driver. The original Inside LaserWriter describes an interface to PAP that was apparently never released. Last year, Bill Parkhurst and Mike Schuster dove into the “LaserWriter” resource file and found that the PDEF-10 resource contains the code that handles PAP communications, the PAP Manager. It turned out that the interfaces to the routines in the PAP Manager look just like those described in Inside LaserWriter, except that the routines are vectored through a list of jumps at the beginning of the code resource.

The interface is quite straightforward. There are basic calls for “open”, “close”, “read” and “write”. These functions operate in the context of a session, a private connection between the client (Mac) and the server (LaserWriter) that may last for the duration of several jobs.

There is a “status” call that generates a one-shot status request to the server, to which the server responds with a human-readable status string. The status request may be issued by any client that can see the server. It does not operate within a session. The status call makes it possible to check the status of the LaserWriter whether or not it is busy processing a job for someone else.

Finally, there is an “unload” call, which must be issued when an application is finished using the PAP manager. If the application releases the memory occupied by the PAP manager without making the unload call, internal PAP manager timers will go off and cause a jump into garbage.

The PAP Manager Interface & Operation

The name of the current (chosen) printer resource file (e.g., “LaserWriter”, “ImageWriter” or whatever) is contained in the System file as STR resource number -8192. If this string contains “LaserWriter”, then the workstation is currently set to print to a LaserWriter. Which one?

The Appletalk “entity” name of the currently chosen LaserWriter is contained in the PAPA resource number -8192, which is located in the LaserWriter resource file. This file is located in the System Folder (the “blessed” folder on HFS volumes). The format of the PAPA resource is exactly that needed for the Name Binding Protocol (NBP) to locate the selected LaserWriter. The chooser changes this resource whenever the chosen printer is changed.

Strictly speaking, the previous two paragraphs contain a white lie. The “LaserWriter” resource file might have been renamed something else, or there may be several versions of the LaserWriter resource file present on the system, each with a unique name. The STR -8192 resource in the System file always contains the name of the currently chosen printer resource file.

Just because the currently chosen printer name isn't “LaserWriter” doesn't mean that it's not a LaserWriter resource. Refer to Denny, Bob (1986), “How the Chooser Works”, Mac Tutor, Vol. 2, No. 7, July, 1986. You might want to look at the STR -4096 resource in the device resource file. This string contains the NBP “type” of the printer. If it contains “LaserWriter” then it's a good bet that the device is indeed a LaserWriter. For the purposes of this article, we assume that the resource file is named “LaserWriter” and that it is located in the System folder.

Once you have located the LaserWriter resource file, you have access to the PAP Manager. The PAP manager is a code resource, specifically PDEF resource number 10 in the LaserWriter resource file. It begins with a fixed sequence of jump instructions to the actual routines:

Offset Code

0: JMP PAPOpen

4: JMP PAPRead

8: JMP PAPWrite

12: JMP PAPStatus

16: JMP PAPClose

20: JMP PAPUnload

Each of the routines takes its parameters in the Toolbox (Lisa Pascal) format. Most execute asynchronously and use a specified memory location to post the status of the operation. Before using the PAP Manager, your program must locate the PDEF-10 resource, then load and lock it.

You must write an assembler “glue” module to handle call and return through the jump table. If your compiler doesn't support native Macintosh toolbox parameter passing, the glue routines must also re-arrange the parameters according to the Pascal conventions. The individual routine interfaces are described below in C (neglecting the jump-table operation).

PAPOpen:

This call is used to open a connection, or session, to a particular LaserWriter. The key to the connection is the reference number (refNum) returned by the call. The process is asynchronous and will complete when the connection is opened or if the specified printer cannot be located. If someone else has a connection open to the printer, the PAPOpen will perform retries for a “long” time, after which it will return an error. You may then re-issue the open call and try again.

 struct statusBuf
 {
 long int papStuff;
 char statusString[255];
 };

 pascal short PAPOpen(refNum,
 printerName, flowQuantum,
 statusBuff, compState)
 short *refNum;
 char *printerName;
 short flowQuantum;
 struct papStatus *statusBuf;
 short *compState;

refNum is the reference number of the PAP connection, returned after the connection has been opened.

printerName is the NBP entity name for the printer (usually the contents of the PAPA -8192 resource).

flowQuantum indicates the maximum size message that your end can receive from the LaserWriter, in multiples of 512 bytes. For example, if your receive buffer is 1024 bytes in size, specify flowQuantum = 1024/512 = 2. The LaserWriter can accept messages up to 4096 bytes; its flow quantum is 8. You should use as large a flow quantum as possible so as to reduce ATP overhead.

statusBuf points to a PAP status buffer as defined above. During the connection opening process, the PAP manager places status messages from the LaserWriter into the statusString field asynchronously. Your application might display the status string contents once a second or so during the connection opening phase. If someone else's job is being processed, the LaserWriter will put descriptive information about that job in the status return. The statusString should be initialized with a zero byte in the first character before calling PAPOpen.

compState is the address of a 16-bit word that is used to indicate the progress of the (asynchronous) connection opening. If the PAPOpen call returns with status noErr (=0), then the word pointed to by compState will contain a number greater than zero, indicating that the opening phase is in progress. When the connection has been opened, this value will change to zero (no error). If the open fails, *compState will contain a negative value (indicating an error).

PAPRead:

This call is used to read data from the LaserWriter. The operation is done asynchronously. Normally, during a printing session, the LaserWriter will not send anything back to the workstation. If there is an error condition, such as paper out or a paper jam, the printer will try to send a status message back to the workstation. In that case, if your application has not issued a PAPRead, the printer will hang. Also, if you send PostScript to the printer that causes data to be sent back to the workstation, you must read that data or the printer will hang.

 pascal short PAPRead(refNum, buffer,
 length, eof, compState);
 short refNum;
 char *buffer;
 short *length;
 short *eof;
 short *compState;

refNum is the reference number of the connection, returned from the PAPOpen call.

buffer is the address of a buffer to receive the data from the server (LaserWriter). Note that the buffer must be large enough to handle messages as large as you specified in the flowQuantum parameter to the PAPOpen call.

length is the address of a word that will be filled in with the length of the data actually received (bytes) when the read completes.

eof is the address of a word that is used to detect an end-of-file signal from the PostScript interpreter in the LaserWriter. When the read completes, if the word addressed by eof is non-zero, the PostScript interpreter in the LaserWriter has sent an end-of-file indication to the workstation. The LaserWriter sends this in response to a previous end-of-file sent to it by the workstation, completing a handshake. This means that your application must have a PAPRead outstanding at the completion of a job, so that the LaserWriter can return its end-of-file. See the PAPWrite description.

compState is the address of a 16-bit word that is used to indicate the progress of the (asynchronous) read operation. If the PAPRead call returns with status noErr (=0), then the word pointed to by compState will contain a number greater than zero, indicating that the read is in progress. When the LaserWriter sends a message to the workstation, this value will change to zero (no error). If the connection has broken, *compState will contain a negative value (indicating an error).

PAPWrite:

This call is used to send data to the LaserWriter. The stream of PostScript sent to the LaserWriter may be buffered into messages as long as 4096 bytes (the LaserWriter's flow quantum). Your application should attempt to buffer the PostScript as much as possible, as this reduces the network overhead dramatically. This will be especially true when Appletalk internetworks include links using public packet-switching nets (such as Telenet or TYMnet). These nets have large propagation delays, and the transaction nature of PAP makes it extremely sensitive to message size.

Just cram the PostScript into the buffer till it's full, then issue the write. Since PAPWrite is asynchronous, you could double-buffer the write stream, filling one buffer while the PAPWrite is outstanding on the other buffer. In any case, at the end of the job, set the eof flag (see below) and issue the final PAPWrite on the partial buffer remaining.

pascal short PAPWrite(refNum, buffer, 
 length, eof, compState);
 short refNum;
 char *buffer;
 short *length;
 short *eof;
 short *compState;

refNum is the reference number of the connection, returned from the PAPOpen call.

buffer is the address of a buffer containing the data to be sent to the server (LaserWriter). This buffer may be up to 4096 bytes in length, per the LaserWriter's flow quantum. You'll get an error if the receiver's flow quantum is exceeded.

length is a word containing the number of bytes to be sent in the message.

eof is a word that is used to signal end-of-file to the PostScript interpreter in the LaserWriter. This should be done at the end of the print job, on the last message of the job. If the LaserWriter detects a PostScript error during a job, it will ignore input until it detects end-of-file in the stream. At that point is will send an end-of-file back to the workstation, reset its state to the permanent defaults, then assume that further data belongs to a new job.

compState is the address of a 16-bit word that is used to indicate the progress of the (asynchronous) read operation. If the PAPWrite call returns with status noErr (=0), then the word pointed to by compState will contain a number greater than zero, indicating that the write is in progress. When the LaserWriter has successfully received the message, this value will change to zero (no error). If the connection has broken, *compState will contain a negative value (indicating an error).

PAPClose:

This function closes the PAP connection to the LaserWriter. This should only be done after the exchange of end-of-file messages as already outlined. The function is executed synchronously, and it cancels any outstanding PAPRead and/or PAPWrite calls.

 pascal short PAPClose(refNum, buffer,
 length, eof, compState);
 short refNum;

refNum is the reference number of the connection, returned from the PAPOpen call.

PAPStatus:

This function operates outside the context of a PAP connection. No PAP connection need be open to obtain status from a server (LaserWriter). It executes synchronously, and it returns a status string from the server. This function is used by the Macintosh print manager to generate the messages that appear in the status window during a LaserWriter print job (except in the case of a PostScript error, when the status is sent via the PAP connection from the LaserWriter to the workstation).

struct statusBuf
 {
 long int papStuff;
 char statusString[255];
 };

 struct addrBlock
 {
 short net;
 char node;
 char socket;
 };

 pascal short PAPstatus(printerName,
 statusBuff, netAddr)
 char *printerName;
 struct papStatus *statusBuf;
 struct addrBlock *netAddr);

printerName is the NBP entity name for the printer (usually the contents of the PAPA -8192 resource).

statusBuf points to a PAP status buffer (defined above). Upon return, it contains a human-readable string indicating the current status of the LaserWriter.

netAddr is the address of an internet address block (defined above) , used to minimize NBP lookups during repetitive status calls on the same printer. You should initialize the contents of the address block to zero before making your first PAPStatus call. After the first call (if successful) the address block will contain the internet address of the printer you specified in the printerName parameter. Thereafter, the PAP Manager will try to use the address in the block rather than perform another NBP lookup. If for some reason the printer stops responding at the cached address, the PAP Manager will automatically revert to an NBP lookup and start the cycle again.

PAPUnload:

This function must be called prior to unlocking or releasing the memory occupied by the PAP Manager. The PAP Manager maintains several timers which are controlled by a VBL task. If the code for the VBL task vaporizes, the next time the VBL timer expires, the system will jump into garbage.

pascal short PAPUnload()

Hooking Up to the PAP Manager

The first thing we need to use the PAP Manager is a set of “glue” routines that provide a function-calling interface to the PAP Manager calls. We also need the code to locate, load and lock the PDEF-10 resource. This section describes a general-purpose interface module that provides these services. The mechanics of preparing the PAP Manager code resource (PDEF-10) for use are implemented in a PAPLoad() call which is added to the “real” PAP Manager calls just described.

;+
;****************
;* PAPIntfc.asm *
;****************
;
; Low level interface to the PAP manager for LightSpeed C
; and anyone else who uses Mac/Lisa standard Pascal
; calling environment.
;
; NOTE: The name 'LaserWriter' is assumed here. The
; name of the resource file can be different.  Should look at
; the STR resource in the System file that holds the name
; of the currently selected printer type, etc.
;
; NOTE: Uses new assembler equates & D files from 
;Software Supplement, Vol. 1 No. 3, March '86 mailed July.
;
; Written by:
;Bob Denny
;Alisa Systems, Inc.
;July, 1986
;

 IncludeTraps.D
 IncludeSysEqu.D
 IncludeFSEqu.D
;
; Following has been wrong in equates forever
;
ioNamePtr equ  ioFileName
;
; Private local storage. Lives during the time the driver
; is open.
;
; *********************
; *** W A R N I N G ***
; *********************
;
; Static data here requires dNeedLock bit set in driver. 
; You can change this by allocating space & storing the
; handle in dCtlVars.
;
papHndl:
 dc.l 0 ; Handle to PAP manager resource
papPtr: 
 dc.l 0 ; Points to locked PAP manager
entHndl:
 dc.l 0 ; Handle to locked entity name
;
;  Dynamic (stacked) data
;
;
; Open PAP connection
;
 xdef papOpen
papOpen:
 move.l papPtr,a0
 jmp    0(a0)
;
; Receive data from PAP server
;
 xdef papRead
papRead:
 move.l papPtr,a0
 jmp    4(a0)
;
; Send data to PAP server
;
 xdef papWrite
papWrite:
 move.l papPtr,a0
 jmp    8(a0)
;
; Get PAP server's status
;
 xdef papStatus
papStatus:
 move.l papPtr,a0
 jmp    12(a0)
;
; Close PAP connection
;
 xdef papClose
papClose:
 move.l papPtr,a0
 jmp    16(a0)
;
; Load and lock the PAP manager
;
; Returns handle to entity name
;
 xdef papLoad
;
; Stack frame (local automatic) variables
;
 
curVol  equ -2 
 ; Client's default dir/vol saved here
LWfRef  equ -2 + curVol 
 ; fRefNum of 'LaserWriter' file
ioParam equ  -ioFQelSize + LWfRef 
 ;MFS/HFS file I/O params
ioHVParam equ  -ioHVQElSize + ioParam
 ; HFS Volume Info params
frameSize equ  ioHVParam

papLoad:
 link   a6,#frameSize
 move a2,-(sp)   ; Save A2

 lea    papHndl,a0   ; Init for error handling
 clr.l  (a0)
 lea    papPtr,a0
 clr.l  (a0)
 lea    entHndl,a0
 clr.l  (a0)
;
; This mess gets the PDEF-10 resource from the
; LaserWriter file on the boot volume without making a
; permanent change in the current default volume. Note,
; HFS volumes require more work to find the file in the
; 'blessed' folder.
;
 lea    ioParam(a6),a2  ; a2 -> ioParam
 clr.l  ioCompletion(a2)
 clr.l  ioNamePtr(a2)
 movea.la2,a0    ; -> ioParams
 _GetVol; **MUST WORK**
 move.w ioVRefNum(a2),curVol(a6)   ; Save it
 movea.lVCBQHdr+2,a0 ; Boot vol 1st on VCB Q
 move.w vcbVRefNum(a0),ioVRefNum(a2)
 ;Boot volume VRefNum
 move.l a2,a0
 _SetVol; Switch to boot volume
 bne    plErr    ; (huh?)
 clr.w  -(sp)    ; Open the 'LaserWriter' file
 pea    'LaserWriter'
 _OpenResFile
 move.w (sp)+,LWfRef(a6)  ; refNum 
 cmp.w  #-1,LWfRef(a6)  ; Error?
 bne    @10 ; (nope)
;
; Failed to find LaserWriter. If on HFS, we can try to
; find it in the System Folder ('blessed' folder). See
; Macintosh Tech Note #67 "Finding the Blessed Folder".
;
 tst.w  FSFCBLen
 bmi    plErr    ; (oops, not running HFS system)
 lea    ioHVParam(a6),a0  ; a0 -> HFS volinfo PB
 clr.l  ioCompletion(a0)  ; Set it up for the call
 clr.l  ioNamePtr(a0)
 clr.w  ioVRefNum(a0)
 clr.w  ioVolIndex(a0)
 _HGetVInfo ; HFS Get Volume Info
 bne    plErr    ; (huh?)
 move.l ioVFndrInfo(a0),ioWDDirID(a2) 
 beq    plErr    ; (oops!)
 movea.la2,a0
 _HSetVol ; Set default to blessed folder
 bne    plErr    ; (huh?)
 clr.w  -(sp)    ; Open the 'LaserWriter' file
 pea  'LaserWriter'
 _OpenResFile
 move.w (sp)+,LWfRef(a6)  ;refNum of 'LaserWriter'
 cmp.w  #-1,LWfRef(a6)  ; Error?
 beq  plErr ; (yes, give up)
 ;
 ; Now the LaserWriter resource file is open
 ;
@10:  
 subq.l #4,sp
 move.l #'PAPA',-(sp); Get the entity name 
 move.w #$E000,-(sp)
 _GetResource
 lea    entHndl,a0
 move.l (sp)+,(a0) ; Entity handle
 beq.s  plErr

 move.l entHndl,a0 ; Lock entity string
 _HLock
 move.l entHndl,-(sp)
 _DetachResource ; Hide it from Rsrc Mgr
 
 subq.l #4,sp    ; Get the PAP manager
 move.l #'PDEF',-(sp)
 move.w #10,-(sp)
 _GetResource
 lea    papHndl,a0 ; (dumb 68000 designers)
 move.l (sp)+,(a0) ; Handle to PAP manager 
 beq.s  plErr    ; (oops)
 
 move.l papHndl,a0
 _HLock ; Lock it down
 move.l papHndl,a0
 lea    papPtr,a1; (geez!)
 move.l (a0),(a1); Save ptr to PAP mgr
 move.l papHndl,-(sp)
 _DetachResource ; Hide it from Rsrc Mgr

 move.w -4(a6),-(sp) ; 'LaserWriter' refNum
 _CloseResFile   ; Close 'LaserWriter'
 
 move.l entHndl,-(sp); Return entity handle
 bra.s  plRet

plErr:  clr.l  -(sp) ; Return nil handle
 
plRet:  move.w curVol(a6),ioVRefNum(a2)
 ; Get original default volume
 move.l a2,a0
 tst.w  FSFCBLen ; MFS or HFS?
 bmi.s  @10 ; (MFS)
 _HSetVol  ; Restore orig default vol/dir
 bra.s  @20
@10:  _SetVol    ; Restore original default vol
@20:  move.l(sp)+,d0 ; Restore return value
 move.l (sp)+,a2 ; Restore a2
 unlk   a6; Standard Pascal function return
 move.l (sp)+,a0 ; (return Address)
 ;;;addq#0,sp    ; (no parameters)
 move.l d0,(sp)  ; (Return Value)
 jmp  (a0); Return
;
; Unload the PAP manager.  Should be called via
; 'needGoodBye' if used in driver or D/A.
;
 xdef papUnload
papUnload:
 link   a6,#-2
 clr.w  -2(a6)   ; Justin Case
 move.l papPtr,a0
 beq.s  @10
 subq.l #2,sp
 jsr    20(a0)   ; Real PAPUnload 
 move.w (sp)+,-2(a6) ; Save PAP result

@10:  move.lentHndl,a0  ; Junk entity 
 beq.s  @20
 _HUnlock
 move.l entHndl,a0
 _DisposHandle

@20:  move.lpapHndl,a0  ; junk PAP Manager
 beq.s  @30
 _HUnlock
 move.l papHndl,a0
 _DisposHandle

@30:  move-2(a6),d0
 unlk   a6; Standard Pascal exit
 move.l (sp)+,a0 ; return address
 ;;;addq#0,sp    ; (no parameters)
 move.w d0,(sp)  ;function result
 jmp    (a0);return

 end

A Driver for PAP

Now that we have an interface for the PAP Manager, we can build a driver that will provide applications with a vanilla interface to the LaserWriter.

The example driver does not support read or status operations. All incoming data from the LaserWriter is thrown away. This makes it very easy to write applications that just send to the LaserWriter and don't care what comes back. A commercial-quality application should handle reading from the LaserWriter and sending periodic status requests. The driver can be easily modified to support these things.

The driver is written for LightSpeed C, which automatically sets some of the driver flags in the header. This driver must have dNeedLock, dNeedTime and dNeedGoodBye set. The control routine handles controlled and emergency (dNeedGoodBye) unloading of the PAP manager & entity string, as well as reading from the LaserWriter. Note that reading is attempted in the prime routine while the driver waits for the write to complete. I had to do this because SmallTalk-80 doesn't call SystemTask, so the driver never gets any dNeedTime control calls. LightSpeed C also handles the choice between returning to the system via RTS or JIODone, so be careful if you have to do this yourself.

Finally, the glue routines are simplified because LightSpeed C has a “pascal” function call feature. If your compiler doesn't support this, the glue routines must change from your compiler's call frame to that expected by the Mac toolbox (ie, left to right push on the stack).

Here is a sample program in MacPascal that uses the PAP driver and PAP Interface to print a little message on the LaserWriter. Note that this is a VERY inefficient use of PAP, as MacPascal does single character writes! Since we are writing directly to the LaserWriter, our text must be in PostScript because we are not going through LaserPrep as the Printing Manager does. [We'll save that topic for another day, hey Bob? -Ed] However, it is easy to format our text in a Postscript context as the example shows.

program PapDemo;

 var
 laser : text;
begin
 open(laser, '.PAP');
 writeln(laser, 
'/showline { gsave show grestore 0 -14  rmoveto } def');
 writeln(laser, '/Courier findfont 12 scalefont setfont');
 writeln(laser, '50 750 moveto');
 writeln(laser, '(Now  learn PostScript, and) showline');
 writeln(laser,'(keep reading Mac Tutor!)  showline'); 
 writeln(laser, 'showpage');
 close(laser);
end.

Implementation Details

By David E. Smith

The whole concept of drivers may seem confusing, and on the Mac it is! What our assembly language interface does is hunt for the LaserWriter file, hopefully in the "blessed folder", and opens it as a resource. Then we snoop into the LaserWriter file for two resources, "PAPA E000" and "PDEF 10", which we steal using GetResource, and lock in memory. We then provide access to the PDEF 10 resource, we learned is the PAP Manager, by setting up calls to the PAP Manager for open, read, write, status and close. Finally, we arrange to unload the PAP Manager when we are done with it. This is the purpose of the assembly interface. Compile the assembly code with either MDS assembler or Consulair C, or some other compatible assembler. The resulting ".REL" file will be linked to our driver.

Once we have an interface to this bit of code in the LaserWriter file, we need a driver. The driver here was written in LightSpeed C, so it may not look exactly like a driver you are used to seeing in assembly. This is because LightSpeed C does a lot of housekeeping for you relative to drivers. The manual has an excellent discussion of why they do this, which is generally related to the problems of how drivers return, via RTS or JIODone. As the manual points out, most people do this wrong, so LightSpeed figures out how to do it right and sets up all the driver header information for you. This might seem a bit inflexible if you are used to doing everything your own way, but is very nice if you are unfamilar with Macintosh drivers and just want something that works. As a result, the driver source code has a MAIN with a case statement, required by LightSpeed C. The BUILD DRIVER function uses this case statement to create the necessary driver header information for you.

LightSpeed C also does some other things that you might not consider standard. When the driver is built, a DATA resource type is created in the driver file. Both the DRVR and this DATA resource must be moved from the driver file into the system file in order for the driver to work properly. This fact was unknown to me and I spent six hours trying to figure out why the driver wasn't working properly. The DATA resource has to do with the driver's global variables and is purely a creation of LightSpeed C's build driver function. You can also move the DRVR and DATA resources into your application file (which is also a resource!). I tried this and it workds too. In fact, it works better, because you don't have to re-boot the system after using ResEdit, as you do if you move the driver into the system file. Also, it is always present for those applications which you've written code to open it.

Because LightSpeed C works under an invisible shell, there is no external link file. But the assembly interface and the C driver source code must be linked together and the driver created. This normal linking step is almost invisible in LightSpeed C so it's easy to forget that other compilers have to provide this step. The reason it's invisible is that both files, after being compiled, are loaded into what is called "a project" and are instantly available at all times. In addition, a seperate "VOC" file can be created that specifies case requirements for upper and lower case names. The manual goes into detail on various C compilers and how they deal with upper and lower case, as well as other compiler dependent problems. The manual is very well done, easily the best I've seen of any of the development systems. It looks like a normal (ie non-Macintosh) language manual, specifying the syntax of each command. For some reason, compiler developers on the Mac assume Mac programmers know their language by heart and rarely provide a language definition manual!

Once the assembly source is assembled, the C source compiled, the two linked and a driver created, that driver is then installed in the system file or our application file, using ResEdit. Remember, if created with LightSpeed C, both the DATA and DRVR resources must be copied into the system file. Also, the purge bit and system heap bits should be set with the GETINFO ResEdit command, if using the MacPascal example.

TML File I/O Example

With the driver installed in the system file, any file I/O command can be given to open and write to the driver. Bob gave a simple MacPascal example. Here is the same example in TML pascal with a bare minimum event shell. A window is put up that says "look at the laserwriter" and then the text is printed out. A click of the mouse exits the program. Providing a simple shell program makes debugging easier.

Program PapTest;

{ By D. Smith for MacTutor}
{ Uses standard Pascal file I/O}

{$I MemTypes.ipas  }
{$I QuickDraw.ipas }
{$I OSIntf.ipas    }
{$I ToolIntf.ipas  }
{$I PackIntf.ipas  }

{$T  APPL ???? }

VAR{global program stuff}
   
 laser: text;    {pascal file ref}
 
{----------------------------------}
PROCEDURE CloseUp;
begin 
 exittoshell;
end;
{-----------------------------------}
PROCEDURE InitIt;
begin   
   InitGraf(@thePort);          
   InitFonts;                    
   InitWindows;                   
   InitMenus;                     
   TEInit;                        
   InitDialogs(Nil);             
   InitCursor;
   FlushEvents(everyEvent,0);
end;
{-------------------------------}
 PROCEDURE writeIt(txt:str255); 
 begin
 writeln(laser,txt); 
 end; 
 {-------------------------------}
PROCEDURE LaserIt;
var
 DrvrName: str255;
begin 
 DrvrName:='.PAP';
 open(laser,DrvrName);
 
 { put your text in PostScript Format!}
 writeIt('/showline { gsave show grestore 0 -7 rmoveto } def');
 writeIt('/Courier findfont 7 scalefont setfont');
 writeIt('50 750 moveto');
 writeIt('(this is a line) showline');
 writeIt('(so is this!) showline');
 writeIt('showpage');
 
 Close(laser);
end;

{-----------------------------------}
PROCEDURE SetupWindows;
VarWRect:    Rect;
 wtitle:str255;
 wtype: integer;
 Vis:   boolean;
 GoAway:   boolean;
 RefVal:   LongInt;
 wptr:  windowptr;
Begin   
   SetRect(WRect,10,40,230,150);  
   wtype := 16;
   wtitle:='David E. Smith';
   Vis := true;               
   GoAway := false;                
 RefVal:=0;
 wptr := NewWindow(Nil,WRect,wtitle,Vis,wtype,Nil, GoAway,RefVal);
 SetPort(wptr); 
 TextFont(Geneva);
 MoveTo(10,30);
 DrawString('Watch the LaserWriter!');
 MoveTo(10,30);
 Move(0,15);
end;
{--------------------------------}
PROCEDURE MainEventLoop;
VarEvent:EventRecord;
 myevent: Boolean;
Begin
   Repeat
      SystemTask;             
      myevent := GetNextEvent(EveryEvent,Event);
      If myevent then 
         Case Event.what of
            mouseDown  : closeup;            
         End;{of Case}
   Until False;
End;

{---------------------------------}
BEGIN {main program}  
   InitIt;
   SetupWindows;
   LaserIt;
   MainEventLoop 
END.

Device Manger Format

Our code above simply mimics the MacPascal example by opening a text file, which we assign to our PAP driver, and then writing lines of text to it, which are really postscript commands. Note that when we open the file someone goes out and looks first in the system file or the application file to see if we have a driver by that name (who?). Since we do, the driver is opened instead of a file on the disk. One thing I discovered after another six hours of frustration is the LaserWriter wants an end of line character before it will do anything. Using the writeln statement as we did above provides that. But when we use the Device Manager, we have to send an eol to get anything to print out. In the second example below, we modify the WriteIt and LaserIt procedures to use the Hight Level Device Manger calls instead of the vanilla Pascal file statements as we did in the previous example:

Program PapTest2;

{ By D. Smith for MacTutor}
{ Uses High level Device Manager}

 

VAR{global program stuff}
   
 laser: integer; {file ref}
 

{-------------------------------}
 PROCEDURE writeIt(txt:str255);
 CONST
 eol=13;
 var
 StrLen: LongInt;
 err: oserr;
 CR:    str255;  {two bytes?}
 begin
 CR:=chr(eol);
 StrLen:=length(txt);
 err:=FSWrite(laser,StrLen,POINTER(ORD(@txt)+1));
 StrLen:=length(CR);
 err:=FSWrite(laser,StrLen,POINTER(ORD(@CR)+1));   
 end; 
 {-------------------------------}
PROCEDURE LaserIt;
var
 DrvrName: str255;
 err:oserr;
begin 
 DrvrName:='.PAP';
 err:=OpenDriver(DrvrName,laser);
 
 { put your text in PostScript Format!}
 writeIt('/showline { gsave show grestore 0 -7 rmoveto } def');
 writeIt('/Courier findfont 7 scalefont setfont');
 writeIt('50 750 moveto');
 writeIt('(this is a line) showline');
 writeIt('(so is this!) showline');
 writeIt('showpage');
 
 err:=CloseDriver(laser);
end;

In the above example, we use the high level device manager calls to open a driver by name, and then write to it with FSWrite. However, we must also write out a carriage return to get the LaserWriter to output anything, so our WriteIt procedure has been modified to do this. The rest of the code is the same as before.

Low Level Device Manager Calls

In our final example, we again modify the above code to use the low level device manager PBWrite command. For this, we need an ioParam block. We stuff the parameter block with the length of each line and a pointer to the text and issue the PBWrite call using a pointer to the parameter block. (There is probably a cleaner way to handle the carriage return, but as I said last month, I don't understand typecasting! Anyway, this worked!)

Program PapTest3;

{ By D. Smith for MacTutor}
{ Uses low level Device Manager}
 

VAR{global program stuff}
   
 pb:  ParmBlkPtr;{ioParam Block}
 

{-------------------------------}
 PROCEDURE writeIt(txt:str255);
 CONST
 eol=13;
 var
 StrLen: LongInt;
 err: oserr;
 CR:    str255;  {two bytes?}
 begin
 CR:=chr(eol);
 StrLen:=length(txt);
 pb^.ioReqCount:=StrLen;
 pb^.ioBuffer:=POINTER(ORD(@txt)+1);
 err:=PBWrite(pb,false);
 StrLen:=length(CR);
 pb^.ioReqCount:=StrLen;
 pb^.ioBuffer:=POINTER(ORD(@CR)+1);
 err:=PBWrite(pb,false);  
 end; 
 {-------------------------------}
PROCEDURE LaserIt;
var
 DrvrName: str255;
 err:oserr;
begin 
 DrvrName:='.PAP';
 pb^.ioNamePtr:=@DrvrName;
 pb^.ioPermssn:=2; {write only}
 pb^.ioCompletion:=nil;
 err:=PBOpen(pb,false);
 
 { put your text in PostScript Format!}
 writeIt('/showline { gsave show grestore 0 -7 rmoveto } def');
 writeIt('/Courier findfont 7 scalefont setfont');
 writeIt('50 750 moveto');
 writeIt('(this is a line) showline');
 writeIt('(so is this!) showline');
 writeIt('showpage');
 
 err:=PBClose(pb,false);
end;

End of Sample Programs

PAP Driver code.
/*
 * PAP Driver
 *
 * This driver allows applications, etc. to communicate
 * with the currently selected LaserWriter via standard
 * Mac I/O calls.
*/

/* macintosh headers */
#include <DeviceMgr.h>
#include <FileMgr.h>

/* n-bit signed integers */
#define int16 int
#define int32 long

typedef struct   /* pap status record */
 {
 int32 systemStuff;
 char statusStr[256];
 } papStatusRec, *papStatusPtr;

int16 prefnum;   /* pap refNum */
Handle entity;   /* h to server entity name */
papStatusRec status; /* pap status */

char rbuff[512]; /* read buffer */
int16 rsize;/* read size */
int16 rstate;    /* Status of PAPRead */
int16 reof; /* Set to read EOF status */
/* 
 *  Assembly Interface to PAP Manager
 */
extern pascal int16 papOpen();
extern pascal int16 papRead();
extern pascal int16 papWrite();
extern pascal int16 papStatus();
extern pascal int16 papClose();
extern pascal Handle papLoad();
extern pascal int16 papUnload();

/* 
 * Driver Dispatch routine (non-standard, for LS C!)
 */
OSErr main(pb, dce, op)
DCtlEntry *dce;
ioParam *pb;
int op;
 {
 switch(op)
 {
 case 0:/* Open */
 return(drvropen(dce, pb));
 case 1:/* Prime */
 return(drvrprime(dce, pb));
 case 2:/* Control */
 return(drvrctl(dce, pb));
 case 3:/* Status */
 return(drvrstatus(dce, pb));
 case 4:/* Close */
 return(drvrclose(dce, pb));
 default:
 ;
 }
 }


/*
 * Open
 */
int16 drvropen(dce, pb)
 DCtlEntry *dce;
 ioParam *pb;
 {
 int16 ostate;
 int16 pres;
 int16 drvrclose();
 
dce->dCtlFlags |= dNeedGoodBye | dNeedTime;
dce->dCtlFlags &= ~dReadEnable;    /* No Read for now */
dce->dCtlFlags &= ~dStatEnable;    /* No Status for now */
dce->dCtlDelay = 12; /* 5 Hz. cycle rate */
rstate = 1; /* Disable 5 Hz reads for now */
 
 pres = noErr;
 entity = 0;/* PAP not loaded */
 prefnum = -1;   /* No connection */
 if(entity = papLoad())
 {
 status.statusStr[0] = '\0';      /* Clear status string */
 if((pres = papOpen(&prefnum, *entity, 1, &status, &ostate)) == noErr)
 {
 while(ostate > 0) 
 ;
 pres = ostate;
 }
 }
 else
 pres = openErr;

 if(pres != noErr)
 {
 prefnum = -1;   /* PAP connection not open */
 drvrclose(dce, pb);
 }
 else
 papRead(prefnum, rbuff, &rsize, &reof, &rstate);
 
 return(pres);
 }

/*
 * Close
 */
int16 drvrclose(dce, pb)
 DCtlEntry *dce;
 ioParam *pb;
 {
 int16 pres;
 int16 wstate;
 
 pres = noErr;
 
 if (prefnum >= 0)
 {
 /* Exchange EOF messages, then close */
 if((pres = papWrite(prefnum, "", 0, 1, &wstate)) == noErr)
 {
 while(wstate > 0)
 ;
 while(rstate > 0)
 ;
 }
 pres = papClose(prefnum);
 }

 if(entity != 0)
 pres = papUnload();

 return(pres);
 }

/*
 * Control
 *
 * Called at 2Hz and for needGoodBye, also called during
 * wait loop in the 'write' routine, since some apps don't
 * call SystemTask (namely Smalltalk-80!).
 */
int16 drvrctl(dce, pb)
 DCtlEntry *dce;
 cntrlParam *pb;
 {
 
 /* If goodBye, shut down */
 if(pb->csCode == -1)
 return(drvrclose(dce, pb));
 
 /* If KillIO, just return */
 if(pb->csCode == 1)
 return(0);
 
 /* if last papRead finished, try another. */
 if (rstate <= 0)
 papRead(prefnum, rbuff, &rsize, &reof, &rstate);
 
 return(0);
 }

/* 
 * Prime
* All read/write calls from the device manger
 * come here to the Prime routine.
 */
int16 drvrprime(dce, pb)
 DCtlEntry *dce;
 ioParam *pb;
 {
 int16 pres;
 int16 wstate;
 
 if((pb->ioTrap & 0xFF) == aRdCmd)
 return(readErr);/* Should not happen (ha) */

 if((pres = papWrite(prefnum, pb->ioBuffer, (int16)pb->ioReqCount, 0, 
&wstate)) == noErr)
 {
 while(wstate > 0)
 if (rstate <= 0)
 papRead(prefnum, rbuff, &rsize, &reof, &rstate);

 pres = wstate;
 }
 return(pres);
 }

/*
 * Status (inop)
 */
int16 drvrstatus(dce, pb)
 DCtlEntry *dce;
 ioParam *pb;
 {
 return 0;/* Not implemented! */
 }
 
AAPL
$116.31
Apple Inc.
+1.64
MSFT
$48.70
Microsoft Corpora
+0.48
GOOG
$534.83
Google Inc.
-2.16

MacTech Search:
Community Search:

Software Updates via MacUpdate

Herald 5.0.1 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
Firetask 3.7 - Innovative task managemen...
Firetask uniquely combines the advantages of classical priority-and-due-date-based task management with GTD. Stay focused and on top of your commitments - Firetask's "Today" view shows all relevant... Read more
TechTool Pro 7.0.6 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PhotoDesk 3.0.1 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos! (NO Uploads! / Image Posting! Instagram forbids that! AND you *need* an *existing* Instagram account). But you can do... Read more
SuperDuper! 2.7.3 - Advanced disk clonin...
SuperDuper! is an advanced, yet easy to use disk copying program. It can, of course, make a straight copy, or "clone" -- useful when you want to move all your data from one machine to another, or do... Read more
MacJournal 6.1.5 - Create, maintain, and...
MacJournal is the world's most popular journaling software for the Mac. MacJournal 6 adds a calendar mode that show entries from any journal, geolocation, word count, and progress tracking, as well... Read more
Skim 1.4.10 - PDF Reader and note-taker...
Skim is a PDF reader and note-taker for OS X. It is designed to help you read and annotate scientific papers in PDF, but is also great for viewing any PDF file. Skim includes many features and has a... Read more
FontExplorer X Pro 4.2.2 - Font manageme...
FontExplorer X Pro is optimized for professional use; it's the solution that gives you the power you need to manage all your fonts. Now you can more easily manage, activate and organize your... Read more
SoftRAID 5.0.5 - High-quality RAID manag...
SoftRAID allows you to create and manage disk arrays to increase performance and reliability. SoftRAID's intuitive interface and powerful feature set makes this utility a must have for any Mac OS X... Read more
DEVONthink Pro 2.8.2 - Knowledge base, i...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more

Latest Forum Discussions

See All

New Tower Defense Game, Kingdom Rush: Or...
New Tower Defense Game, Kingdom Rush: Origins, is Available Today Posted by Jessica Fisher on November 20th, 2014 [ permalink ] iPad Only App - Designed for the iPad | Read more »
Sunburn! (Games)
Sunburn! 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Your ship is gone. Your crew is scattered. One option remains. Gather your crew... and jump into the sun. Reunite your... | Read more »
Tapventures Review
Tapventures Review By Jennifer Allen on November 20th, 2014 Our Rating: :: ODDLY COMPELLINGUniversal App - Designed for iPhone and iPad Tapventures is an increasingly hands-off one-tap RPG, but expect it to hook you despite your... | Read more »
Who Wore it Best? The Hunger Games: Girl...
With The Hunger Games: Mockingjay Part 1 out this weekend, Who Wore it Best? pits two Hunger Games tie-ins, Girl on Fire and Panem Run, against each other in a brutal and pointless fight to the death. I wonder where we got that idea from? | Read more »
Ironkill Review
Ironkill Review By Jennifer Allen on November 20th, 2014 Our Rating: :: LACKLUSTER PUNCHINGUniversal App - Designed for iPhone and iPad Ironkill is a freemium focused fighting game that doesn’t offer particularly thrilling fights... | Read more »
Real-Time Multiplayer Match-3 RPG Crusad...
Real-Time Multiplayer Match-3 RPG Crusaders Quest Set to Launch Next Month Posted by Ellis Spice on November 20th, 2014 [ permalink ] | Read more »
Checkpoint Champion Review
Checkpoint Champion Review By Jennifer Allen on November 20th, 2014 Our Rating: :: SPEEDY DRIFTINGUniversal App - Designed for iPhone and iPad Checkpoint Champion is a drift-focused racing game that’s ideal for short but fun gaming... | Read more »
MediaFire iOS 8 Native Update Brings New...
MediaFire iOS 8 Native Update Brings New “Power Upload” Feature to iPad and iPhone Posted by Jessica Fisher on November 20th, 2014 [ | Read more »
Mark of the Dragon Review
Mark of the Dragon Review By Nadia Oxford on November 20th, 2014 Our Rating: :: TRAIN YOUR DRAGON FOR WARUniversal App - Designed for iPhone and iPad Mark of the Dragon is a bit rough and scaly, but flying into battle on the backs... | Read more »
My PlayHome School (Entertainment)
My PlayHome School 1.0.1 Device: iOS Universal Category: Entertainment Price: $2.99, Version: 1.0.1 (iTunes) Description: New from the creators of the award winning "My PlayHome"! "My PlayHome School" expands the PlayHome world into... | Read more »

Price Scanner via MacPrices.net

64GB iPod touch on sale for $249, save $50
Best Buy has the 64GB iPod touch on sale for $249 on their online store for a limited time. Their price is $50 off MSRP. Choose free shipping or free local store pickup (if available). Sale price for... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale for $1799.99 for a limited time. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of... Read more
New Logitech AnyAngle Case/Stand Brings Flexi...
Logitec has announced the newest addition to its suite of tablet products — the Logitech AnyAngle. A protective case with an any-angle stand for iPad Air 2 and all iPad mini models, AnyAngle is the... Read more
2013 15-inch 2.0GHz Retina MacBook Pro availa...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros available for $1499 including free shipping plus NY sales tax only. Their price is $500 off original MSRP. B&H will... Read more
16GB Retina iPad mini on sale today for $199,...
 Staples has 2nd generation 16GB Retina iPad minis on sale for $199 on their online store for a limited time. Their price is $100 off MSRP. Choose free shipping or free local store pickup (if... Read more
Developers Start Designing Apps for Apple Wat...
Apple has announced the availability of WatchKit, software that gives developers a set of tools to easily create experiences designed specifically for Apple Watch. Apple’s developer community can now... Read more
C Spire Launches iPad Air 2 and iPad Mini 3 o...
C Spire has announced that iPad Air 2 with Wi-Fi + Cellular and iPad mini 3 with Wi-Fi + Cellular are now available on its 4G LTE network. C Spire offers both new iPads with a range of data plans... Read more
Are You On Your Last PC? – The ‘Book Mystique
Will your current PC be your last? Quite possibly so if you define “personal computer” as a traditional desktop or laptop form factor machine according to some commentators. So then, the upshot that... Read more
Save up to $180 on MacBook Airs with Apple re...
The Apple Store has Apple Certified Refurbished 2014 MacBook Airs available for up to $180 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is free.... Read more
16GB iPad mini available for $219, save $30
Walmart has 16GB iPad minis (1st generation) available for $219 on their online store. Their price is $30 off MSRP. Choose free shipping or free store pickup (if available). Price for online orders... Read more

Jobs Board

*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.