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
$568.24
Apple Inc.
+43.49
MSFT
$39.74
Microsoft Corpora
+0.05
GOOG
$524.60
Google Inc.
-2.34

MacTech Search:
Community Search:

Software Updates via MacUpdate

Ember 1.5.1 - Versatile digital scrapboo...
Ember (formerly LittleSnapper) is your digital scrapbook of things that inspire you: websites, photos, apps or other things. Just drag in images that you want to keep, organize them into relevant... Read more
Cyberduck 4.4.4 - FTP and SFTP browser....
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
TechTool Pro 7.0.3 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
MacFamilyTree 7.1.6 - Create and explore...
MacFamilyTree gives genealogy a facelift: it's modern, interactive, incredibly fast, and easy to use. We're convinced that generations of chroniclers would have loved to trade in their genealogy... Read more
EtreCheck 1.9.9 - For troubleshooting yo...
EtreCheck is a simple little app to display the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support... Read more
TeamViewer 9.0.28116 - Establish remote...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
Viber 4.1.0 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device,... Read more
Apple iOS 7.1.1 - The latest version of...
The latest version of iOS can be downloaded through iTunes. Apple iOS 7 brings an all-new design and all-new features. Simplicity Simplicity is often equated with minimalism. Yet true simplicity is... Read more
1Password 4.3 - Powerful password manage...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Lens Blur 1.3.0 - True out-of-focus boke...
Let Lens Blur transform your existing photo into true SLR-quality out-of-focus bokeh effect! Everyone needs a gorgeous personalized background for a social profile, blog, Web/UI design, presentation... Read more

Latest Forum Discussions

See All

Soccer Rally 2 Review
Soccer Rally 2 Review By Carter Dotson on April 24th, 2014 Our Rating: :: GOALKEEPINGUniversal App - Designed for iPhone and iPad Soccer Rally 2 is the most serious vehicular soccer game ever created.   | Read more »
Galaxy Conquerors Review
Galaxy Conquerors Review By Jennifer Allen on April 24th, 2014 Our Rating: :: RETRO SHOOTINGUniversal App - Designed for iPhone and iPad Old school shooting is fun but inaccurate in Galaxy Conquerors.   | Read more »
Yomi Review
Yomi Review By Rob Thomas on April 24th, 2014 Our Rating: :: C-C-C-COMBO BREAKERiPad Only App - Designed for the iPad Round One – Fight! No quarters required for this iOS adaptation of a tabletop adaptation of the arcade fighting... | Read more »
Injustice: Gods Among Us Updated with Ne...
Injustice: Gods Among Us Updated with New Characters, Leaderboards, Gear, and Online Multiplayer Posted by Rob Rich on April 24th, 2014 [ | Read more »
Spin It Review
Spin It Review By Jordan Minor on April 24th, 2014 Our Rating: :: SPIN ME RIGHT ROUNDUniversal App - Designed for iPhone and iPad Spin It has a fine puzzle game model, but its execution lacks energy.   | Read more »
Productivity App NoteSuite is Having its...
Productivity App NoteSuite is Having its Biggest Sale Ever, Just for One Week Posted by Rob Rich on April 24th, 2014 [ permalink ] | Read more »
Wayward Souls Review
Wayward Souls Review By Carter Dotson on April 24th, 2014 Our Rating: :: CARRY ON, WAYWARD SONUniversal App - Designed for iPhone and iPad Wayward Souls is a roguelike-inspired action-RPG that sets a high bar for other games to... | Read more »
The Sandbox Gets Update, Receives New Ca...
The Sandbox Gets Update, Receives New Campaign and New Elements Posted by Tre Lawrence on April 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Football Management Simulator One For El...
Football Management Simulator One For Eleven Released Worldwide Today for iOS Posted by Simon Reed on April 24th, 2014 [ permalink ] Free-To-Play football management title One For E | Read more »
Leo’s Fortune Review
Leo’s Fortune Review By Jordan Minor on April 24th, 2014 Our Rating: :: FORTUNATE SONUniversal App - Designed for iPhone and iPad Leo’s Fortune delivers a platforming experience as creative and refined as any console game.   | Read more »

Price Scanner via MacPrices.net

Strong iPhone Sales Drive Apple Record March...
Apple on Wednesday announced financial results for its fiscal 2014 second quarter ended March 29, 2014. The Company posted quarterly revenue of $45.6 billion and quarterly net profit of $10.2 billion... Read more
Award-Winning NoteSuite Productivity App is $...
Minneapolis based Theory.io has announced an 80-Percent Markdown NoteSuite for iPad. NoteSuite helps users stay organized by capturing their notes, to-dos and documents in one organized place.... Read more
16GB 1st generation iPad mini available for $...
Radio Shack has a select number of refurbished 1st generation 16GB WiFi iPad minis available for $199.99 on their online store. Choose free shipping or free ship-to-store. We expect these to sell out... Read more
13-inch 2.5GHz MacBook Pro on sale for $100 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $1099 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
iPad Sales “Lull” A Reality Correction Of Unm...
I have lots of time for Jean-Louis Gassée, the former Apple Computer executive (1981 to 1990) who succeeded Steve Jobs as head of Macintosh development when the latter was dismissed in 1985. Mr.... Read more
Apple Makes OS X Betas Available To All – Wit...
Apple’s OS X Beta Seed Program, which lets you install the latest pre-release builds, try it out, and submit your feedback, is now open to anyone who wants to sign on rather than to developers and... Read more
Apple Releases iOS 7.1.1 Update
The latest iOS 7.1.1 update contains improvements, bug fixes and security updates, including: • Further improvements to Touch ID fingerprint recognition • Fixes a bug that could impact keyboard... Read more
Logitech Announces Thinner, Lighter, More Fle...
Logitech has announced an update to its Ultrathin for iPad Air, iPad mini and iPad mini with Retina display, improving the flexibility and design of its award-winning predecessor with an even thinner... Read more
Logitech Introduces Hinge, Big Bang and Turna...
Logitech has announced expansion of its tablet product line with three new cases – the Logitech Hinge, the Logitech Big Bang and the Logitech Turnaround – each for the iPad Air, iPad mini and iPad... Read more
WaterField’s Rough Rider Leather Messenger Ba...
WaterField Designs have announced the new 15-inch size of their popular Rough Rider leather messenger bag, a vintage-looking bag that combines Old West charm and ruggedness with distinctly modern... Read more

Jobs Board

*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
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of 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* Inc. Research Data Specialist - Appl...
…of Worldwide Market Research & Intelligence. The team is responsible for conducting Apple branded consumer market research. It is also responsible for analyzing data Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.