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

Latest Forum Discussions

See All

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.