TweetFollow Us on Twitter

MPW Data Exchange
Volume Number:6
Issue Number:7
Column Tag:Programmer's Forum

MPW Data Exchange

By John Calley, Columbia, MO

Multilingual MPW Programming

[John Calley works with Project BioQUEST, a grant funded by the Annenberg/CPB foundation, on educational simulations in biology for the Macintosh. His current projects are a Modula II based programmer’s toolkit for use by other BioQUEST programmers, and two instructional simulations in microbial genetics and mendelian genetics.]

One of the exciting things about the MPW environment is its standard object code format and standard linker that allows a single application to be written in several different source languages. Almost every MPW language offers greater or lesser support for this to the extent of allowing one to call procedures written in other MPW languages. Sometimes, however, this is not enough. In some cases you need to be able to access data declared by another language. In this article I would like to show how this can be done for a couple of pairs of MPW languages and in the process show you the techniques that will, I hope, allow you to do the same for your particular set of languages.

This problem arose for BioQUEST when we needed to use a commercially available graphing package (GrafPak from Invention Software) which is written in MPW Pascal. We work in Modula II, which has a parameter passing mechanism very similar to that of Pascal, so we did not anticipate any problems. We did have very few problems with procedure calls, but had a severe problem that we eventually traced to problems with shared global data. As this problem turns out to be the most complex of those I would like to consider here, I will defer discussion of it until the end.

Let’s look first at the problem of sharing global data declared in a Pascal unit with a program written in MPW C and a program written in Modula II. The Pascal unit (Test.p) is shown in listing one. It declares three integers (a, b, and c) to be externally visible and provides a couple of procedures that set and get the value of c. We provide the procedures as a check on our ability to operate directly on the variables since we assume that calling the procedures is easier to do. Listing two (Driver.p) is a Pascal driver that demonstrates accessing the variables declared by Test.p. It is interesting only as a comparison with the C and Modula programs that do the same thing.

Our primary tool in this project is the MPW tool DumpObj which allows us to see, in human readable form, exactly what the linker will be working with as it tries to combine our two pieces of code into one program. Figure one is the output of DumpObj applied to Test.p.o. The format of an object file is documented in appendix F of the MPW 2.0 manual. We are only interested in a few parts of it at the moment. The ‘Dictionary’ records are used to associate names with module ID numbers. Since DumpObj kindly prints the names wherever module ID numbers are used, we can ignore the dictionary for now. There are three ‘Modules’ defined here, one data module and two code modules. The data module is called ‘__Test’ and is 6 bytes long, 2 bytes for each of the integer variables we declared. The other two modules are code modules corresponding to each of the two procedures we declared. Notice that the Pascal compiler has converted all the names to upper case.

Dump of file test.p.o
First:      Kind 1 Version 1
Dictionary: FirstId 1
      1: TEST
      2: __TEST
Module:              Flags $09 ModuleId __TEST Size 6
EntryPoint:          Flags $09 Offset $0006 EntryId TEST
Dictionary: FirstId 3
      3: INITIALIZE
      4: Main

Module:              Flags $08 ModuleId INITIALIZE SegmentId Main
Reference:           Flags $90 RefId TEST Short Offsets
                      0008
Content:             Flags $08
Contents offset $0000 size $001C
000000: 4E56 0000      ‘NV..’   LINK       A6,#$0000
000004: 3B6E 0008 FFFE ‘;n....’ MOVE.W     $0008(A6),$FFFE(A5)
00000A: 4E5E           ‘N^’     UNLK       A6
00000C: 205F           ‘ _’     MOVEA.L    (A7)+,A0
00000E: 544F           ‘TO’     ADDQ.W     #$2,A7
000010: 4ED0           ‘N.’     JMP        (A0)
000012: C94E           ‘.N’     EXG        A4,A6
000014: 4954           ‘IT’     $$$$       
000016: 4941           ‘IA’     $$$$       
000018: 4C49 0000      ‘LI..’   DIVU.L     A1,D0

Dictionary: FirstId 5
      5: GETVAL
Pad         

Module:              Flags $08 ModuleId GETVAL SegmentId Main
Reference:           Flags $90 RefId TEST Short Offsets
                      0006
Content:             Flags $08
Contents offset $0000 size $0018
000000: 4E56 0000      ‘NV..’   LINK       A6,#$0000
000004: 3D6D FFFE 0008 ‘=m....’ MOVE.W     $FFFE(A5),$0008(A6)
00000A: 4E5E           ‘N^’     UNLK       A6
00000C: 4E75           ‘Nu’     RTS        
00000E: C745           ‘.E’     EXG        D3,D5
000010: 5456           ‘TV’     ADDQ.W     #$2,(A6)
000012: 414C           ‘AL’     $$$$       
000014: 2020           ‘  ‘     MOVE.L     -(A0),D0
000016: 0000           ‘..’         
Last        
End of file test.p.o

