[comp.sys.mac.programmer] MPW Runtime startup, another method.

earleh@eleazar.dartmouth.edu (Earle R. Horton) (11/09/88)

     In the interests of reducing the amount of dead code in my programs
and general tidiness, I have developed the following scheme for use when
coding/linking MPW C programs which are to be used as standalone
Macintosh applications.  The techniques are not extremely well
documented in the MPW 2.0.2 documentation, and may even cease to work
with the introduction of MPW 3.0, but may be instructive nevertheless. 
The examples are all in C, but parallels almost certainly exist in the
Pacsal version of the libraries.  Recommended readings are the Segment
Loader and Memory Manager chapters of IM, and TechNote #93 and perhaps
TechNote #39 for 64k ROM compatibility.

     This scheme is for programs which do not use the standard IO
package in the C runtime library, but which do all file and device IO
through the ToolBox/OS.

Step 1:

     The example application in the CExamples folder on the MPW C
2.0.2 distribution disk is linked with "{CLibraries}"CRuntime.o and
"{CLibraries}"CInterface.o.  This causes the main entry point of the
program to be a function defined in CRuntime.o, which I think is named
CMain() and which looks something like this:

CMain()
{
	_DataInit();
	_RTInit();
	main();		/* Your main() function. */
	exit();
}

     Maybe RTInit() calls _DataInit().  It's not extremely important.
What is is that IF you do not use standard IO, then the only function
that needs to be called before your main() is _DataInit().  Also,
exit() need not be called after your main().  An ExitToShell() or even
a return will do nicely.

     Referring to Sample.c, we have:

main()
{
	/* Declarations. */

	UnloadSeg(_DataInit);

	/* Rest of code. */
}

     We can modify Sample.c so that just before the call to UnloadSeg()
we call _DataInit() ourselves, and link Sample using the following:

Link Sample.c.o "Q80:MPW:CLibraries:"CRuntime.o \
	"Q80:MPW:CLibraries:"CInterface.o \
	-o Sample -m main
                  ^^^^^^^

     The backslashes should, of course, be option-'d's.  If we do
this, then the Sample application file is now 710 bytes smaller than
the one produced by the standard method.  Not much savings, but
possibly enough to mean something to a small program.  Also, there are
presumably data structures which are set up by _RTInit() that we no
longer have in memory, and this means we get a little bit more RAM to
play with.

Step 2:	(Get radical.)

#define __SEG__ %A5Init
Croot()					/* <<-- Entry. */
{
	int i;
	extern main();
	_DataInit();	  /* All after this is defined as InLine macros. */
	ReleaseResource(GetResource('CODE',0));
	SetApplLimit((char *)&i- 10240L);	 /* Program needs stack! */
	if(IS_64K_ROM){
		MaxApplZone();
	}else{
		romMaxApplZone();			   /* Heap, too! */
	}
	jump(main);					    /* Bye, bye. */
}
#define __SEG__ Main
main()
{
	/* Declarations. */

	UnloadSeg(_DataInit);

	ReleaseResource(GetNamedResource('CODE',"\007%A5Init"));

	/* Rest of Stuff. */
}

     On the linker command line, "-m Croot" defines the Croot() function
as our entry point.  Some inline declarations which I have omitted for
the sake of brevity assure that all the calls made by Croot() are direct
traps, and do not cause the main segment to be loaded until we are done.
(In the case of 64k ROMs, all does not work as planned, but we still can
function.) This is actually used in a somewhat larger application than
Sample, but Sample can be set up this way.

     The first thing we do is call _DataInit().  This is necesary if we
use any initialized data.  Note that because of the __SEG__ directive,
we are at this point in the same segment as _DataInit(), which is also
'CODE' #1 and the first 'CODE' segment to be loaded after #0.  The
C library routines and Macintosh interface routines are now in segment
"Main," which is ID #2.  These have not been loaded yet.

     The second thing we do is to toss 'CODE' resource #0.  Once our
main entry point is called, this segment is done with its work, and is
just a relocatable block floating around in the heap.  It gets moved
from time to time as we request memory, and just adds to the clutter in
my opinion.

     Third, we call SetApplLimit(), and define the maximum extent of the
application heap.  See TechNote #93 for why this must be done now,
before we load any more 'CODE' segments.

     Fourth, we call MaxApplZone directly if we have 128k ROMs or newer.
This expands our application heap to its maximum extent.  It is crucial
to what I am doing here that all procedure calls made up to this point
be either in the "%A5Init" segment or inline macros.

     Finally in Croot(), the jump() macro does the following.  The
address of main() is placed into register A0.  The local stack frame
used by Croot() is unlinked, and the program jumps to what is in A0. 
Now, our main segment gets loaded, at the TOP of the application heap. 
The jump() macro looks something like this when compiled:

	lea	main,a0
	unlk	a6
	jmp	(a0)

     The result of calling main in this way is that now the stack is
fixed up so that the logical caller of my main() function is the same as
the logical caller of Croot(), and I can return from main() directly if
I don't want to use ExitToShell().  By the way, a return from main()
winds up at an ExitToShell() trap anyway.  Make sure that Croot() indeed
has a stack frame.  The use of the "&" operator on the variable "i"
should ensure this, but I compile with "-g" just to make sure.

     Once we get into main(), Croot has been removed from the calling
