TweetFollow Us on Twitter

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

A PostScript Driver in LightSpeed C

By Bob Denny, Alisa Systems, Editorial Board

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

A PAP Driver for the LaserWriter

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

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

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

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

The PAP Manager Revisited

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

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

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

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

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

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

The PAP Manager Interface & Operation

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

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

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

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

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

Offset Code

0: JMP PAPOpen

4: JMP PAPRead

8: JMP PAPWrite

12: JMP PAPStatus

16: JMP PAPClose

20: JMP PAPUnload

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

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

PAPOpen:

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

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

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

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

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

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

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

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

PAPRead:

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

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

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

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

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

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

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

PAPWrite:

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

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

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

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

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

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

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

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

PAPClose:

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

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

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

PAPStatus:

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

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

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

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

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

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

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

PAPUnload:

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

pascal short PAPUnload()

Hooking Up to the PAP Manager

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

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

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

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

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

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

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

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

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

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

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

 end

A Driver for PAP

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

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

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

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

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

program PapDemo;

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

Implementation Details

By David E. Smith

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

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

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

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

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

TML File I/O Example

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

Program PapTest;

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

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

{$T  APPL ???? }

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

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

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

Device Manger Format

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

Program PapTest2;

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

 

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

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

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

Low Level Device Manager Calls

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

Program PapTest3;

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

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

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

End of Sample Programs

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

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

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

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

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

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

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


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

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

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

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

 return(pres);
 }

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

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

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

 pres = wstate;
 }
 return(pres);
 }

/*
 * Status (inop)
 */
int16 drvrstatus(dce, pb)
 DCtlEntry *dce;
 ioParam *pb;
 {
 return 0;/* Not implemented! */
 }
 
AAPL
$98.38
Apple Inc.
-0.64
MSFT
$43.89
Microsoft Corpora
-0.09
GOOG
$585.61
Google Inc.
-4.99

MacTech Search:
Community Search:

Software Updates via MacUpdate

Drive Genius 3.2.4 - Powerful system uti...
Drive Genius is an OS X utility designed to provide unsurpassed storage management. Featuring an easy-to-use interface, Drive Genius is packed with powerful tools such as a drive optimizer, a... Read more
Vitamin-R 2.15 - Personal productivity t...
Vitamin-R creates the optimal conditions for your brain to work at its best by structuring your work into short bursts of distraction-free, highly focused activity alternating with opportunities for... Read more
Toast Titanium 12.0 - The ultimate media...
Toast Titanium goes way beyond the very basic burning in the Mac OS and iLife software, and sets the standard for burning CDs, DVDs, and now Blu-ray discs on the Mac. Create superior sounding audio... Read more
OS X Yosemite Wallpaper 1.0 - Desktop im...
OS X Yosemite Wallpaper is the gorgeous new background image for Apple's upcoming OS X 10.10 Yosemite. This wallpaper is available for all screen resolutions with a source file that measures 5,418... Read more
Acorn 4.4 - Bitmap image editor. (Demo)
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more

Latest Forum Discussions

See All