Figure 1. Output of DumpObj applied to Test.p.o

The data module, ‘__TEST’ has one entry point, named ‘TEST’. This is the name that other modules will use to refer to the data. Each of the two procedures, INITIALIZE and GETVAL, makes one reference to this data module, one to set the value of ‘c’ and one to read it. The linker will need to adjust each of these references after it has decided where to allocate space for the data module TEST. In order to tell the linker what it will need to modify, each of these code modules contains a ‘Reference’ record that the linker will use to connect the data reference to the actual data location. The reference record contains the name of the data module entry point referenced (actually its ID which is connected by the Dictionary to the actual name but DumpObj allows us to ignore this). The reference record also contains the offsets into the code module of all references to this data. In this case, there is only one reference, at offset ‘000B’ (for the first code module). At byte number ‘000B’ we see a reference to $FFFE(A5). This can be interpreted as, ‘If A5 points to the end of the data block, the data we need here will be at offset -2 from A5’. If we had referred to the variable ‘b’ instead of ‘c’, the value here would have been -4 instead of -2. The linker always puts global data like TEST at some negative offset from A5. When the linker has decided at what offset to put TEST, it will add that offset to this $FFFE and remove the reference record.

All we need to do to access this data from other languages is to produce references to TEST with the right offsets. We first consider how to do this from MPW C.

As we have seen, MPW Pascal collects all the public data in a unit into one data module. Individual pieces of data are then referred to by offsets into this data module. This means that other Pascal programs that need to access this data need to read the original unit source code so that they can figure out what offsets to use. C does not require that the source code be available for it to use data declared in other programs, so it must operate on a different principle. C creates a new data module for every variable that may be accessed by others, so that the others can access a variable by name directly without needing to know an offset.

Listing three is a C version of the driver. It declares one structure (record in Pascal) called TEST that it will look for externally. The linker will associate this with the ‘TEST’ entry point of the ‘__TEST’ data module declared by our Pascal routine. Unfortunately, however, Pascal accesses the variables in its global data block by negative offsets from the entry point ‘TEST’ (note that it starts at offset 6 from the beginning of the data module). C will expect to access the components of the structure ‘TEST’ by positive offsets from its beginning. This means that we cannot directly use TEST to get at the Pascal data. Instead, we declare a pointer to this data record and manually move it back so that it points at the beginning of the record instead of to the end. We can then use this pointer to directly set and look at the Pascal data.

Accessing the data from a Modula program presents a somewhat different problem. Modula, like Pascal, expects global data to be accessed as offsets into a data module. Unlike Pascal, however, Modula does not read the original source code in order to find a particular piece of data. Where MPW Pascal unit has two parts, the interface and the implementation, Modula has two separately compiled files which together define a module. The two parts are the definition module and the implementation module. A program which needs to use something defined by another module does not need to have access to its source code, it only needs access to the compiled form of the definition module. This compiled definition module is called the ‘symbol’ file. In order to access data declared in a Pascal unit we need to create a definition module that describes that data. Listing four is the Modula version of our driver program. Listing five is the definition module that will allow us to access the Pascal data.

After we compile the definition module (Test.def) and the driver module (Driver.mod) we can try to link them and see what happens. If we try a link we will get two error messages. One will complain that the linker was unable to find a definition for the reference to ‘Test__Globals’ and another complaining that it couldn’t find something called ‘Test__###########’. If we do a DumpObj of the compiled Driver.mod we will see that when it refers to the variables a, b, and c, it expects to find them in a data module called ‘Test__Globals’. As we know from our earlier examination of the Pascal object code, Pascal refers its global data by the name ‘TEST’. Fortunately the linker provides for solving this kind of problem with its ‘-ma’ or ‘module alias’ option which allows us to tell the linker to look for the name ‘Test__Globals’ under the alternate name ‘TEST’.

When we try to link again with the appropriate ‘-ma’ option the results are encouraging. We got rid of one of our two errors, but we still have this cryptic reference to ‘Test__#############’. This is a reference to the initialization code of the Test module. Since the Test module implementation does not actually exist, there is no initialization code. The strange numeric component of the name is a time stamp determined by the compilation time of the definition module. Every time the definition module is recompiled, this time stamp will change. This is Modula’s method of enforcing the requirement that if a definition module is changed and recompiled its corresponding implementation module must also be recompiled.

I chose to fix this by creating a dummy initialization procedure written in assembly language. Listing six is this dummy procedure. Note that whenever the definition module (Test.def) is recompiled we will have to change the name of this little dummy procedure and reassemble it.

