Both MOA clients and Xtras are intended be written in C or C++. The MOA Services Library itself is written in ANSI C, so that MOA can be implemented on platforms that don't support C++ compilers.
The MOA object system uses pointer-based allocation for internal data structures, since much of the library code is core, and MOA doesn't require a lot of memory in the first place. Note that all memory allocators will be configurable to allow for alternate memory managers (e.g., SmartHeap). Note also that every MOA application will provide Xtras with access to an implementation of the IMoaHandle interface for allocating relocatable memory.
The overhead for a MOA method call is identical to that of a C++ virtual function call. The calling technique is call-by-pointer, with an extra level of indirection. The performance impact is typically negligible for all but the tightest of inner loops. To look at some code comparisons, a typical direct C function call such as:
MyFunct();
compiles into the 68K assembly language call:
JSR 0x00000000 ; some address
A comparable MOA method call in C such as:
foo->lpVtbl->MyFunct(foo) /* C notation */
or in C++:
foo->MyFunct() /* C++ notation */
compiles into the assembly language call (again, 68K):
MOVE.L foo,-(A7) ; move first arg onto stack
MOVEA.L foo,A0 ; move foo into a register
MOVEA.L (A0),A0 ; get the vtable
MOVEA.L $000C(A0),A0 ; get the particular function to call
JSR (A0) ; call it
The last instruction is the equivalent to a standard C function call. You can optimize a method call down to a single instruction in a loop by getting the function address first, then calling it directly within the loop. For example:
void (*MyFunctProc)(void) = foo->lpVtbl->MyFunct;
while (tightloop) {
(*MyFunctProc)(foo);
}
In contrast, the speed cost of actually instantiating an interface is a bit more significant than creating a C++ object (you don't really want to do either one in speed-critical loops, naturally).
The memory overhead for MOA objects and interface instances is:
* Overhead per object instance: 20 bytes + instance variables
* Overhead per interface instance: 8 bytes
It is most efficient to keep an object in memory without any interfaces instantiated other than IMoaUnknown. The IMoaUnknown interface is part of the base overhead of the object and can be used to instantiate any other interfaces at appropriate times. To ensure they are removed from memory, call Release() on interfaces you acquire through QueryInterface() when you are through using them.
Class and interface definitions also have some memory overhead. The figures for Mac MOA are (very) approximately:
* Overhead code per Xtra: ~1800 bytes
* Overhead per class definition: ~100 bytes
* Overhead per interface definition: ~400 bytes
In any environment, all structure-alignment must be carefully specified. MOA assumes "native" structure alignment in all cases: PPC code will assume PPC alignment, etc. However, it is strongly recommended that all structures should be long aligned in all cases, with explicit padding to force things to the proper boundary (i.e., structure definitions should not rely on compiler behavior to insert padding or correct alignment problems). This will make for easier portability, less risk of mismatched structures, and better performance on most modern machines.
MPW C promotes all types to long-size before passing them as parameters, while Think and Metrowerks do not. Microsoft's COM calling conventions require C calling conventions for interface methods, thus MOA API is exposed to this problem. For this reason, the arguments to all methods of all interfaces must be exactly 4 bytes in length. Smaller parameters should be passed as longs; larger parameters should be passed by pointer. Exception: 8-byte doubles (MoaDouble) may be passed by value. This rule applies to all interfaces defined by MOA and MOA-compliant applications.
Set your compiler to generate a shared library, file type 'Xtra', owner 'Xown'. PEF version info doesn't matter; you'll need to export the following symbols: "DllGetClassObject", "DllGetInterface", "DllGetClassForm", "DllCanUnloadNow", "ppcSetFileRef" (in Metrowerks, you can select "use #pragma" and the correct symbols will be exported). "DllGetClassInfo". You don't need an Initialize, Terminate or main entry point. Important: set structure alignment to PowerPC. Define the following preprocessor symbols for all files (using either a prefix file, compiler prefs, or command-line options):
#define MACINTOSH // compiling on Macintosh
You'll need the header files moaxtra.h, moastdif.h, and moatypes.h.
In Win95, Win32s, and WinNT, the only compilers currently supported is MSVC2.x. Other compilers will be supported in the future.
Set your compiler to generate a large-model DLL. After producing the file, rename it to have the extension .x32 (rather than .dll). Currently, the windows projects define the following preprocessor symbols for all files:
#define _WINDOWS // compiling on Windows
#define WIN32 // compiling 32-bit Windows
The following symbol gets defined by other MOA headers if it is not defined in the project:
#define WINDOWS // compiling on Windows
Support for MPW C/C++, which promotes all types to long-size before passing them as parameters, gives rise to the rule above requiring long argument types to all methods.
The MPW preprocessor is unable to handle the debugging macros discussed at the end of this chapter. MPW C also forces restrictions on A4 globals, which means some difficult programming for the uninitiated.
On Power PC Macintoshes, when VM is off, MOA uses the call GetMemFragment() to load Xtras. However, the Metrowerks debugger won't properly debug code fragments loaded with this call. However, if you add a file in the Xtras folder named "_XtraDebugMode_", the call GetDiskFragment() is always used to load PPC Xtras, regardless of the VM state. This trick is intended for debugging purposes ONLY; your Xtras should be tested without this workaround in place before shipping.
Header file: moaxtra.h
The debugging macros defined in moaxtra.h may be useful for helping you develop Xtras. These macros can be added to and left in your code, since the behavior described here is only defined when you include the definition #define MOA_DEBUG in your Xtra code. Otherwise, they are defined as nil. As noted earlier, these macros can't be used with MPW C because of incompatibility with the C preprocessor.
MOA_ASSERT(cond, msg)
cond Condition to test
msg Char * for the message to display if test is true
Displays the text msg only if the cond is true. On the Macintosh, this macro invokes debugstr(); on Windows, it invokes MessageBox().
MOA_CLOBBERMEM(p, sz)
p Pointer to start of memory to clobber
sz Size of memory to clobber
Clobbers memory by writing bogus address values to the range from p to p + sz. This macro can force a failure in calls that otherwise find address-like values to jump to in the specified memory.
MOA_CLOBBERTEST(p, msg)
p Pointer to memory to check
msg Char * for the message to display if memory is clobbered
Like MOA_ISCLOBBERED(), except displays the text msg if the memory has been clobbered. On the Macintosh, this macro invokes debugstr(); on Windows, it invokes MessageBox().
MOA_DEBUGSTR(msg)
msg String to display in the debugger
Use this macro to display the string x as your code executes. On the Macintosh, this macro invokes debugstr(); on Windows, it invokes MessageBox().
MOA_ISCLOBBERED(p)
p Pointer to memory to check
Returns: TRUE if clobbered, FALSE otherwise
Checks the memory at address p to see if it has been clobbered.