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