If even the merest smidgeon of assembly code is an anathema to you, there is an alternative. If you create a Modula implementation module for Test, the appropriate initialization procedure will automatically be created by the compiler. Unfortunately, a data module corresponding to the global data we defined in the definition module will also be created automatically. This would not be a problem except that the linker’s ‘-ma’ option will not allow you to change the name of a reference if there really is a definition that corresponds to the reference. That is, when there was no data module called ‘Test__Globals’ it was willing to let us change references to that name to refer to the data module ‘TEST’. Now that such a module does exist, the references will refer to it instead of to the Pascal module. A solution to this problem is outlined below.

We are now ready to tackle the problem that originally caused me to confront the issues that we have discussed above. As I said at the beginning, we needed to use a commercially available set of routines that were written in MPW Pascal from our Modula II programs. My friend and colleague on the BioQUEST Project, Tom Schmidt, worked with me to write a Modula definition module that provided the needed glue to convert Modula’s parameters to the appropriate Pascal parameters. This was fairly easy to do as the differences in parameter passing methods are well documented. It was actually easier than the Modula example outlined above. Since we did not need to directly access any globally declared data in the Pascal units we could use a Modula Implementation module and didn’t have to use the assembly language hack used there.

Once we had written and tested our Modula interface to the Pascal code we began to use it from our programs. Almost all was well. We found that the use of certain Pascal procedures was catastrophic. If we used them, large parts of our graphs would disappear. Tom Schmidt was eventually able to determine that most of our problems occurred if the Pascal procedure we used made reference to the QuickDraw procedure ‘PenPat’. I wrote small Modula and Pascal programs that did nothing but make calls to PenPat, looked at the object code with DumpObj and realized that the problem was that they were referring to different sets of QuickDraw global variables. Since the standard pen patterns (i.e., ‘gray’) are all QuickDraw globals, this was the source of our problems. Since the main program was written in Modula it was initializing its set of QuickDraw globals (with the name ‘QuickDraw__Globals’). The Pascal code, on the other hand, was trying to use these globals under the name ‘QUICKDRAW’. Since this memory was uninitialized, when Pascal tried to set the pen pattern to gray, it was being set to white, and our graphs, on a white background, disappeared.

As we found above, we can’t use the linker’s ‘-ma’ option to solve this problem because both the data modules ‘QUICKDRAW’ and ‘QuickDraw__Globals’ actually exist. My solution to this dilemma was painful but instructive. I hope printing it here will provide you with the same instruction without the pain!

Listing seven is an MPW tool, FixPObj, which will read an MPW object file and modify all references to one name to refer to an alternate name. I ran the GrafPak libraries through this filter to change all references to ‘QUICKDRAW’ to references to ‘QuickDraw__Globals’ and this fixed the problem.

I used the description of the MPW object file format in appendix F of the MPW 2.0 manual as my bible in writing this tool. I am not yet fortunate enough to have MPW 3.0, so I don’t know what the new object file formats look like and what effect they might have on this tool. As a precaution, FixPObj checks the version number of the object file it is reading and issues a warning if it is greater than one.

Conceptually, the operation of FixPObj is simple. As we mentioned above, the actual names used in references appear only in dictionary records so all other kinds of records we simply pass on through to the output. When we find a dictionary record that contains the name we need to change, we modify the dictionary record to replace the old name with the new and write the modified dictionary record back out, following it with a pad record if necessary.

I hope this little discussion will be helpful and will encourage others to take advantage of MPW’s multilingual capability.

Modula II Note:

When I refer to Modula in this discussion I am referring to the Modula that until recently was available as TML Modula II from TML Systems. TML no longer carries Modula II but I am informed by the author that a new version will be available shortly from another publisher. The new version will address the issues I have mentioned above to make it easier to share data with other languages. I also own SemperSoft Modula II and have done the same exercise with it that I used TML Modula II for above. The primary differences are that SemperSoft refers to the global data by the name ‘Test@’ instead of ‘Test__Globals’, and that you have to use ‘-ma’ options for each of the externally defined procedures.

Listing One

UNIT Test;

INTERFACE

VAR
   a,
 b,
 c
    :integer;
 
PROCEDURE Initialize (value:integer);
(*
   Set the public variable <c> to <value>.
*)
FUNCTION GetVal:integer;
(*
   Return the current value of <c>.
*)

IMPLEMENTATION

PROCEDURE Initialize; 
(*
   Set the public variable <c> to <value>.
*)
BEGIN
   c := value;
END;

FUNCTION GetVal; 
(*
   Return the current value of <c>.
*)
BEGIN
   GetVal := c;
END;

END.
Listing Two

Program Driver;
USES Test;

BEGIN
   a := 1;b := 10;
 c := 100;
   WriteLn (‘Values set directly:’, a, b, 
 c, ‘.’);
   Initialize (25);
 WriteLn (‘Value set indirectly to 25 
 is ‘, GetVal);
END.
Listing Three

/*
 * File Driver.c
 */

#include<types.h>
#include <StdIO.h>

