Implementing an Application
Once you have a blueprint for the class hierarchy of a program, you're ready to structure the program, actually write the source code, and then assemble the pieces into a self-running Macintosh application. In this chapter, we provide the details for the following steps:
- Structuring the program for keyboard and/or mouse input;
- Creating readable source code files;
- Compiling your code and predefined classes with load files;
- Debugging the program;
- Installing the program as a standalone application.
Structure of a Typical Application
In most Mops programs for the Macintosh, a handful of classes will be the primary, high-level building blocks for your application. Into these blocks go the specific processing that make your program unique.
Windows tend to contain the major sections or 'mini-applications'
within your code. They will contain a number of Views, each of which
will handle its own area of the window. The views will probably have a
number of subclasses to handle the different kinds of items in the
window. Controls are one such subclass of View, and usually
determine control paths within a given part of the application, or can
be used to provide a more convenient mechanism for setting options. Much
of the important code in your application will probably be called via
the DRAW:
and CLICK:
methods of these
various View classes.
Menus give the user a means to choose another part of the application or to alter an option setting.
And Dialogs are special-purpose windows that focus the user's attention on a specific choice or set of choices.
Bringing Objects to Life
All of the above classes create objects that are recognized by both Mops
and the Toolbox. When your application starts up, it generally must send
New:
or GetNew:
messages to all of
these dual role objects (Mops and Toolbox objects that are needed
immediately. Such messages cause the objects to make themselves known to
the Toolbox, and to allocate any heap data that the Toolbox needs to
keep track of the objects' states. Then the application will begin
listening to events --- thereby becoming sensitive to the user's
keyboard and mouse input.
Waiting for Events
Mac applications, rather than simply starting at the beginning and
working through to the end, are event driven. That is, they
basically sit waiting for the user to ask for something to be done, by
clicking, typing a key, selecting a menu or whatever. When something has
to be done, the program is notified via an event. The program acts on
the event, and then waits again. The waiting loop is called, naturally
enough, the event loop. Mops simplifies this for you by providing a
word, EventLoop
, which executes this loop. The
definition isn't very complex:
`: EVENTLOOP`\
` BEGIN`\
` next: fevent`\
` AGAIN ;`\
FEvent
is our one-off object of class
Event
. The next:
method makes the
system call WaitNextEvent
, which returns to the program
when an event is ready. The next:
method then executes
the appropriate action handler which Mops has set up for this kind of
event. This will usually lead to a message being sent to some object in
your program.
On the other hand, in Carbon PowerMops the definition of
EventLoop
is much more complex and procedural. Carbon
event handling is the task of callbacks. We should write our event
handling as a callback word and register it in each system object. Then,
if we call RunApplicationEventLoop
, appropriate word
would be callbacked on an event by OS. Ideally this system call function
could have replaced our word EventLoop
. But
unfortunately processing much task in a callback sometimes causes
trouble. Especially, compiling code in a callback is unsafe. So we
should call QuitApplicaitionEventLoop
and execute
appropriate words out of the callback in some cases. As the result,
EventLoop
in PowerMops contains some conditional
branches.
The grDemo source file (explained in the Tutorial) provides a simple example of an event-driven Mac application, and is worth examining rather closely for the manner in which the program and user communicate with each other.
Apple Events
Mops is "System 7 friendly". Among other things, this means that it recognises Apple events, which are described in Inside Macintosh, volume VI. Mops handles the �core' Apple events: OpenApplication, OpenDocuments, PrintDocuments and QuitApplication. These have to be available in the nucleus, so that the nucleus can be properly System 7 friendly. We have provided handlers for these Apple events, as required by Apple. The code for the handlers themselves is in assembler, so is not available for modification (and wouldn't mean much to most people). However, we have also provided four corresponding vectors (vects) so that your application can customize things.
The handlers do some setting up, call the appropriate vector, then do
some winding up. These handlers are :PROC
and
:CALLBACK
routines --- they get called from the
system, and return to the system, i.e. they are callback routines.
As the handlers do the :PROC
and
:CALLBACK
bits already, the words you put into the
vectors are ordinary Mops words.
When an Apple event handler is called from the system, there are three parameters on the stack:
( ^AE ^AEReply RefCon -- )
\^AE
is a pointer to the Apple event itself.
\^AEReply
is a pointer to an Apple event that can be
used for a reply from the handler. RefCon is a longword that has been
associated with this particular Apple event by the caller --- this
can be used for anything. The Mops Apple event handlers first call the
word AEhandler, which does the setup housekeeping. It pops these
parameters into the values fAE
,
AEReply
and AERefCon
. It then
redirects AbortVec
and QuitVec
, first
saving the old values. This is because within a :PROC
routine we mustn't Quit
or Abort
. If
either of these is called during the execution of the Apple event
handler, a Mops error number is put in the value (ERR#), and the
handler winds itself up and returns to the system. Back in the Mops
event handling code, which originally called the system to handle the
Apple event, we bring up a Mops error in the usual way.
After calling AEhandler
, the Mops Apple event handlers
call their particular vector to do whatever processing is necessary to
handle this Apple event. They then branch to some common windup code
which takes some action depending on the result returned from the vector
(see below), then restores the previous values of
QuitVec
and AbortVec
and returns to
the system.
Warning: In Carbon PowerMops, all Apple event vectors described below are
NULL
at present. Apple event handling scheme described below also has been changed in Carbon PowerMops.
The Apple event vectors are OpenAppVec
,
OpenDocVec
, PrintDocVec
and
QuitAppVec
. We will now describe the first three of
these, and leave QuitAppVec
for later, as it is the
exception.
These three vectors have the stack effect
( -- code True | -- False )
If False is returned, the default handler windup is used, which performs the recommended system calls to check if we got all the parameters and returns the appropriate error if not. If True is returned, we assume that the event has been fully handled within the vector routine, and so we return straight to the caller --- the code is the result code that gets passed back. The default for these vectors assumes that the Mops development environment is running, and does the appropriate things.
OpenDocVec finds the number of files in the given list of files, and
puts the number in the value \#DocsToOpen
. It then
handles each file in the given list of files by opening it (read and
write permission) using the default file object fFcb, then calling the
vector Read1DocVec to read and close it. If this is sufficient for your
application, you just have to redirect Read1DocVec appropriately, as we
describe in the next paragraph. As set up, Read1DocVec
assumes the file is a Mops dictionary.
Read1DocVec
must point to a routine which will read the
file designated by the file object fFcb
, close it, and
return a result on the stack. True indicates the
OpenDocVec
loop can continue, i.e. open the next file
in the list of files passed by the system, and call Read1DocVec again.
False means the loop must terminate. You could return False, for
example, on an error, or simply if your application can't accept more
than one document open at a time.
If you decide to redirect OpenDocVec
, you will have to
take care of getting the details of the files 'dropped' on your
application youself. In brief, this involves a call to
AEGetParamDesc
to get the docList, then a call to
AECountItems
, then a loop through calls to
AEGetNthPtr
, which returns the pointer to the info
about the Nth file. The procedure is complex, and involves a large
number of parameters, so read the appropriate section of Inside
Macintosh very
carefully first. It is perfectly possible to get your application to
open it's own documents tidily just with redirection of
Read1DocVec
.
PrintDocVec
is set to the same routine as
OpenDocVec
, since it doesn't make sense to try to
print a Mops dictionary. Your application really ought to do something
different if it supports printing.
QuitAppVec
is the exception, in that it is NOT called
from the QuitApplication
Apple event handler. This is
because if you try to quit to the Finder from inside an Apple event
handler, you'll crash! You MUST return in the normal way from an Apple
event handler, or the system won't be pleased with you at all. So what
our QuitApplication
handler does is set a flag
QuitApp?
and return. Then back in normal Mops
execution, after handling an Apple event, we check if
QuitApp?
is True, and if so, we execute
QuitAppVec
. This code is in the source file Event. The
current setting for QuitAppVec
is simply to call
BYE
. Your application will probably want to do
something a bit more intelligent.
Well, sorry about that terribly long winded dissertation on Apple events. They do represent a major new addition to Apple's system, and are decidedly nontrivial to handle and describe!
Note: Unfortunately, you must write your Apple event handlers almost from scratch in Carbon PowerMops (see file CarbonEvents) at present. But, on the other hand, you can use
Class\_AEDesc
,Class\_AEList
, andClass\_AEAddress
classes defined in file AEClasses.
Compiling Your Source
As you write portions of your program, you can load them into Mops (they compile while loading) to let the compiler search the code for errors and to let you fully test how well the code executes. You won't necessarily save the compiled program until a logical section is completed and debugged --- once you save a compiled chunk of code, you will no longer be able to edit what is saved. Instead, while you're reworking a section, you should maintain your program as text files and load them into Mops each time you want to test the code.
When you load a typical program, you will be doing so on top of Mops.dic
(or MopsFP.dic), which contains a number of -- but not all of
-- Mops' predefined classes already compiled. It is important to
understand how source files for your program and the optional predefined
classes should be loaded onto Mops.dic. When you loaded the grDemo in
the Tutorial several predefined classes were automatically loaded before
loading in the grDemo code. This was done by the NEED
command at the start of the grDemo file. The sequence of loading is
important, but can easily be handled by NEED
.
Incidentally, Mops has a powerful file stack facility that allows
you to nest loads up to six deep. Thus, a file loaded by
NEED
can also NEED
other files. When
this happens, Mops stacks the currently open file (i.e., temporarily
interrupt loading of one file) and begins loading the new file. When the
second file load is complete, the load of the original, stacked file
resumes on the line following the NEED
statement.
Switching Between Compiler and Editor
When you compile a source file the first time, you may discover that an error crops up, at which point, the compiler displays a message directing you to the problem area and stops loading. You'll then want to go back into the source file to remedy the problem. This can be done as simply as switching windows to your editor. If you're using QuickEdit, it will have already scrolled your source file to the right place.
Your process of program building will take the following steps:
- Using the editor, load an existing source file or create a blank page for new work.
- After entering a few definitions, you may want to test them. Save your work in the editor (an easy step to forget!), switch to the Mops window and type L (for load), or choose Load... from the File menu, or type Command-L. A dialog box will come up. Select your source file, and it will load. Alternatively you may type // followed by the name of your source file, if you don't want to have to reply to a dialog box.
- After doing this once, you can reload the same file (having made
changes or additions) by typing
RL
. Mops remembers which source file you are using, via a special dictionary entry which is added automatically when a file begins loading. When you typeRL
, Mops first does a FORGET --- although not really the effect of the wordFORGET
in PowerMops --- back to that point in the dictionary, then loads the file. If you just want to FORGET back to that point but not load the file, typeFM
(Forget to Mark). - In the event that you made a mistake in your coding, Mops will
report an error of some sort. Switch to the editor window, edit your
source file, save, switch back, and
RL
.
If you have been accustomed to working with a compiled language like C or Pascal, you might be somewhat startled by the immediacy of Mops while you are in the editor. It can speed your development time tremendously to be able to interact with the language as you write, and you should learn to do this often (sometimes it's easy to forget). Frequently, in the time that would be taken to remember something while editing, you could have gotten an answer directly by using the full power of Mops' interpreter.
Saving Compiled Programs
You can Save an image of the dictionary at any point during compilation of your source (this is different from installing a finished application, as described later), by selecting Save as� from the File menu. This creates a binary image on disk of that portion of the dictionary from the top of the nucleus up to the last word compiled. Save your work often, because you can always use FORGET or FM to remove any part of the dictionary other than the nucleus. It is good to do a Save just before loading any file that is in a questionable state or in the process of being debugged. Then, if the machine crashes, you need only double-click on the saved image's icon to get right back to where you were.
You can have several saved images on a disk without causing any problem.
As you will see in the next chapter, we have changed this slightly for PowerPC Mops, where we actually save the whole dictionary, including the nucleus, each time.
Other Compiling Tips
When loading a file that has never before been compiled, select Echo During Load from the Mops menu to cause each line of the file to be echoed to the screen as it is loaded. If an error occurs while you're watching a file load, you'll have a much better idea of where the problem is.
For files that you know well, disable Echo During Load for a much faster load, but you won't get as detailed messages if an error occurs during compilation. You might then use WORDS to determine the last name loaded into the dictionary�this should be the name of the word containing the error.
After an error, Mops prints the contents of the file stack�the file at the top of the stack is this file containing the error. You can pause an echoed load at any time by hitting the space bar. You can then either continue (by pressing the space bar again), or abort the load (by pressing a different key).
Debugging Your Code
You should begin debugging as soon as you have a small section of code that compiles successfully. Start testing the lowest-level words or methods first, so that you can establish a firm base of code that you have confidence in. Call these words interactively (i.e., from the Mops prompt), setting up reasonable parameters on the stack, and then using the .S stack dump to determine if the results are correct. You can also use the Debugger utility to step through a definition instruction by instruction. We will describe this utility shortly.
Evaluating Error Messages
It's quite possible that in the early stages of program development, you'll generate a Mops error during execution or compilation of a word or method. If this is the case, find the error in the Error Handling section of this manual (not yet in existence) and try to determine the precise cause in your code.
Frequently, Mops might catch an error that is actually an indirect result of another problem which Mops did not catch. For example, if your code accidentally overwrites the header of a previously defined array, upon execution, the error will point to the array, when, in actuality, the problem is with the errant code. Another example would be a number accidentally left on the stack that doesn't interfere with execution until much later in the program. In cases like these, you must work backwards, tracing the origins of each value on the stack, and seeing if it makes sense. Eventually, you will find the word that is producing an incorrect result, and make a change in the source code accordingly. It can be very helpful to place statements in your code that print out key data values.
System Errors
Sometimes, your code will produce an error that is caught by the Macintosh system before Mops becomes aware of it. In these cases, unless you have Macsbug installed, you will get the 'bomb box'. The most common system error codes are 2 (when the CPU tries to access an illegal address) and 3 (when the CPU attempts to execute data as code). You'll probably have to reset your Mac if you're not using Macsbug, but if you are using Macsbug (always a good idea when testing code) you may be able to resume by typing:
a5=currenta5<br /> g 1e4
Sometimes, however, enough 'damage' will be done to the heap or 68000 register contents to necessitate a restart, even with Macsbug installed. Therefore, be sure you save your source code files. This will provide you with a safe record of the code that caused the errors.
If you tried to execute a @
or W@
operation on an odd address, such as 2001, you would generate a System
Error 2 (only on 68000 CPUs). The 68000 processor has a set of
instructions that are optimized for even addresses, and some Mops words
use these instructions in their code. Odd address errors can be caused
directly in the manner described, but are more likely to result from a
different problem that just happens to generate an odd number which then
gets used as an address.
Macs with 68030 or 68040 CPUs won't generate odd address errors, but will give a 'bus error' if something is used as an address which is outside the range of legal addresses for that machine. As with odd addresses, these are most likely to arise from some other problem which leads to something being used as an address which isn't really an address. Note that we store an illegal address value, which is also odd, in nil handles and pointers. Thus any attempt to use a nil handle or pointer will give either an odd address error or a bus error depending on the CPU.
Any fatal system error is best tracked down by first finding the precise location where the error occurs. Do this by testing words interactively, and then reasoning out why the offender isn't working properly.
A System Error 28 means that the system stack (the Mops data stack) has grown down into the top of the heap. Because Toolbox routines use the system stack for their data storage, stack overflow can occur if a deeply nested Mops word calls a Toolbox routine that uses a lot of stack. You can use the Install Utility (described in the Tutorial) to adjust the proportions of heap available for the stack and the dynamic heap.
Some errors may cause the machine to lock up, make strange sounds, or break up the video. In these cases, the code has destroyed something essential to the operating system before either Mops or the Macintosh Operating System could detect it. The only choice here is to reset the Mac and try to determine where the code is going wrong. You might want to scatter ." messages through your code, which can print values and strings to keep you posted on where the code is executing at a given moment. This will help you narrow down the location of a problem fairly quickly.
Your Application Icons
We have already described the use of the Install utility in the final Tutorial lesson. Here we will discuss how to give your application and its documents their own icons.
There is a complex interplay of resources within a Macintosh application that describe an application and its icons to the Finder. You may want to read "Structure of a Macintosh Application" in Inside Macintosh before proceeding, especially if your application manipulates its own document files. We will describe here only the steps that relate to Mops and your application.
When you first use Install to create your application, it has no icons. You can use ResEdit, Icon Edit or various other utilities to create icons. If you give them the expected resource IDs, you can paste them straight into your application using ResEdit. The expected IDs are 128 for the application itself, and 129 to 132 for the various document types which your application uses. Your icon editing utility will have a way of specifying the resource IDs of the icons.
Assuming you have created a resource file using one of these utilities, and that you have given the icons the right IDs, do the following:
- Start ResEdit, then open both your new application and your icon resource file
- Select your icon file, and do Select All (Command-A), then Copy
- Select your application
- Do Paste, then Save
- Quit ResEdit
Your application may not appear with its new icon immediately, since the Finder keeps icon information in its own 'desktop file'. If you close and then open the window containing your application, this may cause the Finder to recognize the new icon.
However, in case of Mach-O application, data fork icns resource is needed for the application icon. Your application installed from Mach-O PowerMops is a Mach-O application. It will be a not yet packed folder structure of the application bundle at first. Assuming you have created a data fork icns resource file, do the following:
- Rename the icon file to be "app.icns"
- Put your icon file in "yourApp:Contents:Resources:" folder
- Complete your application bundle packaging by adding .app extension to the name of outer most folder of your application bundle, i.e. rename 'yourApp' to 'yourApp.app'
"app.icns" is set as a default application icon file name in file 'info.plist'. You can change it by editing the info.plist file.