IAC Driver 2
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, Whos 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 weve 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! Dont 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, its an INIT file). Basically theres a ridiculously simple little INIT in there that opens the DRVR in the system heap, makes sure its in the unit table, and then makes sure that it wont 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 its around and, if it is, to make use of it. The calls were documented last time as to name and function (sort of), so Im 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; its 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 drivers 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 youve never dealt with those before, its going to prove very stressful for a while (although the problems arent 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 dont 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 applications 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 extents 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 extents 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 dont wish to copy it here. Instead, I will provide my commentary on things that were perhaps not that clear. Ill 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 isnt running. The C glue code provided does this check during the open call, so you shouldnt need to.
AddDependency --Whats hard about this? Not much! Basically what it does is to add a new entry to the drivers 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 its 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, Im interested in whatevers available right now. It basically lets the driver set up the Interested mask for the dependency properly. Ill 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 its 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 (theyll get a No such dependency error). On the other hand, it may be a single target thats calling. If that is the case, the appropriate bit in the Master Interest mask (and all editions) gets reset (thats 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? Theres 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 Ill resist the temptation. This is easy. It just makes the extent indicated by the parameters the source to all future defaulted CompleteDependencys 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 --Heres 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 hasnt read yet). Its real raison detre, though, is to tell the caller what version of the driver is installed (ahem).
Census --is like a high-powered Status. It doesnt care whos 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 (lets 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 targets analog of WriteData, of course. Its 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. Its conceivable (in fact, all too likely for some applications, such as databases) that the target wont 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 targets 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 didnt have permission to print his source, and dont 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 wont 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 were 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 (wed 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
;
INCLUDETRAPS.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 doesnt 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 whats
* 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 Franks cool ideas that are documented in the
; article that accompanies this listing.
;
INCLUDETraps.a
INCLUDEQuickEqu.a
INCLUDESysEqu.a
INCLUDESysErr.a
INCLUDEToolEqu.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 isnt 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 dont 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 were 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 ;Were outta here
ENDPROC
DriverClose PROC
IMPORT WipeExtentData
MOVE.L dCtlStorage(A1),D0;Get our global handle
BEQ.S ExitClose;If we dont 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, were 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) ;Its 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 thats 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 its 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 callers
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 its a handle, deal with it
MOVE.W #NoSuchDep,D0;set error code
BEQ.S CompDepExit;If its 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 FEBAHDCs 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 its the source.
MOVEA.LExtentRec.EditionList(A2,D2.L),A0 ;Stuff list
header into A0
JSR WipeExtentData ;Kill the extents 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 docs 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 whats 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 extents 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; Couldnt
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 whos
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 blocks
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 ; Couldnt
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 blocks 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 blocks 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;Were 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 edn
MOVEA.L(A0),A3 ;A3 -> latest edn
_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 edn
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 edn
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
edn
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 headers jump table
;
DriverCtl PROC
EXPORT DriverDone
DriverDone
MOVEQ #0,D0 ;Everythings 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 cant create slot_ID
CMP.L (A3,D1.W),D0 ;Compare it to our docID
BEQ.S StuffSlotID;Exit if were 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 extents docID
BNE.S SkipToNextExtent ;If wrong, skip to next
CMP.W ExtentRec.hatCheck(A2),D1 ;Check this extents 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 its 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 theres 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?
;
INCLUDETraps.a
INCLUDEQuickEqu.a
INCLUDESysEqu.a
INCLUDEToolEqu.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;Wheres 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 thats 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 */