/*
   Declare the stuff in Test.p
*/
pascal void initialize (short) extern;
pascal short getval () extern;
/*
   The following structure acts as a reference to the data in the pascal 
module <test.p> but it cannot be referenced directly because C assumes 
that the address of a structure points at its beginning and pascal points 
to the end of its data block.
*/
extern struct testData {
 short a; 
 short b; 
 short c;
 } TEST;
 
struct testData *tData;
int main()
{
/*
   Adjust tData to point to the beginning of the Test global data block.
*/
/* -1 subtracts sizeof(TEST) */
 tData = (&TEST) - 1; 

 tData->a = 2;
 tData->b = 20;  
 tData->c = 200;  
 
 printf (“Values set directly: %d, %d, 
 %d.\n”, tData->a,
       tData->b, tData->c);
 initialize (98);
 printf (“Value set indirectly to 98 is 
 %d.\n”, getval());
 return 0;
}
Listing Four

MODULE Driver;

FROM Test IMPORT
   (*vars*) a, b, c,
 (*procs*) Initialize, GetVal;
FROM InOut IMPORT
   (*procs*) WriteString, WriteLn, WriteInt;
 
BEGIN
 a := 3; b := 30; c:= 300;
 WriteString (“Values set directly:”);
 WriteInt (a, 4);
 WriteInt (b, 4);
 WriteInt (c, 4);
 WriteLn();
 
 Initialize (107);
 WriteString (“Value set indirectly to 
 107 is  “);
 WriteInt (GetVal(), 1);
 WriteLn();
END Driver.
Listing Five

DEFINITION MODULE Test;
(*
   This is a Modula version of the INTERFACE part of the Pascal
unit ‘Test’.
*)
VAR
 a,
 b,
 c
    :INTEGER;
PROCEDURE GetVal ():INTEGER; EXTERNAL 
 PASCAL;
PROCEDURE Initialize (val:INTEGER); 
 EXTERNAL PASCAL;

END Test.
Listing Six

 TITLE  ‘Test.a --A Fake Implementation module for Test.def’
   CASE ON
 
Test__B72C5624C350 PROC    EXPORT
 RTS      
 ENDPROC
 END
Listing Seven

MODULE FixPObj;
(*
   Go through an object code file and change dictionary occurrences of 
one string to another string.  The default behavior is to change "QUICKDRAW" 
to "QuickDraw__Globals" which makes code generated by the Pascal compiler 
compatible with code generated by the Modula compiler.
   This utility can handle MPW object file formats 1 through 3 (MPW 2.0 
and 3.0).  If it is used on a later object file it will emit a warning 
message.

Arguments:
 --Input file name is required.
 --Output file name may be specified with 
 '-o' option, otherwisea default output 
 name of FixP.o will be used. The output 
 filename must be different from the input 
 file name.
 --Strings for substitution may be specified 
 with the '-s' option. i.e., 
 "-s QUICKDRAW=QuickDraw__Globals" 
 would specify the default behavior.
   
9/20/88
 --Written by John N. Calley   
4/24/89 JNC 
 --Updated for MPW 3.0 object file formats
 --Fixed CAP bug that prevented recognition of 
 options
 --Added spinning MPW cursor
 
*)
FROM CursorControl IMPORT
   (*procs*) SpinCursor;
FROM Diagnostic IMPORT
 (*procs*) WriteString, WriteCard, WriteLongInt, WriteInt, WriteLn;
FROM FileManager IMPORT
 (*types*) FInfo, 
 (*procs*) GetFInfo, SetFInfo;
FROM IntEnv IMPORT
 (*vars *) ArgC, ArgV, Exit;
FROM IntEnvIO IMPORT
 (*const*) InputFD, OutputFD, RDONLY, WRONLY, 
   CREAT, TRUNC,
 (*procs*) ErrNo, open, read, write, close;
FROM MacTypes IMPORT
 (*types*) Str255, StringHandle, OSErr;
FROM MemoryManager IMPORT
 (*procs*) NewHandle, DisposHandle, HLock, 
   HUnlock;
FROM Strings IMPORT
 (*procs*) Length, MakePascalString, Copy, Pos;
FROM SYSTEM IMPORT
 (*types*) ADDRESS,
 (*procs*) VAL, SHIFT, ADR, LONG;
FROM Utilities IMPORT
 (*procs*) Munger;
 
CONST
 defaultOutFile = "FixP.o";
 latestVersion = 3;   (* latest version of Obj
 file format understood *)
 pp = FALSE;  (* print progress  information *)
 
VAR
 inFileName,
 outFileName,
 inString,
 outString
    :Str255;
 
 inFile, (* File IDs for input and output 
 files *)
 outFile,
 status (* Status of last read or write 
 operation *)
    :LONGINT;
 
PROCEDURE PrintUsage();
(*
   Print usage statement.
*)
BEGIN
 WriteString ("# ");
 WriteString (ArgV^[0]^);
 WriteString (": Bad option or unable to open file.");
 WriteLn();
 WriteString ("# Usage: ");
 WriteString (ArgV^[0]^);
 WriteString (" [-s oldString=newString] [-o outFileName] inFileName");
 WriteLn();