Note Review
Note Review By Jennifer Allen on July 29th, 2014 Our Rating: :: TOO SIMPLEiPhone App - Designed for the iPhone, compatible with the iPad Note is a note taking app that’s a little too short on features to be worth its asking price... | Read more »
Chainsaw Warrior Goes on Sale & Ther...
Chainsaw Warrior Goes on Sale & There’s a Chance to Win a Copy of the Original Board Game Posted by Jennifer Allen on July 29th, 2014 [ permalink | Read more »
It Came From Canada: Tiny Tower Vegas
If you go to a casino, you might make a lot of money. If you run a casino, you’re guaranteed to make a lot of money. The choice seems pretty obvious. So while waiting for your shady real estate deals to move forward, get prepared with Tiny Tower... | Read more »
Z Hunter Review
Z Hunter Review By Lee Hamlet on July 29th, 2014 Our Rating: :: RIGHT ON TARGETUniversal App - Designed for iPhone and iPad While it might not necessarily break new ground, Z Hunter has enough tricks up its sleeve to ensure that... | Read more »
Huge Update Comes To Duet, Adding 48 New...
Huge Update Comes To Duet, Adding 48 New Stages Posted by Jennifer Allen on July 29th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Sharknado: The Video Game Available Now....
Sharknado: The Video Game Available Now. Seriously. Posted by Rob Rich on July 29th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Frog Orbs 2 Review
Frog Orbs 2 Review By Nadia Oxford on July 29th, 2014 Our Rating: :: THIS MAGIC IS A TAD MONOTONOUS Universal App - Designed for iPhone and iPad Frog Orbs 2 is repetitive, but younger players should enjoy it nonetheless.   | Read more »
Puzzix Review
Puzzix Review By Jennifer Allen on July 29th, 2014 Our Rating: :: NICE IDEAUniversal App - Designed for iPhone and iPad A little like Tetris, Puzzix is all about piecing together blocks and watching them vanish. It could do with... | Read more »
Cannonball eMail is Now Live – Works Wit...
Cannonball eMail is Now Live – Works With Gmail, Yahoo, Outlook, Hotmail, and AOL Posted by Jessica Fisher on July 29th, 2014 [ permalink ] | Read more »
To The End Review
To The End Review By Lee Hamlet on July 29th, 2014 Our Rating: :: A VICIOUS CYCLEUniversal App - Designed for iPhone and iPad To The End will test players’ patience, timing, and dedication as they try to navigate all 13 levels in... | Read more »

Price Scanner via MacPrices.net

The lowest prices on leftover Retina MacBook...
Best Buy has dropped prices on leftover 13″ and 15″ Retina MacBook Pros by up to $300 off original MSRP on their online store for a limited time. Choose free local store pickup (if available) or free... Read more
Apple Updates MacBook Pro with Retina Display...
Apple today updated its MacBook Pro with Retina display with faster processors and double the amount of memory in both entry-level configurations. MacBook Pro with Retina display features a Retina... Read more
Up to $250 price drop on leftover 15-inch Mac...
B&H Photo has dropped prices on 2013 15″ Retina MacBook Pros by as much as $250 off original MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.3GHz Retina MacBook Pro: $2349... Read more
Updated MacBook Pro Price Trackers
We’ve updated our MacBook Pro Price Trackers with the latest information on prices, bundles, and availability on the new 2014 models from Apple’s authorized internet/catalog resellers as well as... Read more
Apple updates MacBook Pros with slightly fast...
Apple updated 13″ and 15″ Retina MacBook Pros today with slightly faster Haswell processors. 13″ models now ship with 8GB of RAM standard, while 15″ MacBook Pros ship with 16GB across the board. Most... Read more
Apple drops price on 13″ 2.5GHz MacBook Pro b...
The Apple Store has dropped their price for the 13″ 2.5GHz MacBook Pro by $100 to $1099 including free shipping. Read more
Apple drops prices on refurbished 2013 MacBoo...
The Apple Store has dropped prices on Apple Certified Refurbished 13″ and 15″ 2013 MacBook Pros, with model now available starting at $929. Apple’s one-year warranty is standard, and shipping is free... Read more
iOS 8 and OS X 10.10 To Support DuckDuckGo As...
Writing for Quartz, Dan Frommer reports that Apple’s forthcoming iOS 8 and OS X 10.10 operating systems version updates will allow users to select DuckDuckGo as their default search engine. He notes... Read more
U.K. Hospital Using iPods and iPads To Record...
British news journal GazetteLive’s. Ian McNeal notes that the old “an apple a day keeps the doctor away” proverb is being turned on its head at http://southtees.nhs.uk/hospitals/james-cook/ James... Read more
13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more

Jobs Board

Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.