Gestaltstorage
Volume Number: | | 9
|
Issue Number: | | 3
|
Column Tag: | | Pascal/Assembly
|
Related Info: Gestalt Manager
Gestaltstorage
A Global storage technique, using _Gestalt
By Alain Danteny, France
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
This article is merely a re-writing and translation of a previous article I submitted to Apple Frances DTS, which was published in the November 91-issue of La Lettre des Développeurs Apple. Its based on an idea by Franck Lefebvre (Hi, Frank): How to use Gestalt to store global informations, mainly in non-A5 worlds. Frank did it in C, while I propose it in MPW Pascal and Asm
1. Gestalt Was ist das?
System 6.0.4 introduced a new Trap, _Gestalt, that lets you read specific hardware or software configurations and settings: system version, QuickDraw version, etc.
You can use the Gestalt Manager through a single call to:
FUNCTION Gestalt(selector: OSType; VAR response: Longint): OSErr;
Error codes are sometimes returned:
gestaltUnknownErr = -5550;
{ value returned if Gestalt doesn't know the answer }
gestaltUndefSelectorErr = -5551;
{ undefined selector was passed to Gestalt }
gestaltDupSelectorErr = -5552;
{ tried to add an entry that already existed }
gestaltLocationErr = -5553;
{ gestalt function ptr wasn't in sysheap }
They are a lot of predefined read-only selectors available
More interesting, you can define your own private selector through the use of:
FUNCTION NewGestalt(selector: OSType; selectorFunction: ProcPtr): OSErr;
The heart of this routine is the selectorFunction parameter: you have to supply your own mechanism to retrieve your information. This routine is called by the Gestalt Manager using the following interface:
FUNCTION selectorFunction(selector:OSType; VAR response:Longint):OSErr;
You will probably write it using assembly, although this is not necessary.
For the purpose of GestaltStorage, we write the following routine :
;1
FUNC
EXPORT (__SelectorFunc,__EndSelectorFunc):CODE
;FUNCTION __SelectorFunc(selector:OSType;
;VAR response:Longint):OSErr;
__SelectorFunc
result EQU $10
selectorEQU $C
responseEQU $8
BRA.S Skip
Address DC.L0
;place holder for our address of storage structure
;could be anything else, provided a 4-byte length
Skip LINKA6,#0
CLR.W result(A6) ;init no error
MOVEA.Lresponse(A6),A0
MOVE.L Address(PC),D0
BNE.S GFExit
;if Address is $0 (meaning we already used and released
;this selector) then we return the address of the
;selectorFunction itself to re-use it properly
LEA __SelectorFunc,A1
MOVE.L A1,D0
;we return #$0100 as error code to let user know that the
;address of the selectorFunction is returned instead
MOVE.W #$0100,result(A6)
GFExit MOVE.L D0,(A0)
;otherwise, return global address
UNLK A6
MOVEA.L(A7)+,A0
ADDQ.W #$8,A7
JMP (A0)
__EndSelectorFunc;marker to end of routine
ENDFUNC
As you know by now, check out the Gestalt Manager chapter of Inside Mac Volume VI, or your interface files.
2. GestaltStorage
The idea behind the GestaltStorage project is to provide simple routines to create, set, get and dispose of private Gestalt selectors in your application.
Whenever you need to keep data global (for example, between two calls of an XCMD, a MDEF, a WDEF etc.), just create a new selector, fill it, use it and release it when the job is done (actually, when quitting).
Theres one thing you have to remember: Gestalt lives in the system heap, therefore everything you create there stays until the next boot.
The public routines of GestaltStorage are:
FUNCTION GSFreeStorage(selector:OSType):Boolean;
This function tests whether your private Gestalt selector already exists or not.
FUNCTION GSNew(selector:OSType;storageSize:Size; globalData:Ptr):OSErr;
This function allocate a new global storage structure in the System heap of type selector, stuffs the raw data pointed by globalData in it, for storageSize bytes long, and returns an error code (either Gestalt or Memory Manager).
There are three possibilities:
selector doesnt exist yet: we simply create it.
selector already exists, but doesnt point to data any longer: we reuse it and store new data, being aware of the previous storageSize field of the internal structure.
selector already exists and points to data: we overwrite those data for storageSize bytes long (no dynamic re-allocation!)
FUNCTION GSGet(selector:OSType;bucketPtr:Ptr):OSErr;
This function collects the data stored in the selector structure and stuffs them in bucketPtr. This pointer must be an already existing and valid pointer, or any structure pointed to with the @ operator (in Pascal).
FUNCTION GSRead(selector:OSType;offset,length:LongInt;
bucketPtr:Ptr):OSErr;
This is merely the same as GSGet, except that you can get partial data, provided you supply an offset from the beginning of your own structure and a length of bytes to read from.
FUNCTION GSSet(selector:OSType;globalData:Ptr):OSErr;
This routine lets you reset or modify your previously stored global data (through GSNew). globalData points to the new raw data. Only storageSize bytes will be copied
FUNCTION GSWrite(selector:OSType;offset,length:LongInt;
globalData:Ptr):OSErr;
Merely the same as GSSet, except that you can write partial new data, providing an offset and a length to write to.
FUNCTION GSDispose(selector:OSType):OSErr;
When youre done with the use of your own selector, this routine de-allocates any memory used in the System heap and set the global address to $0 (a longint).
(Notice that the selector is still alive, until next boot: theres no [official] way to unregister a Gestalt selector )
3. Optimizing GestaltStorage?
This mechanism is very simple, yet efficient for any data of constant size.
One could consider re-writting GestaltStorage to handle dynamic allocations, linked structures, etc. or implement such routines:
FUNCTION GSAppend(selector:OSType;moreSize:Size;
moreGlobalData:Ptr):OSErr;
FUNCTION GSInsert(selector:OSType;offset:LongInt;
moreSize:Size;moreGlobalData:Ptr):OSErr;
FUNCTION GSRemove(selector:OSType;offset:LongInt;
lessSize:Size):OSErr;
For the OOP folks, theres probably a good class to code here!
4. Tidbits
While sleuthing the MPW libraries, I discovered that the Gestalt Manager routines are embedded in a 500+-byte glue, for compatibility purpose: if you really know what your doing, you can use those un-glued routines:
;2
;FUNCTION __ReplaceGestalt(selector: OSType;
;gestaltFunction: ProcPtr;
;VAR oldGestaltFunction: ProcPtr): OSErr;
;smaller version without MPW glue : we assume _Gestalt is
;implemented <Juil 92> remove extra LINK and UNLK
;instructions refer to A7
__ReplaceGestalt FUNC EXPORT
error EQU $10
selectorEQU $C
newGF EQU $8
oldGF EQU $4
MOVE.L selector(A7),D0
MOVEA.LnewGF(A7),A0
_Gestalt ,Sys
MOVE.W D0,error(A7)
MOVEA.LoldGF(A7),A1
MOVE.L A0,(A1)
MOVEA.L(A7)+,A0
ADDA.W #$C,A7
JMP (A0)
ENDFUNC
;===========================================================
;FUNCTION __NewGestalt(selector: OSType;
;gestaltFunction: ProcPtr): OSErr;
;smaller version without MPW glue : we assume _Gestalt is ;implemented
<Juil 92> remove extra LINK and UNLK ;instructions refer to A7
__NewGestaltFUNC EXPORT
error EQU $C
selectorEQU $8
funct EQU $4
MOVE.L selector(A7),D0
MOVEA.Lfunct(A7),A0
_Gestalt ,Immed
MOVE.W D0,error(A7)
MOVEA.L(A7)+,A0
ADDQ.L #$8,A7
JMP (A0)
ENDFUNC
;===========================================================
;FUNCTION __Gestalt(selector: OSType;
;VAR response: LONGINT): OSErr;
;smaller version without MPW glue : we assume _Gestalt is ;implemented
<Juil 92> remove extra LINK and UNLK
;instructions refer to A7
__Gestalt FUNC EXPORT
error EQU $C
selectorEQU $8
responseEQU $4
MOVE.L selector(A7),D0
_Gestalt
MOVEA.Lresponse(A7),A1
MOVE.L A0,(A1)
MOVE.W D0,error(A7)
MOVEA.L(A7)+,A0
ADDQ.L #$8,A7
JMP (A0)
ENDFUNC
5. Comments
Ill be glad to hear your comments on AppleLink at DANTENY.
{3}
Listing GestaltStorage.p
UNIT GestaltStorage;
INTERFACE
USES Memtypes,QuickDraw,OSIntf,ToolIntf,GestaltEqu,
GestaltStorage_glue;
CONST
gsFromStart=0;
gsStorageSize=8;
TYPE
(* Key concept: through the use of Gestalt and our *)
(* private selector we store an address to a pointer *)
(* in system heap, and thus access it later, even *)
(* during interrupts tasks We could have stored the *)
(* address of the 'raw' pointer to data but we decided *)
(* to use the following structure for our data: *)
gsStoragePtr=^gsStorageData;
gsStorageData=RECORD
{kind of 'private' fields }
gsSize:Size; {size of the 'raw' data }
{address of the GestaltFunction}
gsFuncAddr:LongInt;
{kind of 'public' field }
{raw data goes here for gsSize bytes long}
END;
(* For Pascal uses,we defined that structure to mimic *)
(* the asm structure of the GestaltFunction. Forget *)
(* it otherwise *)
gsFuncPtr=^gsFuncData;
gsFuncData=RECORD
{entrypoint of asm routine}
BRAinstruction:integer;
gsStorageAddr:LongInt; {our DC.L storage}
{func code goes here as defined in the .a code}
END;
FUNCTION GSNew(selector:OSType;storageSize:Size;
globalData:Ptr):OSErr;
FUNCTION GSGet(selector:OSType;bucketPtr:Ptr):OSErr;
FUNCTION GSRead(selector:OSType;offset,length:LongInt;
bucketPtr:Ptr):OSErr;
FUNCTION GSSet(selector:OSType;globalData:Ptr):OSErr;
FUNCTION GSWrite(selector:OSType;offset,length:LongInt;
globalData:Ptr):OSErr;
FUNCTION GSDispose(selector:OSType):OSErr;
IMPLEMENTATION
FUNCTION GSNew(selector:OSType;storageSize:Size;
globalData:Ptr):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
theStorage:Ptr;
theGestaltFunc:Ptr;
theGestaltFuncAddr:LongInt;
oldStorageSize:Size;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=gestaltUndefSelectorErr THEN
{ An undefined selector was passed to Gestalt : it's OK }
{ for us to allocate a brand new storage component. }
BEGIN
theGestaltFunc:=GSNewFunction;
IF theGestaltFunc<>NIL THEN
BEGIN
theStorage:=GSNewStorage(storageSize,globalData);
IF theStorage<>NIL THEN
BEGIN
gsFuncPtr(theGestaltFunc)^.gsStorageAddr:=
ORD(theStorage);
gsStoragePtr(theStorage)^.gsFuncAddr:=
ORD(theGestaltFunc);
theErr:=__NewGestalt(selector,
ProcPtr(theGestaltFunc));
END
ELSE
BEGIN
gsStoragePtr(theStorage)^.gsFuncAddr:=0;
theErr:=memFullErr;
END;
END
ELSE theErr:=memFullErr;
END
ELSE IF theErr=$0100 THEN
{ value returned by the _Gestalt function if storage }
{ address is NIL (header) the selector is still valid, }
{ so we can re-use it in this case, the globalDataAddr }
{ longint holds the address of the gestalt function. }
BEGIN
theGestaltFuncAddr:=globalDataAddr;
IF theGestaltFuncAddr<>0 THEN
{probably paranoid }
BEGIN
theGestaltFunc:=Ptr(theGestaltFuncAddr);
IF gsFuncPtr(theGestaltFunc)^.BRAinstruction=$6004 THEN
BEGIN
theStorage:=GSNewStorage(storageSize,globalData);
IF theStorage<>NIL THEN
BEGIN
gsFuncPtr(theGestaltFunc)^.gsStorageAddr:=
ORD(theStorage);
gsStoragePtr(theStorage)^.gsFuncAddr:=
theGestaltFuncAddr;
theErr:=noErr;
END
ELSE theErr:=memFullErr;
END
ELSE theErr:=gestaltDupSelectorErr;
{really paranoid }
END
ELSE theErr:=gestaltDupSelectorErr;
END
ELSE IF theErr=noErr THEN
{an entry already exist: we OVERWRITE it with same Size}
BEGIN
oldStorageSize:=
gsStoragePtr(Ptr(globalDataAddr))^.gsSize;
BlockMove(globalData,Ptr(globalDataAddr+
SizeOf(gsStorageData)),oldStorageSize);
END
ELSE theErr:=gestaltDupSelectorErr;
{an unexpected error occurs: we safetely do nothing}
GSNew:=theErr;
END;
FUNCTION GSGet(selector:OSType;bucketPtr:Ptr):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=noErr THEN
WITH gsStoragePtr(Ptr(globalDataAddr))^ DO
BlockMove(Ptr(globalDataAddr+
SizeOf(gsStorageData)),bucketPtr,gsSize);
GSGet:=theErr;
END;
FUNCTION GSRead(selector:OSType;offset,length:LongInt;
bucketPtr:Ptr):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=noErr THEN
BlockMove(Ptr(globalDataAddr+
SizeOf(gsStorageData)+offSet),bucketPtr,length);
GSRead:=theErr;
END;
FUNCTION GSSet(selector:OSType;globalData:Ptr):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=noErr THEN
WITH gsStoragePtr(Ptr(globalDataAddr))^ DO
BlockMove(globalData,Ptr(globalDataAddr+
SizeOf(gsStorageData)),gsSize);
GSSet:=theErr;
END;
FUNCTION GSWrite(selector:OSType;offset,length:LongInt;
globalData:Ptr):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=noErr THEN
BlockMove(globalData,Ptr(globalDataAddr+
SizeOf(gsStorageData)+offSet),length);
GSWrite:=theErr;
END;
FUNCTION GSDispose(selector:OSType):OSErr;
VAR
theErr:OSErr;
globalDataAddr:LongInt;
globalData:Ptr;
BEGIN
theErr:=__Gestalt(selector,globalDataAddr);
IF theErr=noErr THEN
BEGIN
globalData:=Ptr(globalDataAddr);
IF globalData<>NIL THEN
BEGIN
gsFuncPtr(Ptr(gsStoragePtr(globalData)^.
gsFuncAddr))^.gsStorageAddr:=0;
DisposPtr(globalData);
globalData:=NIL;
END
ELSE theErr:=nilHandleErr;
END;
GSDispose:=theErr;
END;
END.