END PrintUsage;

PROCEDURE SetOptions():BOOLEAN;
(*
   Set up input file name, optional output file name and optional string 
substitutions.  Return
TRUE if all options are interpretable, FALSE if there is an unrecognizable 
option or if no
input file name is given.
*)
VAR
    i, j
    :INTEGER;
   tempLength
      :INTEGER;
 optionsOK
    :BOOLEAN;
 equalPos (* Position of '=' in  substitution option *)
    :INTEGER;
BEGIN
(* Set defaults *)
 inFileName := "";
 outFileName := defaultOutFile;
(* Default substitutions *)
 inString := "QUICKDRAW";
 outString := "QuickDraw__Globals";
 optionsOK := TRUE;
 i := 1;
 WHILE i < ArgC DO
      IF ArgV^[i]^[0] = '-' THEN
 IF CAP(ArgV^[i]^[1]) = "O" THEN
 INC(i);(* next argument should 
 be output file name *)
 outFileName := VAL(Str255,
 ArgV^[i]^);
 ELSIF CAP(ArgV^[i]^[1]) = "S" THEN
 INC(i);(* next argument shoud be
 set of substitution strings *)
 equalPos := Pos ("=", ArgV^[i]^);
 IF equalPos = -1 THEN
    optionsOK := FALSE;
 WriteString ("No = sign");
 ELSE
 Copy (ArgV^[i]^, 0, equalPos, 
 inString);
 Copy (ArgV^[i]^, equalPos + 1,
 VAL(INTEGER, Length(ArgV^[i]^)) - 
 equalPos, outString);
 END; (*IF*)
 ELSE (* Unknown '-' option *)
 optionsOK := FALSE;
 WriteString ("Unknown -");
 END; (*IF*)
 ELSE 
 (* We assume it is the input file name *)
    inFileName := VAL(Str255, ArgV^[i]^);
      END; (*IF*)
 INC(i);
 END; (*WHILE*)
 RETURN (optionsOK);
END SetOptions;

PROCEDURE OpenFiles():BOOLEAN;
(*
   Open the files indicated by <inFileName> and <outFileName>. Return 
TRUE if the operations are successful, FALSE otherwise.
*)
VAR
   success
    :BOOLEAN;

BEGIN
 success := TRUE;
 IF Length(inFileName) = 0 THEN
    success := FALSE;
 ELSE
 inFile := open (inFileName, RDONLY);
 IF ErrNo() <> 0D THEN
    success := FALSE;
 END; (*IF*)
 END; (*IF*)
   IF Length(outFileName) = 0 THEN
    outFile := OutputFD;  (* Standard output *)
 ELSE
    outFile := open (outFileName, WRONLY +
 CREAT + TRUNC);
 IF ErrNo() <> 0D THEN
    success := FALSE;
 END; (*IF*)
 END; (*IF*)
 RETURN (success);
END OpenFiles;

PROCEDURE ReadWord (VAR value:CARDINAL);
(*
   Read the next two characters as a binary value and return that value.
*)
BEGIN
   status := read (inFile, ADR(value), 2D);
END ReadWord;

PROCEDURE WriteWord (value:CARDINAL);
(*
   Write out the indicated integer as two consecutive bytes.
*)
BEGIN
   status := write (outFile, ADR(value), 2D);
END WriteWord;

PROCEDURE WriteByte (value:INTEGER);
(*
   Write out the low byte of the indicated integer.
*)
BEGIN
   status := write (outFile, ADR(value) + 1D, 1D);
END WriteByte;

PROCEDURE ReadByte ():CHAR;
(*
   Read in one byte and return it as a character
*)
VAR
 tempChar
    :CHAR;
BEGIN
 status := read (inFile, ADR(tempChar), 1D);
 RETURN (tempChar);
END ReadByte;

PROCEDURE Pass (length:CARDINAL);
(*
   Pass through the indicated number of bytes from input to output. 
*)
VAR
   tempStr
    :Str255;
 tempLength
    :CARDINAL;
BEGIN
 WHILE length > 0 DO
    IF length > 256 THEN
    tempLength := 256;
 DEC(length, 256);
 ELSE
    tempLength := length;
 length := 0;
 END; (*IF*)
 status := read (inFile, ADR(tempStr),
 LONG(tempLength));
 status := write (outFile, ADR(tempStr)
 LONG(tempLength));
 END; (*WHILE*)
END Pass;

PROCEDURE ReadString (VAR string:Str255; VAR
 length:INTEGER);
(*
   Read the pascal formatted string from the input and return it in <string>. 
<length> is the length of the returned string.
*)
VAR
 inChar
    :CHAR;
