Bitwise Operator
by Matt Slot
Fundamentals of a Macintosh Application
This month's article walks you through the process of writing the application startup, event loop, and termination routines. This is the first article to provide source code (found at http://www.ambrosiasw.com/~fprefect/bitwise/issue3/bitwise.c) relevant to the discussion, so you can actually roll up your sleeves and start to experiment. You should print out that file now.
The main source file is broken into sections to improve readability and promote better organization: included header files, function prototype declarations, global variable definitions, and then the function definitions.
When programming the Macintosh, you'll need to reference the functions or data used by the MacOS or the Toolbox. Declarations for these objects are often included automatically by the compiler using "precompiled headers" (or "MacHeaders") -- which basically means that 95% of the code you write will simply work.
For the sake of compiler speed, however, not all of the MacOS interfaces are included automatically. In certain cases, you'll need to explicitly reference the appropriate MacOS header file (or "Universal Header") using #include or similar feature. These headers are also quite useful as a reference when you don't know the exact parameters to pass to a MacOS or Toolbox function.
The basic design of a Macintosh program includes 3 parts: initialization, interaction, and termination. Typically your main() program function performs or delegates each of these tasks in order.
The DoInitialize() function consists of the standard Toolbox initialization code as it should always be performed. The Gestalt() function and its result validate that we are running on System 7.0 or later (an assumption that will save us time later). If we are not, we call our error handling function and then force the application to quit immediately.
MaxApplZone() and MoreMasters() are Memory Manager functions which set up your application to allocate and use your memory "heap", The next series of functions initalizes the standard Toolbox "managers" so that you can use them. At some point, we will also add code to install callback functions, such as AppleEvent handlers.
After initializing the Toolbox, you should perform any other startup tasks that your application needs, such as loading and creating the application's menu bar, displaying a cool splash screen, or just starting your game. Again, as this framework evolves, you'll see how to do these things.
Next, the DoInteraction() function handles most of the actual interaction with the application user (obviously). As that person clicks the mouse, types on the keyboard, or performs other actions, your application will receive information about such "events" and should dispatch them to be handled. Once the user's actions indicate that he is done with your program, it will mark itself ready to quit and the do-while loop will terminate.
While the program is operating, it periodically calls WaitNextEvent() to receive another event that needs to be handled. Some of these events come from the user (mouse downs, key downs) and others are generated automatically by the system or other software (update events, OS events), but its your application's responsibility to handle them (or ignore them if you like).
By examining the event record's what field, you can determine which kind of event was received and dispatch that to a function which will handle it. For now, however, the only event this software recognizes is a key down event, for which it either quits the program (pressing 'Q') or beeps (pressing any other key). Certainly its not a Microsoft product yet.
Whether or not WaitNextEvent() returned an event, your software may still need to perform its own periodic tasks such as: setting the cursor, flashing a text insertion caret, or processing some internal data. Insert such code at the appropriate location and it will be called on a regular basis.
DoTermination() is simply the place to clean up the application's memory structures, close any files it may have opened, etc. Since the current software doesn't need this, the function is pretty empty.
Finally, HandleError() will be the standard function that is called when our application encounters an unexpected error. By forcing all error handling to this function, it becomes simpler to present a single error notification and to intercept specific errors that may occur in several locations across a large applications. As with most of the sample code, this function hasn't been fully implemented until we cover some more material. For the time being, we can just play the beep sound.
At the most fundamental level, all applications operate the same way. The associated sample code illustrates common code that can be used to write any kind of application from a word processor to a game. The next column will extend this code with menus and windows, so that you can actually feel like you are making something.
Writing software using an IDE
OK, so now you have some source code and you are ready to actually make it go. If you've never used an Integrated Development Environment, or IDE, then a brief introduction is appropriate. For simplicity, let's take a look at creating an application using the CodeWarrior IDE.
The center of the IDE interface is the project window, which contains a list of relevant source files, libraries, and resource files, as well as the compiler settings which to use. Unlike makefiles, however, a project file contains the information necessary to generate a full Macintosh application: type and creator information, application runtime flags, etc.
When you create a project file, the IDE typically fills it with the necessary sources and libraries to make a sample application -- the MacOS and Toolbox interfaces, and perhaps the ANSI libraries if you are writing a console application. After creating the application project, you'll want to remove the sample source file (CodeWarrior
typically includes "sillyballs.c") and replace it with your own source files -- in this case, the file "bitwise.c".
Once the project window lists all the necessary files, simply select "Make" or "Run" from the menu. If there are compiler or link errors, then an error window will appear describing them, so that you can track them down and fix them. Once your application compiles and links sucessfully, you can actually run it to see how well it works.
If your application doesn't seem to be working properly, you can also use the "source level debugger" provided with your IDE. By choosing "Enable Debugger" from the menu, you can then choose "Debug" to automatically launch and prepare the application to be debugged. Once in the debugger, you can set breakpoints to stop and examine the application state at almost any point. You can also trace execution one instruction at a time while watching global and local variables.
As always, you should read the full documentation on your IDE and debugger -- since these are powerful and complex pieces of software that deserve more than a quick introduction. In an upcoming article we'll take the time to examine the IDE more closely, as well as walk through a sample debugging session.
Matt Slot, Bitwise Operator