[comp.sys.mac.programmer] Managing A5 globals from non-apps

tim@hoptoad.uucp (Tim Maroney) (04/13/90)

In article <1049@biar.UUCP> trebor@biar.UUCP (Robert J Woodhead) writes:
>Ok, so I need to add a custom button to the standard file dialog.  Thats easy -
>just copy the DLOG and DITL resources from the system file and edit them.  The
>DLOGHook routine is easy too.  No problem.  Except...
>
>The hook routine just gets the item number and a pointer to the dialog.  What I
>need to do is be able to set a flag so that my main program knows the extra
>button is pressed.  It would be lovely to be able to diddle the SFReply field
>but no can do apparently.  Which means i have to alter a global from within the
>hook routine.  Which means futzing with the A5-WORLD.
>
>Unfortunatley, the recommended way to do this seems  to change with every
>new set of tech-notes, and I for one have never been able to get it to work.
>
>Anyone run into this problem before and got a simple solution that works?

The only solution I know involves global storage of some kind or
another.  In applications, it's easy, because your hooks will be called
with the proper A5.  Otherwise, you need to use the techniques of Tech
Note #256 to maintain an A5 world; or, perhaps more cleanly but
probably less compatibly, you can store your "globals" in the
ApplScratch region.  Be sure to save the old contents and restore them
after returning from Standard File, so as not to interfere with an
application that's using ApplScratch.

The techniques of Tech Note #256 do work, sort of.  However, you may
find that you need not only globals, but a valid QuickDraw setup as
well.  If so, then allocate a pointer, not a handle, because the
QuickDraw global area is self-referencing.  (In fact, I think you
should *always* use a pointer rather than a handle.  Globals may point
to other globals, and if the area gets relocated, you're screwed.)  You
also need to add 206 bytes for the QuickDraw globals and adjust your A5
pointer accordingly.  You also need to SetPort each time you enter the
A5 world to make sure you're pointing at a valid port, and you need to
call InitGraf when you make it.  I recently pounded these routines into
shape, and this seems like as good a time as any to unleash them on the
world.  MPW C, natch, but they should be reasonably easy to translate
to Pascal.

#define QDglobSize 206
#define AppParmSize 32

long A5Size(void);
void A5Init(long globals);
Ptr MakeA5World(long *oldA5);
long SetA5World(Ptr A5Ref);
void DisposeA5World(Ptr A5Ref);
void RestoreA5World(long oldA5, Ptr A5Ref);
void StashA5(Ptr a5);
Ptr FetchA5(void);

Ptr MakeA5World(long *oldA5)
{	long size, newA5; Ptr A5Ref;
	if ((A5Ref = NewPtrClear(size = A5Size() + QDglobSize)) == 0)
		return 0;
	StashA5(A5Ref); /* only if you're keeping A5 in code-relative space */
	newA5 = ((long)A5Ref) + size - AppParmSize;
	A5Init(newA5);
	*oldA5 = SetA5(newA5);
	InitGraf(&qd.thePort);
	return A5Ref;
}

long SetA5World(Ptr A5Ref)
{	GrafPtr port; long oldA5;
	GetPort(&port);
	oldA5 = SetA5(((long)A5Ref) + A5Size() + QDglobSize - AppParmSize);
	SetPort(port);
	return oldA5;
}

void RestoreA5World(long oldA5, Ptr A5Ref)
{	SetA5(oldA5);
#pragma unused (A5Ref)
}

void DisposeA5World(Ptr A5Ref)
{	DisposPtr(A5Ref);
}

Note that unlike the TN#256 routines, you do *not* call SetA5World
after MakeA5World, which does it for you and returns the old a5.

Also, for INIT-installed trap patches, be sure you allocate your A5
world in the system heap, not the application heap.  You can either
change my NewPtrClear to a NewPtrSysClear, or set the zone to the
system zone before calling MakeA5World.  (Be sure to restore it for the
next INIT to use!)

You won't need the StashA5 call for a simple application like yours
where you only need temporary globals and you can keep track of the
A5Ref in a local variable in the routine that calls SFPGetFile.
However, for more sophisticated applications such as trap patches where
you don't have some higher-level caller keeping track of your A5 for
you, you need to use assembly language to keep a copy of the A5Ref in
code-relative space.  Here's the file to do that.  StashA5 is called
from MakeA5World.  Call FetchA5 to get your A5Ref before calling
SetA5World.  You'll need to use a slightly different calling sequence
(and name the routines in all capitals) for Pascal.

	CASE	ON

FetchA5	PROC	EXPORT
	EXPORT	StashA5
	bra.s	fetch
Stashed	dc.l	0
fetch	lea	Stashed,a0
	move.l	(a0),d0
	rts
StashA5	lea	Stashed,a0
	move.l	4(sp),(a0)
	rts
	ENDPROC

	END

All this code was actually written by Clarus's evil twin, Oscar.

>PS: It seems really STUPID that the dloghook routine in SFGetFile can't touch
>SFReply!

You got *that* right!  One of many deficient callback hooks in the Mac
OS, I'm afraid.

PS.  The MPW C 3.0 compiler sometimes generates A5-relative references for
string globals (probably string constants as well) at the beginning of a
routine, long before the code actually accesses it.  So, if the routine
sets up its own A5, the global reference will be bad, and you will be
singing "Las Cucarachas" or perhaps "This Is Radio Crash".  For this
reason, it is wise to use a front-ending approach in which the main
callback simply sets up the a5 world, calls the routine which accesses
all the global data, then restores the old A5 when that routine returns
to the main callback.
-- 
Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com

"I am of the opinion that my life belongs to the whole community, and as long
 as I live it is my privilege to do for it whatever I can." -- Shaw