BEGIN
 status := read (inFile, ADR(inChar), 1D);
 length := VAL(INTEGER, inChar);
 status := read (inFile, ADR(string),
 LONG(length));
(* null terminate the string *)
 string[length] := VAL(CHAR, 0);  
END ReadString;

PROCEDURE WritePString (string:Str255);
(*
   Write out the indicated string in pascal format.
*)
VAR
   tempStr
    :Str255;
BEGIN
 MakePascalString (string, tempStr);
 status := write (outFile, ADR(tempStr),
 LONG(VAL(INTEGER, tempStr[0]) + 1));
END WritePString;

PROCEDURE ProcessFirst();
(*
   Pass a First record through. Print a warning if the version number 
is late that the latest we know about (1).
*)
VAR
 version
    :CARDINAL;
BEGIN
 IF pp THEN
 WriteString ("First");
 WriteLn();
 END; (*IF*)
 WriteByte (1);
 Pass (1);
 ReadWord (version);
 WriteWord (version);
 IF version > latestVersion THEN
    WriteString ("# Warning: Unknown object
 file format version. ");
 WriteLn();
 WriteString ("# Output may not be correct.");
 WriteLn();
 END; (*IF*)
END ProcessFirst;

PROCEDURE ProcessLast();
(*
   Pass a Last record through.
*)
BEGIN
   IF pp THEN
 WriteString ("Last");
 WriteLn();
 END; (*IF*)
 WriteByte (2);
 Pass (1); 
END ProcessLast;

PROCEDURE ProcessComment();
(*
   Pass a comment record on through.
*)
VAR
 size
    :CARDINAL;
BEGIN
   IF pp THEN
 WriteString ("Comment record");
 WriteLn();
 END; (*IF*)
 WriteByte (3);
 Pass (1);
 ReadWord (size);
 WriteWord (size);
 Pass (size - 4);
END ProcessComment;

PROCEDURE ReadDict (dict:StringHandle; length:LONGINT);
(*
   Read <length> bytes from standard input into the handle <dict>.
*)
BEGIN
 HLock (dict);
 status := read (inFile, dict^, length);
 HUnlock (dict);
END ReadDict;

PROCEDURE ModifyDict (dict:StringHandle):BOOLEAN;
(*
   Substitute <outString> for the string <inString> in <dict>.  There 
will not be more that one occurrence. Return TRUE if a replacement was 
done, FALSE if no replacement occurred.
*)
VAR
   pInString,  (* <inString> and <outString> are 
 modula strings, we actually need *)
 pOutString (* to replace pascal format strings. *)
    :Str255;
 result
    :LONGINT;
BEGIN
 MakePascalString (inString, pInString);
 MakePascalString (outString, pOutString);
 result := Munger (dict, 2, ADR(pInString),
 LONG(VAL(INTEGER, pInString[0]) + 1),
 ADR(pOutString), LONG(VAL(INTEGER,
 pOutString[0]) + 1));
 IF result > 0D THEN
    RETURN(TRUE);
 ELSE
    RETURN(FALSE);
 END; (*IF*)
END ModifyDict;

PROCEDURE WriteDict (dict:StringHandle;
 length:LONGINT);
(*
   Write <length> bytes from <dict> to standard output.
*)
BEGIN
 HLock (dict);
 status := write (outFile, dict^, length);
 HUnlock (dict);
END WriteDict;

PROCEDURE ProcessDict();
(*
   Process a dictionary record. If the record defines the string <inString> 
then replace it
with the string <outString> and write out the modified dictionary record. 
 If it does not
contain <inString> write it out unchanged.

   Method:
 •Find out what the current length of the record is.  
 •Allocate a handle that is large enough for
 the record after the string has been
 changed.
 •Read the record into the handle.
 •Use Munger to perform the substitution if any.
 •Write the potentially modified record back out.
*)
VAR
   inChar
    :CHAR;
 length (* length of the dictionary record *)
    :CARDINAL;
 wasOdd (* TRUE if original dictionary record had an odd length *)
    :BOOLEAN;
 dict
    :StringHandle;
BEGIN
   IF pp THEN
 WriteString ("Dictionary");
 WriteLn();
 END; (*IF*)
   inChar := ReadByte(); (* This byte should 
 always be 0 *)
 ReadWord (length);  (* length of the dictionary record *)
 IF pp THEN
 WriteString ("Dictionary length is ");
 WriteCard (length, 4);
 WriteLn();
 END; (*IF*)
 wasOdd := ODD(length);
 dict := NewHandle (length + 
 Length(outString));
(* Compensate for the fact that we have already read 4 bytes of header*)
 length := length - 4;  
(* Read the dictionary into the <dict> handle *)
 ReadDict (dict, length);   
 IF ModifyDict (dict) THEN
    IF pp THEN
 WriteString("Changed: Old length = ");
 WriteCard (length, 4);
 END; (*IF*)
    length := length + Length(outString) -
 Length(inString);
 IF pp THEN
 WriteString ("  outString = '");
 WriteString (outString);
 WriteString ("'  length = '");
 WriteCard (Length(outString), 4);
 WriteString ("  inString = '");
 WriteString (inString);
 WriteString ("'  length = '");
 WriteCard (Length(inString), 4);
 WriteLn();
 WriteString("New Dictionary length=");
 WriteCard (length, 4);
 WriteLn();
 END; (*IF*)
 END;
 WriteByte (4);
 WriteByte (0);
 WriteWord (length + 4);
 WriteDict (dict, length);
 IF NOT (wasOdd = ODD(length)) THEN
    WriteByte (0);   (* Write a pad record *)
 END; (*IF*)
 DisposHandle (dict);
END ProcessDict;

PROCEDURE ProcessPad();
(*
   Acknowledge that a padding record has been read.
*)
BEGIN
   WriteByte (0);
   IF pp THEN
 WriteString ("Pad");
 WriteLn();
 END; (*IF*)
END ProcessPad;

PROCEDURE ProcessDataModule();
(*
   Pass a data module record on through.
*)
VAR
 moduleID,
 size
    :CARDINAL;
BEGIN
 ReadWord (moduleID);
 WriteWord (moduleID);
 ReadWord (size);
 WriteWord (size);
 IF pp THEN
 WriteString ("Data Module: ");
 WriteCard (moduleID, 4);
 WriteString ("size is ");
 WriteCard (size, 4);
 WriteLn();
 END; (*IF*)
END ProcessDataModule;

PROCEDURE ProcessCodeModule();
(*
   Pass a code module record on through.
*)
VAR
 moduleID,
 segID
    :CARDINAL;
BEGIN
 ReadWord (moduleID);
 WriteWord (moduleID);
 ReadWord (segID);
 WriteWord (segID);
 IF pp THEN
 WriteString ("Code Module: ");
 WriteCard (moduleID, 4);
 WriteString (" seg ID:");
 WriteCard (segID, 4);
 WriteLn();
 END; (*IF*)
END ProcessCodeModule;

PROCEDURE ProcessModule();
(*
   Pass a module record on through.
*)
VAR
 inChar
 :CHAR;
 flags
 :INTEGER;
 
BEGIN
 WriteByte (5);
 inChar := ReadByte (); (*flags*)
 flags := VAL(INTEGER, inChar);
 WriteByte (flags);
 IF ODD(flags) THEN
    ProcessDataModule();
 ELSE
    ProcessCodeModule();
 END; (*IF*)
END ProcessModule;
 
PROCEDURE ProcessEntryPoint();
(*
   Pass an entry point record on through.
*)
BEGIN
 WriteByte (6);
 Pass (7);
 IF pp THEN
 WriteString ("Entry Point");
 WriteLn();
 END; (*IF*)
END ProcessEntryPoint;

PROCEDURE ProcessSize();
(*
   Pass a size record on through.
*)
BEGIN
 WriteByte (7);
 Pass (5);
END ProcessSize;

PROCEDURE ProcessContents();
(*
   Pass a contents record on through.
*)
VAR
 size     (* Size of the contents record *)
    :CARDINAL;
BEGIN
 WriteByte (8);
 Pass (1);       (* flags *)
 ReadWord (size);
 WriteWord (size);
 IF pp THEN
 WriteString ("Contents:   size=");
 WriteCard (size, 4);
 WriteLn();
 END; (*IF*)
 Pass (size - 4);
END ProcessContents;

PROCEDURE ProcessReference ();
(*
   Pass a reference record on through.
*)
VAR
 size
    :CARDINAL;
BEGIN
   WriteByte (9);
   IF pp THEN
 WriteString ("Reference Record");
 WriteLn();
 END; (*IF*)
 Pass (1);      (* flags *)
 ReadWord (size);
 WriteWord (size);
 Pass (size - 4);
END ProcessReference;

PROCEDURE ProcessCReference ();
(*
   Pass a computed reference record on through.
*)
VAR
 size
    :CARDINAL;
BEGIN
 WriteByte (10);
 IF pp THEN
 WriteString ("Computed Reference Record");
 WriteLn();
 END; (*IF*)
 Pass (1);      (* flags *)
 ReadWord (size);
 WriteWord (size);
 Pass (size - 4);
END ProcessCReference;

PROCEDURE ProcessSymbolic (type:INTEGER);
(*
   Pass a symbolic record on through to the output.
*)
VAR
   size
    :CARDINAL;
BEGIN
 WriteByte (type);
 IF pp THEN
 WriteString ("Symbolic Record: type ");
 WriteInt (type, 1);
 WriteLn();
 END; (*IF*)
 Pass (1); (* flags *)
 ReadWord (size);
 WriteWord (size);
 Pass (size - 4);  (* Body of record data *)
END ProcessSymbolic;

PROCEDURE Dispatch (inChar:CHAR);
(*
  Decide who should process this and dispatch control to them.
*)
VAR
   type
    :INTEGER;
BEGIN
   type := VAL(INTEGER, inChar);
   CASE type OF
 0 :ProcessPad();                 |
 1 :ProcessFirst();               |
 2 :ProcessLast();                |
 3 :ProcessComment();             |
 4 :ProcessDict();                |
 5 :ProcessModule();              |
 6 :ProcessEntryPoint();          |
 7 :ProcessSize();                |
 8 :ProcessContents();            |
 9 :ProcessReference();           |
 10:ProcessCReference();          |
 (* Symbolic Records for MPW 3.0 *)
 11..19:ProcessSymbolic(type);    |
 ELSE
 (* 
 This happens when the byte past the 
 last byte of the file is read.  Ignore 
 it.
 *)
 END; (*CASE*)
END Dispatch;

PROCEDURE SetOutFileType();
(*
   We created a text file, we need to make it into an OBJ file so that 
the linker will accept it.
*)

VAR
   fInfo
    :FInfo;
 err
    :INTEGER;
BEGIN
 err := GetFInfo (outFileName, 0, fInfo);
 IF err = 0 THEN
 fInfo.fdType := 'OBJ ';
 err := SetFInfo (outFileName, 0, fInfo);
 END; (*IF*)
 IF err <> 0 THEN
 WriteString ("# Problem setting output
 file type to 'OBJ '");
 WriteLn();
 END; (*IF*)
END SetOutFileType;

BEGIN (*Main*)
   IF SetOptions() AND OpenFiles() THEN
 REPEAT
 SpinCursor (1);
 Dispatch (ReadByte());
 UNTIL status = 0D;
 SetOutFileType(); 
 Exit (0D);
 ELSE
 PrintUsage();
 Exit (1D);
 END; (*IF*)
END FixPObj.

 
AAPL
$116.31
Apple Inc.
+1.64
MSFT
$48.70
Microsoft Corpora
+0.48
GOOG
$534.83
Google Inc.
-2.16

MacTech Search:
Community Search:

Software Updates via MacUpdate

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

Latest Forum Discussions

See All

Weather or Not - Reports and Forecasts...
Weather or Not - Reports and Forecasts for your Calendar 1.0.0 Device: iOS iPhone Category: Weather Price: $2.99, Version: 1.0.0 (iTunes) Description: Weather or Not is a beautiful and intuitive way to check the weather and... | Read more »
Sago Mini Road Trip (Education)
Sago Mini Road Trip 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: Go for a fun-filled drive with Jinja the cat. Pick a destination, select a vehicle and hit the road. What will Jinja... | Read more »
New Tower Defense Game, Kingdom Rush: Or...
New Tower Defense Game, Kingdom Rush: Origins, is Available Today Posted by Jessica Fisher on November 20th, 2014 [ permalink ] iPad Only App - Designed for the iPad | Read more »
Sunburn! (Games)
Sunburn! 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Your ship is gone. Your crew is scattered. One option remains. Gather your crew... and jump into the sun. Reunite your... | Read more »
Tapventures Review
Tapventures Review By Jennifer Allen on November 20th, 2014 Our Rating: :: ODDLY COMPELLINGUniversal App - Designed for iPhone and iPad Tapventures is an increasingly hands-off one-tap RPG, but expect it to hook you despite your... | Read more »
Who Wore it Best? The Hunger Games: Girl...
With The Hunger Games: Mockingjay Part 1 out this weekend, Who Wore it Best? pits two Hunger Games tie-ins, Girl on Fire and Panem Run, against each other in a brutal and pointless fight to the death. I wonder where we got that idea from? | Read more »
Ironkill Review
Ironkill Review By Jennifer Allen on November 20th, 2014 Our Rating: :: LACKLUSTER PUNCHINGUniversal App - Designed for iPhone and iPad Ironkill is a freemium focused fighting game that doesn’t offer particularly thrilling fights... | Read more »
Real-Time Multiplayer Match-3 RPG Crusad...
Real-Time Multiplayer Match-3 RPG Crusaders Quest Set to Launch Next Month Posted by Ellis Spice on November 20th, 2014 [ permalink ] | Read more »
Checkpoint Champion Review
Checkpoint Champion Review By Jennifer Allen on November 20th, 2014 Our Rating: :: SPEEDY DRIFTINGUniversal App - Designed for iPhone and iPad Checkpoint Champion is a drift-focused racing game that’s ideal for short but fun gaming... | Read more »
MediaFire iOS 8 Native Update Brings New...
MediaFire iOS 8 Native Update Brings New “Power Upload” Feature to iPad and iPhone Posted by Jessica Fisher on November 20th, 2014 [ | Read more »

Price Scanner via MacPrices.net

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

Jobs Board

*Apple* Solutions Consultant (ASC)- Retail S...
**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
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.