

|
Volume Number: | 4 | |
Issue Number: | 10 | |
Column Tag: | Advanced Mac'ing |
IAC Driver, Part II
By Frank Alviani, Contributing Editor, Paul Snively, Contributing Editor
Inter-Application Communication Under MutiFinder: The Continuing Saga,
or
Data, Data, Who’s Got the Data?
or
If One Application is the Master and the Other is the Slave, is the IAC Driver a Slave Driver?
Greetings, ladies and gentlemen, and welcome to yet another episode in the latest Macintosh development soap opera. In this segment, we ask the crucial question, “What does it actually take to make this puppy fly?” Luckily, Frank has spent a great deal of time and effort answering that question to his own satisfaction while I was out dancing with Shellie and Alice in Boston. (We should all be so lucky, I might add.)
Seriously, here is our attempt at cleaning up some of the mysteries surrounding how this driver we’ve written actually works. The definitive answer to that whole question can, of course, only be ascertained by careful examination of the source code, a herculean task at best.
Rather than put you, gentle reader, through that, here is our distillation of both what we wrote last month, and what is in the source code that accompanies this article (which is supposed to be reasonably short, since the code is far larger than anything MacTutor has ever published before).
Disclaimer!! This version of the IAC Driver is ß1!! We have had enough requests and inquiries to convince us to go ahead and release this early Beta version for people to experiment with, understanding that revisions are inevitable. THIS DRIVER UNDOUBTEDLY STILL HAS A FEW BUGS IN IT! Don’t commit all your eggs to the IAC basket just yet!!
First of all, the driver had to be remarkably transparent (read: drop it in your System Folder and forget that it exists). For that reason it is a Startup Document (for you pre-System 6.0 freaks, it’s an INIT file). Basically there’s a ridiculously simple little INIT in there that opens the DRVR in the system heap, makes sure it’s in the unit table, and then makes sure that it won’t go away when the INIT file is closed (by detaching the resource). Peter Helme of Mac DTS kindly provided a small chunk of code that finds the highest unoccupied slot in the unit table and renumbers the DRVR resource to ensure it is loaded there.
Once the driver is there, applications are free to check to see if it’s around and, if it is, to make use of it. The calls were documented last time as to name and function (sort of), so I’m going to try to focus on some of the not-so-obvious aspects of what goes on, and why (Frank is actually explaining the “why”; it’s his design, after all).
First of all, understand one important fact: inter-application communication under MultiFinder (or any other multitasking environment, for that matter) is neither more nor less than an exercise in memory management. The source application has to allocate storage for the transfer of data to the target application, and somehow the target application has to be made aware that the block of memory has something that it wants. Also, since the source data can be changed in a fairly dynamic manner relative to the target retrieving the data, you have to keep track of various versions or “editions” of the data somehow. All of this is the driver’s responsibility. It allocates and releases the storage (yes, Paul Sydney, it “does” release the storage), and it tracks editions as necessary.
A second matter to consider during program design is that inter-application communication is very much like network communication (only without the messy wires) and therefore similar problems arise, such as timing and coordination difficulties. If you’ve never dealt with those before, it’s going to prove very stressful for a while (although the problems aren’t really of the same magnitude).
Specification changes
Since the time the protocol was published in the August 1988 issue of MacTutor, a few small changes were found to be necessary in the specifications. Here they are:
1) The identifier for data types was changed from a 16-bit integer to the 4-character resource type used by everybody else (no, I don’t recall the good reason I had for choosing an integer in the first place).
2) The information returned by the Census call for an extent now includes the latest edition, since that is necessary for the application to be able to determine if it needs to read that extent or not.
3) When an extent is deleted by its source, the entry is removed from the table entirely (rather than just setting the edition to -1 as originally stated). Targets that try to access that extent in the future will get a “No such extent” error from the driver, and will know to not try to read it anymore (the extent should NOT be purged from the application’s internal tables, since re-opening the document in the future will re-establish the dependency).
Internal Data Structures
One of the keys to understanding any piece of code is a good grasp of the data structures it uses: how they are used and why they were chosen. The data structures used in the IAC driver are not terribly complex.
The extent table - is the heart of the driver. It is a fixed-length table that includes information about where an extent originates, who is interested in it, and what is the latest version of data that each target has read.
Since the entry for an extent is a fixed size structure, the entries for each edition are kept in a linked list. Editions are added to the list in chronological order, with the newest edition closest to the extent entry. Each edition in turn is a variable-size structure (the size of an extent’s data can of course vary, and the number of formats written to the driver can vary for each edition) and is therefore also a linked list. The data format identifier for each chunk of data is stored in the block with the data as a 32-bit header.
The interest mask - is used to identify targets for a source extent. The position in the document-ID table serves as a bit number in this mask (i.e. slot 2 is bit 2 in the mask). In the extent’s entry in the master table, the bits serve as identifiers of the target documents, and are only turned off by a target document severing a link. In the mask stored with each edition, the bits serve to record which documents have not yet read the data, and are turned off as soon as a target has read an edition. The read routine checks the interest mask for an edition after returning the data, and deletes the edition if no interested targets remain.
The document-ID table - is a table of unique document IDs. Every entry in this table is one of three types - Empty (zero), Target Document (1), or Source Document (a negative number). It is critical for every document interacting with the IAC driver to appear in this table, since the slot_ID is used to access the interest mask for extent entries; yet, document IDs are only assigned to source documents (a document that is only a target of others does not need to be identified by other than its slot_ID) - how to resolve this?
Target-only documents are those that do not pass a document ID to the “Complete Dependency” call; a dummy entry is put into the table to uniquely identify them for this session, and a slot_ID is assigned. Later, if they make an “Add Dependency” call and pass the existing slot_ID in (as they must under the protocol), the slot_ID is used to put the new document ID in the table, replacing the dummy entry. We take advantage of the fact that all date/times returned by the system are negative numbers to make this easy to process.
These data structures were designed for simplicity; they contain the minimum amount of information that will allow the driver to do its job. They do not, for example, maintain any information about the “boundaries” of extents, since that is highly application specific and impossible to anticipate; also, maintaining that material in the driver would result in excessive traffic between the IAC driver and applications.
A Routine Commentary
Most of the stuff from last time that concentrated on the interface specifications was explained fairly well, and I don’t wish to copy it here. Instead, I will provide my commentary on things that were perhaps not that clear. I’ll go routine by routine.
Open - it turns out to be impossible to check for MultiFinder at boot time, when this driver is opened. Therefore, it is the responsibility of the application to check and refuse to try and support IAC operations when MultiFinder isn’t running. The ‘C’ glue code provided does this check during the open call, so you shouldn’t need to.
AddDependency --What’s hard about this? Not much! Basically what it does is to add a new entry to the driver’s internal tables that are used to keep track of data that is being made available to interested applications. This is called by the source application.
There are 2 ways in which a source application can make this call: with a default document ID of zero, or with existing document and slot IDs (the driver has no way of checking up on you, and assumes that non-zero IDs are valid). The default case is easy - just give the document it’s IDs and send it home. If IDs are provided, however, life becomes complicated - there is the possibility of collisions with slot IDs already registered. If that occurs, a new slot ID is assigned and returned to the caller - this is why the slot ID returned from this call MUST ALWAYS be the one used in further communications with the driver.
A reminder that this call does NOT pass any data to the driver; an explicit “Write Data” call must be made.
CompleteDependency --This one is called by the “target” application, not the source! It tells the driver “hey, I’m interested in whatever’s available right now.” It basically lets the driver set up the “Interested mask” for the dependency properly. I’ll say a bit more about the “Interested mask” later.
RemoveDependency --This one is a bit trickier, as it can be called by either the source application or the target. If it’s called by the source, all editions of the data that have been posted get totally wiped, and the extent itself is deleted so that any targets that were interested get informed that the data has been blasted out from under them (they’ll get a “No such dependency” error). On the other hand, it may be a single target that’s calling. If that is the case, the appropriate bit in the “Master Interest mask” (and all editions) gets reset (that’s one less suitor, darling). When no one cares at all anymore (that is, the entire “Master Interest mask” goes to zero), the driver will blow away all of the editions just as it would if the source removed the dependency. Got all that? There’s a quiz next month.
A reminder that this call does NOT retrieve any data from the driver; an explicit “Read Data” call must be made.
AvailableDependency --I had a truly bad pun for this one, but I’ll resist the temptation. This is easy. It just makes the extent indicated by the parameters the source to all future defaulted “CompleteDependency”s until another dependency is made the available one. This has the highly desirable side-effect of making it trivial to have more than one target interested in the same source.
Status --Here’s a simple one, too. It just tells whomever is calling how many documents have been registered with the driver, and how many extents that exist are relevant to the document asking for the status (have editions that the target hasn’t read yet). Its real raison d’etre, though, is to tell the caller what version of the driver is installed (ahem).
Census --is like a high-powered Status. It doesn’t care who’s making the call. It just blasts out a list of all the extents that it knows of, period.
WriteData --this is rather like using the clipboard. The idea is that you may want to pass data in more than one format. A spreadsheet, for example, may wish to pass both the cell selection (let’s call that type CELL) and a pie chart of the selection (as a PICT). Remember, this call is made by the source after it has established a dependency. The master interest mask is copied from the extent table to the header for the new edition to establish which targets have yet to read the new data.
ReadData -- the target’s analog of WriteData, of course. It’s like SFGetFile inasmuch as it gives a list of preferred data types, in order of desirability. The target application makes this call, passing the most recent edition number of the data that it received last time. If there are one or more editions more recent than that, the driver will return the latest, marking the “Interested mask” for each successive edition as appropriate. It’s conceivable (in fact, all too likely for some applications, such as databases) that the target won’t be calling ReadData sufficiently frequently to get each and every edition that the source application posts, which is why we use editions in the first place. Reading an edition turns off the target’s bit in the edition-interest-mask, and all older editions. An interest mask of zero results in that edition being deleted.
Notes on the code
This was developed on a Mac Plus running MPW 2.0.2 under System 6.0 It should work on any machine running MultiFinder, although the technique used in the glue to determine if MultiFinder is actually running is not authorized by Apple. Their claim that “you should only check for services you need” ignores the fact that in this case the service needed is the multitasking capability. A few other matters...
First, none of the people involved with this project belong to the “Names-cost-a-dollar-a-letter” school of identifiers. Hence, the verbose identifiers and extensive comments. It was hard enough to debug with almost every line commented, believe me!
Second, the INIT resource is set up so that if you have the object to ShowInit by Paul Mercer, you can modify a couple of lines and link it in to display start-up icons. We didn’t have permission to print his source, and don’t like publishing software in MacTutor without every line of source needed to rebuild it, so this seems a workable compromise.
Third, some of the makefiles may have traces of the Odesta development environment showing through, even though I tried to “genericize” them. I hope they won’t be too hard to correct..
Bug Fixes and Updates
Inevitably, bugs will be found in this code, and we already have thought of several desirable improvements to add in the future (after we’re recovered from this exercise in hubris). It is not reasonable to expect Dave Smith and MacTutor to act as the central exchange for this driver, since the effort involved could become significant.
After January 1st, 1989, we will be acting as IAC Central, issuing revised versions as necessary, etc. Bug reports can be sent to us immediately (we’d like to think there will never be any, but...) along with suggestions for improvements. We can be reached by electronic mail (please, no phones calls!!) at the following addresses:
Paul Snively - GEnie: PSNIVELY
(The first line of any letter should be:
ATTN: PSNIVELY PAUL SNIVELY for the mail server he is using)
Frank Alviani - GEnie: TOOLSMITH
Delphi: TOOLSMITH
In addition, US Snail can reach us (slowly) at the following address:
CodeSmiths
P.O. Box 8744
Waukegan, Ill 60079
We will try and respond in a timely fashion, given the constraints of very demanding jobs, a house renovation, and small children.
That about wraps it up for our end. Here -- at long last!-- is the source code for the driver and the glue (in ‘C’) to simplify writing applications to support the protocol!
{1} Listing: driver.m # Makefile for the IAC Driver! # Frank Alviani -- 8/88 ob = {hlxEtc} #Set these definitions to your normal working directories sr = {hlxSrc} prog = {hlx} e = echo “{prog}SAWSInit” ƒ “{ob}SAWSdrvr.lnk”“{ob}SAWSInit” “{sr}SAWSINIT.r” {e} ‘Date -a -t‘ Rez IAC >> “{log}” Rez -t INIT -c SAWS -o “{prog}SAWSInit” “{sr}SAWSinit.r” “{ob}SAWSDRVR.a.o” ƒ “{sr}SAWSDRVR.a” {e} ‘Date -a -t‘ Asm SAWSDRVR.a >> “{log}” Asm “{sr}SAWSDRVR.a” -i Me:MPW:AIncludes: -o “{hlxetc}” “{ob}SAWSdrvr.lnk” ƒ “{ob}SAWSDRVR.a.o” {e} ‘Date -a -t‘ Link SAWSDRVR.a. >> “{log}” Link -t INIT -c SAWS -rt DRVR=31 -sg .IAC “{ob}SAWSDRVR.a.o” -o “{ob}SAWSdrvr.lnk” “{ob}SAWSINIT.a.o” ƒ “{sr}SAWSINIT.a” {e} ‘Date -a -t‘ Asm SAWSINIT.a >> “{log}” Asm “{sr}SAWSINIT.a” -i Me:MPW:AIncludes: -o “{hlxetc}” “{ob}SAWSInit” ƒ “{ob}SAWSINIT.a.o” {e} ‘Date -a -t‘ Link SAWSINIT.a >> “{log}” Link -rt INIT=0 -ra =16 “{ob}SAWSINIT.a.o” -o “{ob}SAWSInit”
{2} Listing: SysEnv.a ; ;Glue for SysEnvirons that was skipped by Apple ;F. Alviani ;7/88 ; INCLUDE‘TRAPS.A’;Include Memory manager macros. PRINT ON SYSENVIRONS FUNC EXPORT movea.l 4(sp),a0;a0 contains ptr to world record move.w 8(sp),d0 ;d0 contains desired version _SysEnvirons movea.l(sp)+,a0 ;get return addr, remove lea 6(sp),sp ;pop parameters move d0,(sp) ;put result in place jmp (a0);go home DC.B ‘SYSENVIR’ END
{3} Listing: IAC.h /* This is the set of externs needed to access the IAC interface routines */ /* The pointer/handle indicators are only reminders */ extern short iac_add_dependency(); /* *doc_ID, *slot_ID, *hat_check, *edition */ extern short iac_available_dependency(); /* doc_id, hat_check */ extern short iac_census();/* *extent_count, **extent_info */ extern short iac_complete_dependency(); /* *doc_id, *slot_id, *hat_check */ extern short iac_open(); extern short iac_read_data(); /* doc_id, slot_id, hat_check, *edition, fmt_pref[], *fmt_code, **ext_data */ extern short iac_remove_dependency();/* doc_id, slot_id, hat_check */ extern short iac_status();/* slot_id, *vers_id, *doc_count, *extent_count */ extern short iac_write_data(); /* doc_id, hat_check, *edition, fmt_count, **ext_data */ /* error codes for iac_open, etc. */ # define NO_DRIVER -1 # define EARLY_SYS -2 # define MAX_EXTS64 typedef struct { long doc_ID; short hat_check; short ed_level; }info_rec; typedef struct { info_rec ext_entry[MAX_EXTS]; } info_tbl, *info_tblP, **info_tblH;
{4} Listing: IAC.c /*** * * File:IAC.c * * Package: Inter Application Communications * * Description: interface package to the driver for the use of * application programs. * * Author(s): * FEA 6/19/88 * */ # include <types.h> # include <files.h> # include <memory.h> # include <osutils.h> # include <serial.h> # include <iac.h> # defineMIN_BLK_SIZE 0xC # defineDEBUG false static short iac_ref_num; /** * Routine: iac_open * * The IAC driver is opened by this call and the ioRefNum * saved so it doesn’t need to be passed with all calls. * A null value is returned if the open is successful; * otherwise the operating system error is * returned. */ short iac_open() { SysEnvRec theWorld; /* ALMOST complete knowledge about the world */ THzs_ZoneP, a_ZoneP; /* so we can check zone adjacency */ OSErr the_err; short result = 0; the_err = SysEnvirons(1, &theWorld); if (theWorld.systemVersion < 0x0420)/* too early! */ { result = EARLY_SYS; /* “no multifinder” */ } else { /*check for multifinder active by checking if system zone is adjacent to application zone, which never happens under MultiFinder. */ s_ZoneP = SystemZone(); a_ZoneP = ApplicZone(); if ( ((long) s_ZoneP->bkLim + MIN_BLK_SIZE) == (long) a_ZoneP ) { result = EARLY_SYS; /* zones adjacent - no multifinder */ } else /* now try to actually open the IAC driver */ { SysBeep(1); result = OPENDRIVER(“\p.IAC”, &iac_ref_num); } } return(result); } /** * Routine: iac_add_dependency * * This is used to inform the IAC driver that a new * dependency has been established and should be *added to its internal tables. A null value is * returned if the open is successful; otherwise the operating * system error is returned. */ short iac_add_dependency(doc_id, slot_id, hat_check, edition) long *doc_id; /* identifies the source document (permanent) */ short *slot_id; /* identifies the source document (session) */ short *hat_check;/* extent identifier */ short *edition; /* how many times extent has changed (session) */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short slot_id; short hat_check; short edition; } my_params; # if DEBUG return(the_err=noErr); /* short circuit testing without MF */ # endif my_params.func = 1; /* set up private parameter block */ my_params.doc_id = *doc_id; my_params.slot_id = *slot_id; my_params.hat_check = *hat_check; the_blk.ioCompletion = nil;/* setup driver parametr block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBWrite(&the_blk.qLink, false); /* add the dependency */ if (the_err == noErr) /* update output parameters */ { *doc_id = my_params.doc_id; *slot_id = my_params.slot_id; *hat_check = my_params.hat_check; *edition = my_params.edition; } return(the_err); } /** * Routine: iac_complete_dependency * * This is used to inform the IAC driver of a document that * is interested in a particular dependency. A null value is * returned if the open is successful; otherwise the * operating system error is returned. */ short iac_complete_dependency(doc_id, slot_id, hat_check) long *doc_id; /* identifies the source document (permanent) */ short *slot_id; /* identifies the source document (session) */ short *hat_check;/* extent identifier */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short slot_id; short hat_check; } my_params; # if DEBUG return(the_err=noErr); /* short circuit for testing without MF */ # endif my_params.func = 2; /* set up private parameter block */ my_params.doc_id = *doc_id; my_params.slot_id = *slot_id; my_params.hat_check = *hat_check; the_blk.ioCompletion = nil;/* setup driver parametr block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBWrite(&the_blk.qLink, false); /* complete the dependency */ if (the_err == noErr) /* update output parameters */ { *doc_id = my_params.doc_id; *slot_id = my_params.slot_id; *hat_check = my_params.hat_check; } return(the_err); } /** * Routine: iac_remove_dependency * * This is used to inform the IAC driver that a document is * no longer interested in a particular dependency. If the * document is the original source all “targets” will be * informed that there is no longer any such * extent; if all targets lose interest the source will be * informed that it no longer needs to update the extent. */ short iac_remove_dependency(doc_id, slot_id, hat_check) long doc_id; /* identifies the source document (permanent) */ short slot_id; /* identifies the source document (session) */ short hat_check; /* extent identifier */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short slot_id; short hat_check; } my_params; # if DEBUG return(the_err=noErr); /* short circuit testing without MF */ # endif my_params.func = 3; /* set up private parameter block */ my_params.doc_id = doc_id; my_params.slot_id = slot_id; my_params.hat_check = hat_check; the_blk.ioCompletion = nil; /* setup driver paramtr block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBWrite(&the_blk.qLink, false); /* remove the dependency */ return(the_err); } /** * Routine: iac_available_dependency * * This sets the indicated extent as the “available extent” *to be usedas the source for future defaulted *”complete_dependency” calls. */ short iac_available_dependency(doc_id, hat_check) long doc_id;/* identifies the source document (permanent) */ short hat_check; /* extent identifier */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short hat_check; } my_params; # if DEBUG return(the_err=noErr); /* short circuit for testing without MF */ # endif my_params.func = 4; /* set up private parameter block */ my_params.doc_id = doc_id; my_params.hat_check = hat_check; the_blk.ioCompletion = nil;/* setup driver parametr block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBWrite(&the_blk.qLink, false); /* this dependency is now “available” */ return(the_err); } /** * Routine: iac_status * * This allows the application program to find out what’s * going on. */ short iac_status(slot_id, vers_id, doc_count, extent_count) short slot_id; /* identifies the inquiring document (session) */ short *vers_id;/* driver version*100 */ short *doc_count;/* count of active documents */ short *extent_count; /* count of extents relevant to inquiring doc */ { IOParam the_blk; OSErr the_err; struct { short func; short slot_id; short vers_id; short doc_count; short extent_count; } my_params; # if DEBUG return(the_err=noErr); /* short circuit testing without MF */ # endif my_params.func = 5; /* set up private parameter block */ my_params.slot_id = slot_id; the_blk.ioCompletion = nil;/* setup driver parametr block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBRead(&the_blk.qLink, false); /* read IAC status */ if (the_err == noErr) /* update output parameters */ { *vers_id = my_params.vers_id; *doc_count = my_params.doc_count; *extent_count = my_params.extent_count; } return(the_err); } /** * Routine: iac_census * * This provides identifying info for all registered extents. */ short iac_census(extent_count, extent_info) short *extent_count; /* count of extents registered */ info_tblPextent_info; /* Ptr to table of info for each extent */ { IOParam the_blk; OSErr the_err; short i; struct { short func; short extent_count; info_rec extent_info[MAX_EXTS]; } my_params; # if DEBUG return(the_err=noErr); /* short circuit for testing without MF */ # endif my_params.func = 6; /* set up private parameter block */ the_blk.ioCompletion = nil;/* set up driver parameter block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBRead(&the_blk.qLink, false); /* read IAC status */ if (the_err == noErr) /* update output parameters */ { *extent_count = my_params.extent_count; BlockMove (&my_params.extent_info[0], (Ptr) extent_info, (long) my_params.extent_count * sizeof(info_rec)); } return(the_err); } /** * Routine: iac_write_data * * This updates the data for the specified extent, resulting * in a new change level. */ short iac_write_data(doc_id, hat_check, edition, fmt_count, ext_data) long doc_id; /* identifies source document (permanent) */ short hat_check; /* extent identifier */ short *edition;/* how many times extent has changed (session) */ short fmt_count; /* number of formats being written */ Handle ext_data;/* Handle to actual data */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short hat_check; short edition; short fmt_count; Handle the_dataH; } my_params; # if DEBUG return(the_err=noErr); /* short circuit for testing without MF */ # endif my_params.func = 7; /* set up private parameter block */ my_params.doc_id = doc_id; my_params.hat_check = hat_check; my_params.fmt_count = fmt_count; my_params.the_dataH = ext_data; the_blk.ioCompletion = nil;/* set up driver parameter block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params);; the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBWrite(&the_blk.qLink, false); /* update dependency */ if (the_err == noErr) /* update output parameters */ { *edition = my_params.edition; } return(the_err); } /** * Routine: iac_read_data * * This is used to retrieve the actual data for the latest * change_level for the specified extent. The IAC driver will * record that the inquiring document has read the data. * * ext_data will be resized by the driver to hold the data. */ short iac_read_data(doc_id, slot_id, hat_check, edition, fmt_pref, fmt_code, ext_data) long doc_id; /* identifies the source document (permanent) */ short slot_id; /* identifies the source document (session) */ short hat_check; /* extent identifier */ short *edition;/* how many times extent has changed (session) */ long fmt_pref[3]; /* preferred formats, descending desirability */ long *fmt_code; /* format returned to caller */ Handle ext_data;/* Handle to actual data */ { IOParam the_blk; OSErr the_err; struct { short func; long doc_id; short slot_id; short hat_check; short edition; long fmt_pref[3]; long fmt_code; Handle ext_data; } my_params; # if DEBUG return(the_err=noErr); /* short circuit for testing without MF */ # endif my_params.func = 8; /* set up private parameter block */ my_params.doc_id = doc_id; my_params.slot_id = slot_id; my_params.hat_check = hat_check; my_params.edition = *edition; my_params.fmt_pref[0] = fmt_pref[0]; my_params.fmt_pref[1] = fmt_pref[1]; my_params.fmt_pref[2] = fmt_pref[2]; my_params.ext_data = ext_data; the_blk.ioCompletion = nil;/* set up driver parameter block */ the_blk.ioRefNum = iac_ref_num; the_blk.ioBuffer = &my_params; the_blk.ioReqCount = sizeof(my_params); the_blk.ioPosMode = fsFromStart; the_blk.ioPosOffset = 0; the_err = PBRead(&the_blk.qLink, false); /* read the data */ if (the_err == noErr) { *edition = my_params.edition; *fmt_code = my_params.fmt_code; } return(the_err); }
{5} Listing: SAWSDRVR.a ;************************************************************** ;***** The SAWS Inter-Application Communication Driver ***** ;***** Written with blazing speed 6-8/88 by Paul F. Snively ;***** With one HECK of a lotta help from Frank Alviani *** ;***** and Mike Walsh ***** ;************************************************************** ; ;Modification History: ;6/19/88 First Draft--1.0D1--Paul F. Snively ;7/10/88 Second draft--1.0B1--Paul F. Snively & Michael R. ;Walsh ;8/7/88 Yet another crack at it--1.0B1--Paul F. Snively & ; Frank Alviani ;8/8-21/88 Final Debugging -- Frank Alviani ; ;Basically this puppy is the DRVR resource that actually ;implements Frank’s cool ideas that are documented in the ; article that accompanies this listing. ; INCLUDE‘Traps.a’ INCLUDE‘QuickEqu.a’ INCLUDE‘SysEqu.a’ INCLUDE‘SysErr.a’ INCLUDE‘ToolEqu.a’ STRING PASCAL NumOfDocs EQU 32;Max of 32 documents NumOfExtentsEQU 64 Unimpl EQU $9F ;For testing environment OSDisp EQU $8F DummyID EQU 1 NoMoreDocsEQU -2000 ;This one isn’t used--yet NoMoreSlots EQU -2001 ;Neither is this one--yet WriteFailed EQU -2002 ;Or this one... MissingLink EQU -2003;Yet another SAWS-defined error NoNewerEdition EQU -2004 ;YASAWSE ReadFailedEQU -2005 ;SASAWSE NoSuchDep EQU -2006 OldROMs EQU -2007 ExtentRec RECORD 0 DocID DS.L1;Home document for this extent HatCheckDS.W1 ;Unique ID for this extent Edition DS.W1 ;The edition number MstrInterestMsk DS.L1 ;Master Interest Mask EditionList DS.L 1 ;Edition List ExtentSizeEQU * ENDR EditionHdrRECORD 0 NextEd DS.L1 ;Handle to next Ed InterestMaskDS.L 1 ;Mask for this particular Ed NumFormatsDS.W 1 ;How many formats stored? EdInstances DS.L 0 ;Instances start here EditionSize EQU * ENDR IACGlobalsRECORD 0 ExtentCount DS.W 1 ;Number of extents extant ExtentTable DS.L NumOfExtents*ExtentRec.ExtentSize DocIDs DS.LNumOfDocs;Unique Document identifiers AvailDependency DS.L1 ;Offset of currently available dependency IACGlobalsSize EQU * ENDR DriverEntry PROC ; See Device Manager IM:2 IMPORT DriverOpen,DriverPrime IMPORT DriverCtl,DriverDone IMPORT DriverClose DC.B (1<<dReadEnable) + (1<<dWritEnable) ; We handle reads/writes DC.B 0 ; Lower byte is unused DC.W 0*60; 0 sec periodic update DC.W 0 ; We don’t do events DC.W 0 ; No menu for this accessory DC.W DriverOpen-DriverEntry; Open routine DC.W DriverPrime-DriverEntry ; Prime DC.W DriverCtl-DriverEntry ; Control DC.W DriverDone-DriverEntry; Status - unused DC.W DriverClose-DriverEntry ; Close DC.B ‘.IAC’;The name of our driver ALIGN 2 ; Word align ENDPROC DriverOpenPROC IMPORT IACGlobalsSize TST.W ROM85 ;old ROMs? BGE.S @1;no, keep going? MOVE.W #OldROMs,D0 BRA.S ExitOpen @1 with IACGlobals TST.L dCtlStorage(A1) ;Did we already allocate our space? BNE.S ExitOpen ;If so, then we’re already open MOVE.L #IACGLobalsSize,D0;How much global space do we want? _NewHandle sys,clear;Try to allocate it BNE.S ExitOpen ;Fail miserably since error occurred @2 MOVE.L A0,dCtlStorage(A1);Otherwise store handle MOVE.W #0,ExtentCount ;We start with zero extents endwith;IACGlobals ExitOpen RTS ;We’re outta here ENDPROC DriverClose PROC IMPORT WipeExtentData MOVE.L dCtlStorage(A1),D0;Get our global handle BEQ.S ExitClose;If we don’t have one, leave! MOVEA.LD0,A6 ;Otherwise get into address register with IACGlobals MOVE.W ExtentCount(A6),D7;How many extents are there? LEA ExtentTable(A6),A0 ;Point to the ExtentTable MOVE.L A0,D6 ;Get it in the right register endwith;IACGlobals WalkExtents MOVEA.LD6,A0 ;Get Extent Pointer JSR WipeExtentData ;Kill its data NextExtent with ExtentRec SUBQ.W #1,D7 ;Decrement extent counter BEQ.S EndClose ;And continue until done ADD.L #ExtentSize,D6 ;Point to next extent BRA.S WalkExtents;And loop endwith;ExtentRec EndClose MOVEA.LdCtlStorage(A1),A0;Get our globals handle _DisposHandle ;And get rid of it ExitClose RTS ;Back to caller ENDPROC ; ; This is the main entry point for ALL functions of the driver ; DriverPrime PROC IMPORT DriverAddDependency,DriverCompleteDependency IMPORT DriverRemoveDependency,DriverRemoveDependency IMPORT DriverAvailableDependency,DriverStatus IMPORT DriverCensus,DriverWriteData IMPORT DriverReadData MOVEA.LdCtlStorage(A1),A6;Get our global handle MOVEA.LioBuffer(A0),A4 ;Get the address of the “data” MOVE.W (A4),D0 ;Get the routine identifier ADD.W D0,D0 ;Multiply it by two MOVE.W RoutineTable(D0),D0 ;Get routine offset JSR RoutineTable(D0) ;Go to the proper routine MOVE.L JIODone,-(A7);wrap up RTS RoutineTable DC.W 0 ;1-based function codes... DC.W DriverAddDependency-RoutineTable DC.W DriverCompleteDependency-RoutineTable DC.W DriverRemoveDependency-RoutineTable DC.W DriverAvailableDependency-RoutineTable DC.W DriverStatus-RoutineTable DC.W DriverCensus-RoutineTable DC.W DriverWriteData-RoutineTable DC.W DriverReadData-RoutineTable ENDPROC ; ; This routine adds the source of a link to the dependency table ; DriverAddDependencyPROC IMPORT FindSlotID AddDepRec RECORD 0 Func DS.W1 docID DS.L1 slot_ID DS.W1 hatCheckDS.W1 edition DS.W1 AddDepRecSize EQU * ENDR MOVEA.L(A6),A2 ;Get globals pointer LEA IACGlobals.DocIDs(A2),A3;Point past end of extentTable LEA IACGlobals.ExtentTable(A2),A2;Point to base of extent table MOVEQ #0,D2 ;Clear our counter AddDepLoop TST.L ExtentRec.docID(A2) ;Is this slot available? BEQ.S FoundAvailSlot ;If so, we’re done looking ADD.L #ExtentRec.ExtentSize,D2 ;Maintain “available” offset ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next record CMPA.L A2,A3 ;Are we done? BGT.S AddDepLoop ;If not, look again AddDepFail MOVE.W #NoMoreDocs,D0 ;Return custom OSErr BRA AddDepExit ;And leave FoundAvailSlot MOVE.L AddDepRec.docID(A4),D0;Get the docID BNE.S ExistingDocID;Go if it already exists MOVE.L Time,D0 ;Else copy it from low RAM MOVE.L D0,AddDepRec.docID(A4);Update param block ExistingDocID MOVE.L D0,ExtentRec.docID(A2);Store the docID MOVE.L D0,D5; ;Save for later MOVE.L A2,D4 ;Save extent-rec addr for later MOVE.W AddDepRec.slot_ID(A4),D1 ;Did we get passed a slot_ID? BEQ.S NeedASlot;Go get one if not MOVE.L (A6),A3 LEA IACGlobals.DocIDS(A3),A3;Pt to table of unique IDs SUBQ.W #1,D1 ;Adjust index to 0-based LSL.W #2,D1 ;Convert to offset CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID target only? BEQ.S HaveASlot;Yes, put in real docID CMP.L 0(A3,D1.W),D5;ID in table OK? BEQ.S HaveASlot;Yes, use slotID as is NeedASlot BSR FindSlotID ;Get a valid slot ID BLT.S AddDepExit ;No slots - Leave HaveASlot MOVE.L D5,0(A3,D1.W);Save docID in slot MOVEA.L(A6),A2 ;Dereference global handle MOVE.W D0,AddDepRec.slot_ID(A4) ;Store the slot_ID MOVE.W AddDepRec.hatCheck(A4),D1 ;Is there already a hatCheck? BNE.S StuffHatCheckCalc ;If so, bypass calculation LEA IACGlobals.docIDs(A2),A3;Point past the end LEA IACGlobals.ExtentTable(A2),A2;Point to right place ; Search for highest hatCheck registered for this docID MOVEQ #0,D1 ;Initialize counter MOVE.L AddDepRec.docID(A4),D0;Get the docID again HatCheckLoop CMP.L ExtentRec.docID(A2),D0;Is the docID the same? BNE.S NotSame ;No, ignore CMP.W ExtentRec.hatCheck(A2),D1 ;Compare hatChecks BGE.S NotSame ;Go if no need for update MOVE.W ExtentRec.hatCheck(A2),D1 ;Save max hatCheck NotSame ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record CMPA.L A2,A3 ;Are we done? BGT.S HatCheckLoop ;Go if not ADDQ.W #1,D1 ;Bump to next hatCheck MOVE.W D1,AddDepRec.hatCheck(A4) ;Update param block StuffHatCheckCalc MOVEA.LD4,A2 ;Point to “working” extent MOVE.W D1,ExtentRec.hatCheck(A2) ;Store hatCheck MOVE.W #0,AddDepRec.edition(A4) ;Pass back edition zero MOVE.W #0,ExtentRec.edition(A2) ;Set edition zero MOVEA.L(A6),A2 ;Get globals pointer MOVE.L D2,IACGlobals.AvailDependency(A2) ;It’s also the available dependency ADD.W #1,IACGlobals.ExtentCount(A2) MOVE.W #noErr,D0;We succeeded! AddDepExit RTS ENDPROC ; ; This routine adds the destination to a link that’s been ; started. If a passed-in slot_ID conflicts with an existing ;entry, it is re-assigned DriverCompleteDependency PROC IMPORT FindSlotID CompDepRecRECORD 0 Func DS.W1 docID DS.L1 slot_ID DS.W1 hatCheckDS.W1 CompDepRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L CompDepRec.docID(A4),D0 ;Get the passed docID BNE.S HaveDocID;Go if we were passed docID MOVE.L IACGlobals.AvailDependency(A2),D1 ;Get available dependency LEA IACGlobals.ExtentTable(A2),A2;Point to extent records MOVE.L ExtentRec.docID(A2,D1.L),D0;Get the docID with CompDepRec MOVE.L D0,docID(A4) ;Return to sender HaveDocID MOVE.W slot_ID(A4),D1 ;Did we get passed a slot_ID? BNE.S ExistingSlotID ;Go get one if not MOVE.L #DummyID,D3;Use dummy ID in slot for now BRA.S NeedSlotID ;Go find slot to put it in ExistingSlotID MOVE.L (A6),A3 LEA IACGlobals.DocIDS(A3),A3;Pt to table of unique IDs SUBQ.W #1,D1 ;Adjust index to 0-based LSL.W #2,D1 ;Convert to offset CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID a dummy? BNE.S CheckSlotID;No, see if it’s OK MOVE.L D0,0(A3,D1.W);Yes, replace with real thing CheckSlotID CMP.L 0(A3,D1.W),D0;DocIDs match? BEQ.S HaveSlotID NeedSlotID BSR FindSlotID ;Get a valid slot ID BLT.S CompDepExit;failed... HaveSlotID MOVE.W D0,slot_ID(A4) ;Store slot_ID in caller’s block MOVE.L docID(A4),D3 endwith;CompDepRec MOVE.L D3,(A3,D1.W) ;Store docID MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L IACGlobals.AvailDependency(A2),D2 ;Get available dependency index LEA IACGlobals.ExtentTable(A2),A2;Point to extent records with ExtentRec MOVE.L MstrInterestMsk(A2,D2.L),D3;Use register to address all 32 bits BSET D0,D3 ;Set the interested bit MOVE.L D3,MstrInterestMsk(A2,D2.L);Back into table MOVE.L EditionList(A2,D2.L),D1 ;Get the “Edition List handle” BGT.S WalkEditions0;If it’s a handle, deal with it MOVE.W #NoSuchDep,D0;set error code BEQ.S CompDepExit;If it’s zero, we do nothing MOVE.L #0,EditionList(A2,D2.L) ;Otherwise give it no respect BRA.S CompDepExit endwith;ExtentRec WalkEditions0 MOVE.W ExtentRec.HatCheck(A2,D2.L),D3 MOVE.W D3,CompDepRec.hatCheck(A4) ;Return to caller WalkEditions MOVEA.LD1,A3 ;Copy the handle MOVEA.L(A3),A3 ;Dereference the handle with EditionHdr MOVE.L InterestMask(A3),D3 ;So we can use 32-bit mode BSET D0,D3 ;Set the appropriate bit MOVE.L D3,InterestMask(A3) MOVE.L NextEd(A3),D1;Get handle to next handle endwith;EditionHdr BNE.S WalkEditions ;If there is one, deal with it MOVE.W #noErr,D0;We succeeded! CompDepExit RTS ENDPROC ; ; This routine severs a link and removes it from the tables ; DriverRemoveDependency PROC IMPORT FindExtentByDandHC,WipeExtentData RemovDepRec RECORD 0 Func DS.W1 docID DS.L1 slotID DS.W1 hatCheckDS.W1 RemovDepRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L RemovDepRec.docID(A4),D0 ;Get the passed docID MOVE.W RemovDepRec.hatCheck(A4),D1;Get the passed hatCheck JSR FindExtentByDandHC ;Find the extent BNE.S RemovDepExit ;Use FEBAHDC’s errcode and scram. MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A2;Get table base MOVE.L ExtentRec.MstrInterestMsk(A2,D2.L),D3 ;Get interest mask MOVE.W RemovDepRec.slotID(A4),D4 ;Get the passed slotID BCLR D4,D3 ;Declare this slot uninterested BNE.S RemovTarget;If bit WAS set, target was doing the removing RemovSource ; Otherwise it’s the source. MOVEA.LExtentRec.EditionList(A2,D2.L),A0 ;Stuff list header into A0 JSR WipeExtentData ;Kill the extent’s data LEA IACGlobals.ExtentTable(A2),A2;Get table base with ExtentRec MOVE.L #0,DocID(A2,D2.L) ;Clear DocID MOVE.L #0,HatCheck(A2,D2.L);Clear HatCheck/Edition MOVE.L #0,MstrInterestMsk(A2,D2.L);Clear MstrInterestMsk MOVE.L #0,EditionList(A2,D2.L) ;Clear EditionList endwith;ExtentRec MOVE.W #noErr,D0;Set return code BRA.S RemovDepCleanup RemovTarget TST.L D3;Does anybody care? BEQ.S RemovSource;If no, then go to sleep MOVE.W #noErr,D0;Set return code RemovDepCleanup MOVE.W RemovDepRec.slotID(A4),D4 ;Get the passed slotID SUBQ.W #1,D4 LSL.W #2,D4 ;Turn into offset MOVEA.L(A6),A2 ;Dereference globals handle with IACGlobals SUBQ.W #1,ExtentCount(A2);One less extent in world LEA DocIDs(A2),A2;Point to table of unique IDs MOVE.L #0,0(A2,D4.W);Clear doc’s slot endwith;IACGlobals RemovDepExit RTS ;Bye-bye ENDPROC ; ; This routine marks a link source as “available for ; completion” ; DriverAvailableDependency PROC IMPORT FindExtentByDandHC AvailDepRec RECORD 0 Func DS.W1 docID DS.L1 hatCheckDS.W1 AvailDepRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L AvailDepRec.docID(A4),D0 ;Get the passed docID MOVE.W AvailDepRec.hatCheck(A4),D1;Get the passed hatCheck JSR FindExtentByDandHC ;Get dependancy if it exists BNE.S AvailDepExit ;If subr plotzed get out. MOVEA.L(A6),A2 MOVE.L D2,IACGlobals.AvailDependency(A2) ;Otherwise, we have a winner! MOVE.W #noErr,D0;Set return code AvailDepExit RTS ;Bye-bye ENDPROC ; ; This routine lets the client know what’s happening ; DriverStatusPROC StatusRec RECORD 0 Func DS.W1 slotID DS.W1 versID DS.W1 docCountDS.W1 extentCount DS.W 1 StatusRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.W StatusRec.slotID(A4),D0 ;Get slotID MOVE.W #100,StatusRec.versID(A4) ;Report version as ‘100’ LEA IACGlobals.AvailDependency(A2),A3;Point past end LEA IACGlobals.DocIDs(A2),A2;Point to extent table MOVEQ #0,D3 ;Clear our counter CountDoxLoop TST.L 0(A2) ;Is the docID for real? BGE.S CountDoxSkip ;If neg, then a modern date ADDQ #1,D3 ; so add 1 to the count CountDoxSkip ADDA.L #4,A2 ;Else point to next record CMPA.L A2,A3 ;Are we done? BGT.S CountDoxLoop ;If not, look again MOVE.W D3,StatusRec.docCount(A4) ;Report number of docs MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.DocIDs(A2),A3;Point past end LEA IACGlobals.ExtentTable(A2),A2;Point to extent table MOVEQ #0,D3 ;Clear our counter CountExtLoop with ExtentRec MOVE.L MstrInterestMsk(A2),D4;Fetch interest mask BTST.L D0,D4 ;Is this slot interested? BEQ.S CountExtSkip ;If not, then get next extent MOVEA.LEditionList(A2),A0;Get EditionList HANDLE endwith;ExtentRec FindExtLoop MOVE.L A0,D4 ;valid address? BEQ.S CountExtSkip ;No more editions, get out MOVEA.L(A0),A0 MOVE.L EditionHdr.InterestMask(A0),D4 BTST.L D0,D4 ;Is this edition interested? BEQ.S FindExtSkip;No, so check next extent ADDI.L #1,D3 ;Yes, add 1 and BRA.S CountExtSkip ; Get out FindExtSkip MOVEA.LEditionHdr.NextEd(A0),A0 ;Move next EditionList in BRA.S FindExtLoop;And test back there CountExtSkip ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next record CMPA.L A2,A3 ;Are we done? BGT.S CountExtLoop ;If not, look again MOVE.W D3,StatusRec.extentCount(A4) ;Report number of extents StatusExit MOVE.W #0,D0 ;Indicate exit OK RTS ENDPROC ; ; This routine returns a count of active programs, links, etc. to the client ; DriverCensusPROC Census1RecRECORD 0 docID DS.L1 edition DS.W1 hatCheckDS.W1 Census1RecSize EQU * ENDR CensusRec RECORD 0 Func DS.W1 extentCount DS.W 1 Census1 DS.BCensus1Rec CensusRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L #CensusRec.Census1,D4 ;Offset to write for output recs LEA IACGlobals.DocIDs(A2),A3;Point past end LEA IACGlobals.ExtentTable(A2),A2;Point to extent table MOVEQ #0,D3 ;Clear our counter CensusLoop TST.L ExtentRec.docID(A2) ;Check this extent’s docID BEQ.S CensusSkip ;If wrong, skip to next with ExtentRec MOVE.L docID(A2),Census1Rec.docID(A4,D4.L) ;Move docID MOVE.L Edition(A2),Census1Rec.edition(A4,D4.L) ;Move edition MOVE.L HatCheck(A2),Census1Rec.hatCheck(A4,D4.L) ;Move hatCheck endwith;ExtentRec ADDI.L #Census1Rec.Census1RecSize,D4;Move to next output rec ADDQ.W #1,D3 CensusSkip ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next record CMPA.L A2,A3 ;Are we done? BGT.S CensusLoop ;If not, look again CensusExit MOVE.W D3,CensusRec.extentCount(A4) ;Report extent count MOVE.W #noErr,D0;Nothing is wrong RTS ;Bye-bye ENDPROC ; ; This routine is called by the client to write data to the ; driver DriverWriteData PROC IMPORT FindExtentByDandHC WriteRecRECORD 0 Func DS.W1 docID DS.L1 hatCheckDS.W1 edition DS.W1 formatCount DS.W 1 theData DS.L1 WriteRecSizeEQU * ENDR FmtDataBlockRECORD 0 formatCodeDS.L 1 dataSizeDS.L1 myData DS.L1 ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L WriteRec.docID(A4),D0 ;Get the passed docID MOVE.W WriteRec.hatCheck(A4),D1 ;Get the passed hatCheck JSR FindExtentByDandHC ;Get dependancy if it exists BEQ.S TableSetup ;Get going. Otherwise, RTS ;subr plotzed leave w/o popping A1 TableSetup MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A2;Get table base MOVE.W WriteRec.formatCount(A4),D5;Get the passed formatCount EXT.L D5 ASL.L #2,D5 ;Make it an offset MOVE.L A1,-(SP) ;preserve dCtlStorage Ptr MOVEA.LWriteRec.theData(A4),A1 ;Get the passed theData MOVEA.LA1,A0 _HLock ;Freeze the user data MOVEA.L(A1),A1 ; And put the pointer in A1 MOVE.L #EditionHdr.EditionSize,D0 ;How much global space do we want? ADD.L D5,D0 ;Add format entries _NewHandle sys,clear;Try to allocate it (sys temporary) BNE WriteDataFailed; Couldn’t MOVE.L A0,D7 ;preserve MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A2;Get table base with ExtentRec ADDQ.W #1,Edition(A2,D2.L) ;Bump edition # MOVEA.LEditionList(A2,D2.L),A3 ;Get handle to new chain head MOVE.L A0,EditionList(A2,D2.L) ;Point to new head of chain MOVE.L MstrInterestMsk(A2,D2.L),D6;Need to copy into editionHdr endwith;ExtentRec MOVEA.LD7,A0 ;Retrieve editionHdr handle MOVEA.L(A0),A0 ;Hook list after new block MOVE.L A3,EditionHdr.NextEd(A0) ;Set EditionList->next MOVE.L D6,EditionHdr.InterestMask(A0) ;Remember who’s interested MOVE.W WriteRec.formatCount(A4),D6;Get the passed formatCount MOVE.W D6,EditionHdr.NumFormats(A0) ;set format count MOVEA.LD7,A0 _HLock ;Freeze new block MOVEA.L(A0),A3 ;Deref it OncePerFormat SUBI.W #4,D5 ;Knock down the format offset MOVE.L FmtDataBlock.dataSize(A1),D0 ;Get this block’s size ADDI.L #4,D0 ;Add room for the fmtcode MOVE.L D0,D6 ;Set up count SUBQ.L #1,D6 ;Adjust for DBRA _NewHandle sys,clear;Try to allocate it BNE.S WriteDataFailed ; Couldn’t MOVE.L A0,D6 ;Save handle to IAC data copy MOVE.L A1,D7 ;Save ptr to user data MOVEA.L(A0),A0 ;Deref the IAC copy block with FmtDataBlock MOVE.L dataSize(A1),D0 ;Get this block’s size MOVE.L formatCode(A1),(A0)+;Copy the format code MOVEA.LA0,A1 ;Dest pointer for BlockMove MOVEA.LD7,A0 ;Source pointer for BlockMove ADDA.L #8,A0 ;Skip hdr info _BlockMove MOVE.L D7,A1 MOVE.L dataSize(A1),D0 ;Get this block’s size endwith;FmtDataBlock ADDQ.L #8,D0 ;Allow for header ADD.L D0,A1 ;Reset A1 to next format block MOVE.L D6,A0 ;Get handle to IAC data copy MOVE.L A0,EditionHdr.EdInstances(A3,D5.L) ;Record handle in EditionList TST.L D5;Are we done? BNE.S OncePerFormat;Do it again MOVEA.LWriteRec.theData(A4),A0 ;Get the passed theData _HUnlock ;And unlock this puppy too MOVE.L ExtentRec.Edition(A2,D2.L),D4 MOVE.L D4,WriteRec.edition(A4) ;Update the edition MOVE.W #noErr,D0;We’re copacetic at this point WriteDataExit MOVEA.L(SP)+,A1 ;restore dCtlStorage RTS ;Bye-bye WriteDataFailed MOVE.W #WriteFailed,D0 ;So solly BRA.S WriteDataExit ENDPROC ; ; This routine is called by client to read data from the driver ; DriverReadData PROC IMPORT FindExtentByDandHC,IsFormatAvailable ReadRec RECORD 0 Func DS.W1 docID DS.L1 slotID DS.W1 hatCheckDS.W1 edition DS.W1 formatPrefDS.L 3 formatCodeDS.L 1 theData DS.L1 ReadRecSize EQU * ENDR MOVEA.L(A6),A2 ;Dereference globals handle MOVE.L ReadRec.docID(A4),D0;Get the passed docID MOVE.W ReadRec.hatCheck(A4),D1 ;Get the passed hatCheck JSR FindExtentByDandHC ;Get dependancy if it exists BNE ReadDataExit ;If subr plotzed get out. Otherwise, MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A3;Get table base MOVE.W ExtentRec.Edition(A3,D2.L),D4;Fetch latest stored edition CMPI.W #-1,D4 ;If edition not -1 BNE.S LinkStillValid ; Link is OK MOVE.W #MissingLink,D0 ; Else link was invalidated BRA ReadDataExit ;Go bye-bye LinkStillValid CMP.W ReadRec.edition(A4),D4;Has he seen my latest? BLE.S FetchLatestEdition MOVE.W #NoNewerEdition,D0;Yes, he has BRA ReadDataExit FetchLatestEdition MOVEA.LExtentRec.EditionList(A3,D2.L),A0 ;The handle for latest ed’n MOVEA.L(A0),A3 ;A3 -> latest ed’n _HLock ;Freeze this puppy LEA ReadRec.formatPref(A4),A2 ;Point to first format JSR IsFormatAvailable ;Is format 1 ok? BNE.S RenderMe ;If so, render it LEA 4(A2),A2 ;Point to second format TST.W (A2);If no format 2 BEQ ReadDataFailed JSR IsFormatAvailable ;Is format 2 ok? BNE.S RenderMe ;If so, render it LEA 4(A2),A2 ;Point to third format TST.W (A2);If no format 3 BEQ ReadDataFailed JSR IsFormatAvailable ;Is format 3 ok? BEQ ReadDataFailed ;If not, bail out RenderMe MOVE.L (A2),ReadRec.formatCode(A4);Set format code MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A2;Get table base MOVEA.LExtentRec.EditionList(A2,D2.L),A0 ;The handle for latest ed’n MOVE.L A0,-(SP) ;Save for later _HUnlock ;Dismiss it MOVE.L D7,A0 ;Put render handle in A0 _GetHandleSize ;How big are we? SUBQ.L #4,D0 ;Fudge size param for type code MOVE.L D0,D6 ;Save size param _HLock ;Freeze the EdInstance MOVEA.L(A0),A3 ;Deref it MOVEA.LReadRec.theData(A4),A0;Get output handle MOVE.L D6,D0 ;Get real size _SetHandleSize ;Make enough room MOVE.L A1,-(SP) ;Stack dCtlEntry MOVEA.L(A0),A1 ;Destination LEA 4(A3),A0 ;Source starts after formatCode MOVE.L D6,D0 ;Reload real size _BlockMove MOVE.L (SP)+,A1 ;Retrieve dCtlEntry MOVE.L D7,A0 ;Put render handle in A0 _HUnlock ;Let it go ; ; Now delete edition if no further interest ; KillEditionLoop TST.L (SP);Valid address? BEQ.S ReadDataOK ;Exit if not.. MOVEA.L(SP),A0 ;Reload handle to edition header MOVEA.L(A0),A3 ;A3 -> latest ed’n with EditionHdr MOVE.L InterestMask(A3),D5 ;Get interest mask MOVE.W ReadRec.slotID(A4),D0 ;Get bit position BCLR.L D0,D5 ;Clear interest bit MOVE.L D5,InterestMask(A3) ;Save updated mask BNE.S ReadDataOK ;Still interested parties, keep data MOVE.W NumFormats(A3),D7 ;Loop limit SUBQ.W #1,D7 ;Set up for DBRA LEA EdInstances(A3),A2 ;Point to base of table endwith;EditionHdr KillLoop MOVEA.L(A2),A0 ;Handle to data _DisposHandle ;Kill it LEA 4(A2),A2 ;Next entry DBRA D7,KillLoop MOVE.L EditionHdr.NextEd(A3),D5 ;Save Link MOVEA.L(SP),A0 ;Reload header handle _DisposHandle ;Kill it MOVE.L D5,(SP) ;Replace handle with link BRA.S KillEditionLoop ;Try removing earlier edition ReadDataOK MOVEA.L(A6),A2 ;Dereference globals handle LEA IACGlobals.ExtentTable(A2),A2;Get table base MOVE.L (SP)+,ExtentRec.EditionList(A2,D2.L) ;Relink handle for latest ed’n MOVE.W #0,D0 ;Indicate exit OK ReadDataExit RTS ReadDataFailed MOVE.W #ReadFailed,D0 ;So solly BRA.S ReadDataExit ENDPROC ; ; Unused entry points that must be defined for the header’s jump table ; DriverCtl PROC EXPORT DriverDone DriverDone MOVEQ #0,D0 ;Everything’s cool RTS ;Return to sender ENDPROC ; ;Many years later...the Subroutines!! ; ;Returns D0.W = slot_ID ; A3,D1.W pointing to available entry in docID list FindSlotIDPROC MOVEA.L(A6),A2 ;Dereference global handle LEA IACGlobals.docIDs(A2),A3;Point to list of docIDs MOVEQ #-4,D1 ;Starting index of zero MOVEQ #0,D3 ;Starting slot_ID SlotIDLoop ADD.W #4,D1 ;Bump to next index ADD.W #1,D3 ;Bump counter CMP.W #NumOfDocs+1,D3 ;Are we done yet? BEQ.S SlotIDFail ;If so, we can’t create slot_ID CMP.L (A3,D1.W),D0 ;Compare it to our docID BEQ.S StuffSlotID;Exit if we’re done TST.L (A3,D1.W);See if open slot BNE.S SlotIDLoop ;If not, try again StuffSlotID MOVE.W D3,D0 ;Move to result register SlotExit TST.W D0 RTS ;Return to sender SlotIDFail MOVE.W #NoMoreSlots,D0 ;Custom OSErr BRA.S SlotExit ENDPROC ; ;Looks for dep in extent table by matching docID and hatCheck ;ASSUMES THE FOLLOWING ;D0 = docID ;D1 = hatCheck ;A6 = globals handle [always] ;RETURNS ;D0 = noErr OR NoSuchDep ;D2 = desired offset ;We merrily destroy A2 and A3 during this routine ; FindExtentByDandHC PROC MOVEA.L(A6),A2 LEA IACGlobals.DocIDs(A2),A3;Point past end LEA IACGlobals.ExtentTable(A2),A2;Point to extent table MOVEQ #0,D2 ;Clear our offset FEBDAHCLoop CMP.L ExtentRec.docID(A2),D0;Check this extent’s docID BNE.S SkipToNextExtent ;If wrong, skip to next CMP.W ExtentRec.hatCheck(A2),D1 ;Check this extent’s docID BNE.S SkipToNextExtent ;If wrong, skip to next MOVE.W #noErr,D0;Nothing is wrong BRA.S FEBDAHCExit;Get out. SkipToNextExtent ADD.L #ExtentRec.ExtentSize,D2 ;Bump offset ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record CMPA.L A2,A3 ;Are we done? BGT.S FEBDAHCLoop;If not, look again FEBDAHCFail MOVE.W #NoSuchDep,D0;Return custom OSErr FEBDAHCExit TST.W D0 RTS ;Bye-bye ENDPROC ; ;This routine wipes out all data and formats thereof for a single extent ;ASSUMES ;A0 = current EditionList ;We merrily destroy A3,D0,and D5 (A2 restored from A6) ;RETURNS ;Nothing in particular WipeExtentData PROC with IACGlobals with ExtentRec MOVE.L A0,D0 ;Test handle to Edition List BEQ.S DoneExtent ;If none, do nothing MOVEA.LD0,A3 ;Keep Edition List handle NextEdition MOVEA.LA3,A0 ;Get Edition List handle _HLock ;Lock the Edition List handle MOVEA.L(A3),A0 ;Get pointer to Edition List with EditionHdr LEA EdInstances(A0),A2 ;Point to instance data MOVE.W NumFormats(A0),D5 ;Get counter of formats KillFormat MOVEA.L(A2),A0 ;Get the handle to the data _DisposHandle ;Get rid of it LEA 4(A2),A2 ;Point to next instance SUBQ.W #1,D5 ;Decrement the counter BNE.S KillFormat ;And do it again MOVEA.L(A3),A0 ;Dereference the Edition List handle MOVE.L NextEd(A0),D5;Get handle to next Edition MOVEA.LA3,A0 ;Restore handle _HUnlock ;Unlock it _DisposHandle ;Get rid of it TST.L D5;Is there a next Edition? BEQ.S DoneExtent ;If not, go to next extent MOVEA.LD5,A3 ;Otherwise move the handle BRA.S NextEdition;And loop endwith;EditionHdr endwith;ExtentRec endwith;IACGlobals DoneExtent MOVEA.L(A6),A2 ;Restore RTS ENDPROC ; ;Looks for an dep in the extent table by matching the docID and hatCheck ;ASSUMES THE FOLLOWING ;A2 = pointer to format(integer) ;A3 = pointer to Edition record ;RETURNS ;D7 = NIL OR handle to EdInstance ;We do not touch A2,A4 during this routine ;We merrily destroy A0,D4,D5 during this routine ; IsFormatAvailablePROC MOVE.W EditionHdr.NumFormats(A3),D5 ;Get number of formats available MOVE.L (A2),D4 ;Hold desired fmt code SUBI.W #1,D5 LSL.W #2,D5 ;Make it an offset MOVE.L #0,D7 ;Set return to NIL FormatCheck BLT.S FormatCheckDone ;D5 is now zero MOVEA.LEditionHdr.EdInstances(A3,D5.W),A0 ;Move handle to EdInstance MOVE.L A0,D7 ;Save in case it’s good MOVEA.L(A0),A0 ;Deref CMP.L (A0),D4 ;Are the formats the same?? BEQ.S FormatCheckDone ;If so, leave SUBI.W #4,D5 ;Check next one back BRA.S FormatCheck FormatCheckDone TST.L D7;Set the condition code first RTS ENDPROC END
{6} Listing SAWSINIT.a ;************************************************************** ;*****The SAWS Inter-Application Communication Driver Loader ;*****Written with blazing speed 6-7/88 by Paul F. Snively ;***** With one HELL of a lotta help from Frank Alviani ;************************************************************** ; ;Modification History: ;6/19/88 First Draft--Paul F. Snively ;7/10/88 Hopefully last draft--this SHOULD work-- ;Paul F. Snively ;8/21/88 Now searches for open slot from end of table-- ;Frank Alviani ;(Algorithm from Pete Helme, Apple MACDTS) ; ;Basically what this puppy does is to assume that there’s a ;DRVR resource lying around that happens to be named “.IAC” ;and, if there is, it loads it into the ;System Heap and opens it. Simple, huh? ; INCLUDE‘Traps.a’ INCLUDE‘QuickEqu.a’ INCLUDE‘SysEqu.a’ INCLUDE‘ToolEqu.a’ ;STRING ASIS successID EQU 128 failureID EQU 129 SAWSINIT: PROC IMPORT ShowINIT Frame RECORD4,DECR return DS.L1 A6Link DS.L1 theID DS.W1 theType DS.L1 name DS.B256 fSize EQU * ENDR LINK A6,#Frame.fSize;space for locals... ; Find an open slot in the driver table and load into that MOVE.W UnitNtryCnt,D2 ;How many slots? SUBQ.W #1,D2 ;Adjust MOVE.W D2,D1 ;Set up offset LSL.W #2,D1 MOVEA.LUTableBase,A0;Where’s the table? SrchLoop TST.L 0(A0,D1.W) ;Available? BEQ.S GotSlot ;Yup... SUBQ.L #4,D1 ;Drop Offset SUBQ.W #1,D2 ;Drop slot ID CMPI.L #39,D2 ;At bottom limit? BGT.S SrchLoop BRA.S BadNews ;No open slots in the inn ;Get resource by name GotSlot SUBQ.W #4,A7 ;Space for handle MOVE.L #$44525652,-(A7) ;’DRVR’ PEA DriverName _GetNamedResource MOVE.W ResErr,D0;Get it? BNE.S BadNews MOVE.L (A7)+,D7 ;Was there enough memory? BEQ.S BadNews ;Change ID to open slot MOVE.L D7,-(A7) PEA Frame.theID(A6);ID PEA Frame.theType(A6) ;theType PEA Frame.name(A6) ;name _GetResInfo MOVE.L D7,-(A7) MOVE.W D2,-(A7) PEA Frame.name(A6) ;name _SetResInfo ;Open the driver! CLR.W -(A7) ;Room for refnum PEA DriverName ;Point to driver name _OpenDeskAcc ;Open it MOVE.W (A7)+,D1 ;Pop refnum ;Ensure Driver undisturbed MOVE.L D7,-(A7) _DetachResource ;Restore previous slot in file MOVE.L #$44525652,-(A7) ;’DRVR’ PEA DriverName _GetNamedResource MOVE.L D7,-(A7) MOVE.W Frame.theID(A6),D0 MOVE.W D0,-(A7) MOVE.L #0,-(A7) ;leave alone name _SetResInfo MOVE.W #successID,-(SP) ;Stack success ICN# ID ShowICON: MOVE.W #-1,-(SP);Use standard pixel offset MOVE.L (SP)+,D0 ;TEMPORARY! POP OFF PARMS ;JSR ShowINIT ;Tell user that driver installed UNLK A6 RTS ;And that’s all, folks! BadNews: MOVE.W #failureID,-(SP) ;Tell user we failed miserably BRA.S ShowICON ;And leave DriverName: DC.B ‘.IAC’;The name of our driver ENDPROC END
{7} Listing SAWSINIT.r /* The Resource Description File for the IAC Driver Frank Alviani -- 8/88 */ include $$Shell(“hlxEtc”) “SAWSdrvr.lnk” ‘DRVR’ (31) as ‘DRVR’ (31,”.IAC”,sysheap,nonpurgeable); /* get DRVR resource */ include $$Shell(“hlxEtc”) “SAWSINIT”;/* get INIT resource */ /* ICN# s will go here asap */

- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine