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! */
 }
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

FileMaker Pro 19.4.2 - Quickly build cus...
FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
Adobe Illustrator 26.0.3 - Professional...
You can download Adobe Illustrator for Mac as a part of Creative Cloud for only $20.99/month. Adobe Illustrator for Mac is the vector graphics classics in the design industry. It is a digital... Read more
WhatRoute 2.4.9 - Geographically trace o...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the router... Read more
Notion 2.0.20 - A unified workspace for...
Notion is the unified workspace for modern teams. Notion Features: Integration with Slack Documents Wikis Tasks Release notes were unavailable when this listing was updated. Download Now]]> Read more
Monterey Cache Cleaner 17.0.2 - Clear ca...
Monterey Cache Cleaner is an award-winning general-purpose tool for macOS X. MCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
Firetask Pro 4.6.8 - Innovative task man...
Firetask Pro represents the next generation of easy-to-use, project-oriented task management apps. By combining David Allen's powerful Getting Things Done (GTD®) approach with classical task... Read more
Smultron 13.0.4 - Easy-to-use, powerful...
Smultron 13 is the text editor for all of us. Smultron is powerful and confident without being complicated. Its elegance and simplicity helps everyone being creative and to write and edit all sorts... Read more
Box Sync 4.0.8057 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Audio Hijack 3.8.10 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
Direct Mail 6.0.1 - Create and send grea...
Direct Mail is an easy-to-use, fully-featured email marketing app purpose-built for macOS. Create, send, and track great looking email campaigns that get results. Start your newsletter by selecting... Read more

Latest Forum Discussions

See All

Hopefully Not Jared’s Last Show – The To...
My suspicions from last week were correct, and after my two kids tested positive for Covid last week both my wife and I have now tested positive as well. It seems you just can’t escape this stuff lately. Thankfully the two little ones are pretty... | Read more »
TouchArcade Game of the Week: ‘Micro RPG...
I feel like idle games are one of those perfect fits for the mobile platform. Not that they replace more involved gaming experiences when you’re in the mood for that, but they do fit in alongside other types of games just fine as a “go to" when you... | Read more »
‘Phantom Blade: Executioners’ Closed Bet...
Phantom Blade: Executioners is holding a small-scale technical test that lets players get first dibs on the KungFuPunk action RPG. Offered to selected players only, S-Game’s first Closed Beta Test will provide players with limited edition in-game... | Read more »
New ‘Warhammer 40,000: Tacticus’ Video S...
Back in September Snowprint Studios, who you may know from their previous Legend of Solgard or Rivengard, announced that they’d partnered up with Games Workshop to put out a new tactical game in the Warhammer 40,000 universe titled Warhammer 40,000... | Read more »
SwitchArcade Round-Up: ‘Pokemon Legends:...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 28th, 2022. We’ve got a bunch of new releases to look at today, with a few big hitters, a few mid-level diversions, and a healthy supply of compost. Since it’s Friday, we... | Read more »
Phantom Blade: Executioners, S-Game...
S-Game has kicked off its first Closed Beta Test for Phantom Blade: Executioners, inviting a selected few to get first dibs on the upcoming KungFuPunk action RPG on mobile. The CBT officially begins this January 28th, and beta testers will receive... | Read more »
‘Infinite Galaxy’ First Anniversary: Cel...
Cultivating a new generation of valiant commanders across 240 countries worldwide, Infinite Galaxy has quenched players’ thirst to explore the vastness of space – and there are only more intergalactic adventures to embark on from here on out. Camel... | Read more »
War and Order: How to brave the cold in...
War and Order's 6th-anniversary celebrations are underway, and all in good time too - this season not only brings about fabulous festivities, but it also lets players experience the harsh winter in an entirely new way. [Read more] | Read more »
‘Hidden Folks+’ Is This Week’s New Apple...
The original Hidden Folks from Adriaan de Jongh is an excellent hidden objects game featuring hand drawn visuals. It is an absolute joy to play, and it has now released on Apple Arcade in the form of Hidden Folks+ () as an App Store great. If you’... | Read more »
Mini Metro’s First Big Update of 2022 Ad...
Last year saw great updates for Dinosaur Polo Club’s Mini Metro ($3.99) which is also available on Apple Arcade as an App Store Great. | Read more »