sequence by this clever trick, and 'CODE' segment #1 does not logically
need to be in RAM anymore.  So, we unload it, and then "ReleaseResource"
it to make sure.  UnloadSeg() does not remove 'CODE' segments from the
heap, it just makes them purgeable and unlocks them.  ReleaseResource()
turns the block occupied by the 'CODE' resource into a free block.

     The benefits of this scheme can be easily seen by using the "hd"
command of MacsBug after main() is called.  Instead of having 'CODE'
resource #1 locked at the bottom of the heap, we have this segment
completely purged, and only a few locked blocks at the bottom.  At the
top of the heap, all of our active 'CODE' segments are locked in place.

Notes:

     More initializations may be done in the Croot() routine, but these
should not allocate any nonrelocatable blocks.  This is because the
initialization segment is due to be purged, and is at the bottom of the
heap now.  We wouldn't want to allocate immovable storage on top of it,
thus fragmenting the heap.  Since a GrafPort is a nonrelocatable object,
there isn't a whole lot that can be done here in most programs, but this
depends on the application.

     Total code savings in the linked application is about 1k in most
cases, chiefly due to the loss of _RTInit(), _RTExit(), and exit(). 
Some data space is probably saved, also, but this is not a real big
space saver.  The chief virtue of this scheme to me is that the
structure of the application is somewhat cleaner than that of an MPW
program linked in the standard way.  Another virtue is that you, the
programmer, are slightly more in control of what library routines get
called by parts of your finished application.  Then again, if you ship
1000 copies of a program which is 1k smaller, then you have saved 1
Megabyte of storage in total.

     I don't know what the application heap looks like with a program
structured this way running on a 64k ROM machine.  Probably a mess.
Since I have all my 'CODE' segments except #1 unlocked in the
application file, anyway, compatibility with 64k ROMs is questionable.
If you need this compatibility, the LoadSeg patch discussed in
TechNote #39 could be used to fix things up.  Note, however, that
MaxApplZone() is not in the 64k ROMs, and some cleverness will be
needed to call it without getting the segment containing it locked
down in the middle of the heap.  I would have worked all this out, but
do not have access to a 64k ROM Mac.

     The stack size, 10240L, which I use here is adequate for one
program in which I use this scheme.  The exact value will have to be
tuned on an individual application basis.  The default set up by the
Segment Loader when launching a program is 8192L, which may be enough
stack in some cases.  I like to be conservative here, since it is not
hard to eat up stack.

     Apple folks:  If this scheme is going to blow up in the future,
please let me know.  Thanks.  I surmise that a similar scheme is used
in Finder 6.0.1, since I just had the thought to do a MacsBug heap dump
on the Finder's application heap, and 'CODE' resource #1 was nowhere to
be found.

     Actual code follows.  You can clip this out and paste it into your
own application if you like.  Don't forget to make Croot() the entry
point using "-m Croot" on the linker command line.

------------------------------cut here------------------------------
/*
 * Main entry point MPW C program.
 *
 * This routine is set up as the main entry point, and placed in the
 * same segment as _DataInit().  It calls _DataInit() to initialize
 * the a5-based data, then sets up the size of the application heap,
 * and jumps to the real main routine.  The main routine unloads this
 * segment, and all active segments are loaded into high memory, thus
 * reducing heap fragmentation.  In addition, we lose _RTInit(),
 * _RTExit(), and exit(), thus saving about 1k of code and an unknown
 * amount of storage devoted to device tables, IO buffers, etc.
 */
#include <Resources.h>
#include <Segload.h>
#define GetNamedResource GETNAMEDRESOURCE
/*
 * Inlines to make sure we don't load the library stuff too soon.
 */
pascal void PopA0(adr)
char *adr;
extern 0x205f;		/* move.l	(a7)+,a0 */
pascal void _SetApplLimit()
extern 0xA02D;
pascal void romMaxApplZone()
extern 0xA063;
pascal void JMPA0()	/* jmp	(a0) */
extern 0x4ED0;
pascal void UNLKA6()	/* unlk	a6 */
extern 0x4E5E;
#define SetApplLimit(a) PopA0(a);_SetApplLimit();
#define jump(a) PopA0(a);JMPA0();
#define __SEG__ %A5Init
#define IS_64K_ROM	((*((short *) 0x28E) & 0xF000) == 0xF000)
Croot()							       /* <<-- Entry. */
{
	int i;
	extern main();
	_DataInit();
	ReleaseResource(GetResource('CODE',0));
	SetApplLimit((char *)&i- 10240);	      /* Program needs stack! */
	if(IS_64K_ROM){
		MaxApplZone();
	}else{
		romMaxApplZone();				/* Heap, too! */
	}
/*
 * Further initialization may be done here, but do not cause any
 * nonrelocatable blocks to be allocated before this segment is
 * unloaded and purged.  Do not call Moremasters() here.
 */
	UNLKA6();					     /* Fix up stack. */
	jump(main);						 /* Bye, bye. */
}
#define __SEG__ Main
main()
{
	extern _DataInit();
	
	UnloadSeg(_DataInit);
	ReleaseResource(GetNamedResource("\007%A5Init"));
	
	/* Your code here. */	
}
Earle R. Horton. 23 Fletcher Circle, Hanover, NH 03755
(603) 643-4109
Sorry, no fancy stuff, since this program limits my .signature to three