Price Scanner via MacPrices.net

Apple has clearance 2020 13″ MacBook Airs ava...
Apple has clearance, Certified Refurbished, 2020 13″ Intel-based MacBook Airs in stock today starting at only $719 and up to $370 off original MSRP. Each MacBook features a new outer case, comes with... Read more
The cheapest iPhones for sale today at Apple...
Apple has restocked Apple Certified Refurbished iPhone 8 models starting at only $359. Each refurbished iPhone comes with a fresh external case, standard Apple 1-year warranty, and free shipping.... Read more
14″ MacBook Pro with Apple M1 Max CPU now in...
Looking for a new 14″ MacBook Pro with an Apple M1 Max CPU? Stock is finally trickling into Apple resellers. B&H has Silver 14″ M1 Max MacBook Pros in stock today for $2899 including free 1-2 day... Read more
14″ MacBook Pros with Apple M1 Pro CPUs are i...
Amazon is reporting stock of 14″ MacBook Pros with M1 Pro CPUs today with a $50 discount. Shipping is free, and delivery is available by February 1st for most configurations. Be sure to make your... Read more
Apple has restocked 13″ M1 MacBook Pros for $...
Apple has restocked a full line of 13″ M1 MacBook Pros available Certified Refurbished, starting at only $1099 and up to $230 off original MSRP. These are the cheapest M1 MacBook Pros for sale today... Read more
Apple’s AirPods Max headphones are on sale fo...
Amazon has Silver, Blue, and Space Gray Apple AirPods Max headphones on sale today for $100 off MSRP. Shipping is free, and all models are in stock today. Their price is the lowest currently... Read more
Open a new line of service at Verizon and get...
Verizon is giving away 64GB Apple iPhone 12 minis or your choice of an iPhone 11 to customers who choose one of these phones and open a new line of service. Offer is available online only, and no... Read more
Open-box 13″ M1 MacBook Airs now available st...
QuickShip Electronics has open-box return 13″ M1 MacBook Airs in stock and on sale for $200-$400 off MSRP on their eBay store right now with free express delivery. According to QuickShip, “The item... Read more
Verizon’s 2022 iPad promo: $100-$310 off any...
Verizon has cellular-capable iPads on sale for $100-$310 off MSRP when purchased with an Unlimited service plan. Sale price is applied to your account monthly over a 24 or 30 month period, depending... Read more
Sunday Sale: Apple AirPods are on sale for up...
Amazon has Apple AirPods on sale for $10-$100 off MSRP today, depending on the model. All are in stock today with free delivery: – AirPods Max headphones (Blue): $449 $100 off MSRP – AirPods Max... Read more

Jobs Board

Registered Nurse (RN) Employee Health PSJH -...
…is calling for a Registered Nurse (RN) Employee Health PSJH to our location in Apple Valley, CA.** We are seeking a Registered Nurse (RN) Employee Health PSJH to be Read more
Systems Administrator - Pearson (United State...
…and troubleshoot Windows operating systems (workstation and server), laptop computers, Apple iPads, Chromebooks and printers** + **Administer and troubleshoot all Read more
IT Assistant Level 1- IT Desktop Support Anal...
…providing tier-1 or better IT help desk support in a large Windows and Apple environment * Experience using IT Service Desk Management Software * Knowledge of IT Read more
Human Resources Business Partner PSJH - Provi...
…**is calling a** **Human Resources Business Partner, PSJH** **to our location in Apple Valley, CA.** **Applicants that meet qualifications will receive a text with Read more
Manager Community Health Investment Programs...
…is calling a Manager Community Health Investment Programs PSJH to our location in Apple Valley, CA.** **Qualified candidates will be invited to do a self-paced video Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.