This chapter discusses the life of an application as well as several topics most application programmers will want to cover at one time or another. Before reading this chapter, you should have read each of the previous chapters; this chapter builds on the knowledge you gained in those chapters.
This chapter details the components and features of a geode , a GEOS executable. The chapter is presented in the following five sections:
1 Geodes
1.1 Geode Components and Structures
1.2 Launching an Application
1.3 Shutting Down an Application
1.4 Saving and Restoring State
1.5 Using Other Geodes
1.6 Writing Your Own Libraries
1.7 Working with Geodes
1.8 Geode Protocols and Release Levels
1.9 Temporary Geode Memory
2 Creating Icons
2.1 The Token Database
2.2 Managing the Token Database File
3 Saving User Options
3.1 Saving Generic Object Options
3.2 The GEOS.INI File
4 General System Utilities
4.1 System Clock
4.2 Using Timers
4.3 System Statistics and Utilities
4.4 Shutting the System Down
5 The Error-Checking Version
5.1 Adding EC Code to Your Program
5.2 Special EC Routines
6 Inter-Application Communication
6.1 IACP Overview
6.2 GenApplicationClass Behavior
6.3 Messages Across an IACP Link
6.4 Being a Client
6.5 Being a Server
Geode is the term used to describe a GEOS executable. Just as DOS has executables (programs) that reside in files on a disk, so too does GEOS. GEOS executables normally have the filename extension .GEO.
Each geode may have up to three different aspects:
A geode can have any combination of these aspects. For example, the print spooler is a process-library (and therefore provides routines for other geodes while also having a thread of its own), but the sound library is actually a library-driver since it manipulates the machine's sound hardware.
Library and driver geodes that do not have the process aspect do not initially have event-driven threads. Therefore, typically they will not contain objects, just procedural code. They can contain objects, however, as any geode is free to create an event-driven thread for itself at any time. In fact, the parallel port driver does just that when it is printing to a port through DOS or BIOS.
Geodes are loaded either with a call to the kernel routine
GeodeLoad()
or as a side effect of a library's client being loaded (in that case, the library geode will be loaded as well). The generic UI supplies the special routine
UserLoadApplication()
, which you may use to load an application--a geode which has both a process aspect and its process class subclassed off of
GenProcessClass
(and therefore can put generic UI objects on the screen).
Once a geode has been loaded, it is identified by its geode handle , which is the memory handle of the block that holds all the geode's system-administrative data. This block is called the core block and should not be accessed by anything other than the kernel. The geode handle is also used to determine the owner of a particular block in memory; when queried for the owner of a particular block, the kernel will return the geode handle of the geode that owns that block. A geode is the only entity that may own a system resource. If the geode is a process, the geode handle may also be known as a process handle.
When a geode is loaded, its core block is connected to a linked list of the core blocks of other geodes running in the system. This linked list is chronological, with the first entry belonging to the first geode loaded and the last entry belonging to the most recent geode loaded. Each core block contains an entry for the handle of the next core block in the list; the kernel can follow these links to locate any geode in the system. (Only the kernel may do this.)
After the core block is appended to the list, GEOS scans the list for other instances of the same core block. If the geode has been loaded more than once, it will have multiple instances in the list (one instance of the core block for each time the geode is loaded; each core block references the same copy of the geode, however). GEOS then copies the shared-resource handles from an existing core block (if found) into the new core block, thus reducing the amount of work required to load a particular geode multiple times (the shared resources do not need to be reloaded or recreated). Non-shared resource handles are not copied; the resources are loaded or constructed as necessary.
Each geode's core block contains a reference count for that particular geode. When the geode is first loaded, the reference count is set to one. If the geode is a process, the act of initializing the process thread increments the reference count. Each time the geode is loaded again, the new core block will get its own reference count. If the geode is loaded implicitly (as a library, with
GeodeUseLibrary()
, or with
GeodeUseDriver()
), or if it spawns a new thread, it will receive yet another reference count.
The reference count is decremented when a thread owned by the geode exits. If a client of a library geode exits, the library's reference count goes down by one.
When a geode's reference count reaches zero, all the geode's non-sharable resources are freed along with all the file, event, and timer handles owned by the geode. If a sharable resource is co-owned by another instance of the geode, ownership is transferred to the geode's next-oldest instance. (Shared resources are always owned by the oldest instance of their geode.) Once the resources have been freed or transferred, the core block is removed from the linked list and is freed.
To make sure no synchronization problems occur while updating the core block list (e.g. a geode is being loaded while it has just been freed), GEOS maintains an internal semaphore. The geode loading and freeing routines automatically maintain this semaphore.
Geode Components and Structures
Geode Protocols and Release Levels
A geode is simply a special type of GEOS file. It has a special file header that gets loaded in as the geode's core block. This file header contains the geode's type, attributes, release and protocol levels, and many other pieces of information necessary for GEOS to work with the geode. You never will have to know the exact structure of this header as the kernel provides routines necessary to access important portions of it.
Several important items contained in the header are listed below.
GeodeAttrs
. The geode attributes are described below in Geode Attributes
.
GeodeToken
. Tokens and icons are discussed in Creating Icons
.
Each geode has in its core block a record of type
GeodeAttrs
. This record defines several things about the geode, including which aspects it uses and which of its aspects have been initialized. The
GeodeAttrs
record contains one bit for each of the following attributes.
As stated above, every geode is associated with a token in the token database. This token is defined by the use of a
GeodeToken
structure. This structure and its uses are discussed in Creating Icons
.
GeodeLoad(), UserLoadApplication(), MSG_GEN_PROCESS_OPEN_APPLICATION
An application is a geode with its GA_APPLICATION attribute set. This type of geode may be launched by the user through GeoManager or some other means provided by the system or another application. For the most part, the system will invoke and carry out the launch; your responsibilities are limited.
An application may be loaded in essentially two ways: It may be launched, or it may be reloaded from a state file. In both cases, the kernel will load the proper resources and build out the UI properly according to the application.
Most of the procedure of launching is handled within
GenProcessClass
, a subclass of
ProcessClass
. An application should define its own subclass of
GenProcessClass
for its Process objects (event-driven threads not acting as Process objects should be subclassed from
ProcessClass
, not
GenProcessClass
). The launch procedure may be invoked by any thread and by any geode in either of the following ways:
GeodeLoad()
This loads a geode from a given file and begins executing it based on the geode's type.
GeodeLoad()
takes the name of the geode's file as well as a priority to set for the new geode's process thread, if it has one.
GeodeLoad()
first creates the process thread of the application, then sends this thread a message. The process thread (a subclass of
GenProcessClass
) then creates a UI thread for the application.
UserLoadApplication()
Used by most application launchers, this routine loads an application. (C programmers will generally use
MSG_GEN_PROCESS_OPEN_APPLICATION
instead.) This routine takes some additional parameters and can load a geode either in engine mode or from a state file as well as in the normal open mode (see below). The base functionality of opening and loading the geode is implemented in this routine by a call to
GeodeLoad()
. Note, however, that this routine may only open application geodes--geodes with the GA_APPLICATION attribute set.Geodes may be launched in three modes:
GenProcessClass
(your Process object).It is possible, however, for one application to launch another in a custom mode. If this is done, the application being launched is responsible for implementing the special mode.
When the launch process has been initiated with the above routines, a thread is created for the application and its Process object is loaded immediately. Also loaded is the application's GenApplication object. The Application and Process objects interact with the User Interface to load the objects on the application's active list and set them all usable, bringing them up on-screen.
Near the end of this procedure, just before the GenApplication is set usable, the Process object will receive a message based on the mode of launch. If the application must set up any special notification (such as for the quick-transfer mechanism) or must restore special state file data, it should intercept this message. Typically the message received will be
MSG_GEN_PROCESS_OPEN_APPLICATION
--two others are received (when restoring from state and when opening in engine mode), but they should not be intercepted.
MSG_GEN_PROCESS_CLOSE_APPLICATION, MSG_GEN_PROCESS_CLOSE_ENGINE, MSG_GEN_PROCESS_CLOSE_CUSTOM, MSG_META_QUIT
Just as loading an application is handled almost entirely by the system and GEOS classes, application shutdown is also fairly automatic. If the application intercepted
MSG_GEN_PROCESS_OPEN_APPLICATION
for its own purposes on startup, it likely has to do a little cleanup; otherwise, it won't have to worry about shutting down. (See Saving and Restoring State
for special information on using this message.)
Any object in the system may cause an application to shut down. Usually, shutdown occurs either when the system is being exited (when a user exits to DOS, for example) or when the user has closed the application. Therefore, the usual source of the shutdown directive is the User Interface.
An application begins shutting down when either its Process object or its Application object receives a
MSG_META_DETACH
. If you want to cause a shutdown manually, you should send
MSG_META_QUIT
to the application's GenApplication object; this will execute some default functions and then send the appropriate
MSG_META_DETACH
. Essentially, the same detach and destruction mechanisms used for any object are used for the entire application. The object receiving
MSG_META_DETACH
passes the message along to all of its children and to all the objects on its active list. (If a
MSG_META_DETACH
is used without
MSG_META_QUIT
, the application will create a state file.)
When they have all acknowledged the detach, the application acknowledges the detach and sets itself unusable. It automatically flushes its message queues before shutting down to avoid synchronization problems. You should not subclass the
MSG_META_DETACH
handler unless you have special needs for cleaning up or sending special detach messages to other objects or geodes. If you do subclass it, you must call the superclass at the end of your handler. Otherwise, the application will not finish detaching (see the GEOS Programming chapter).
Instead of intercepting
MSG_META_DETACH
, though, the application may intercept the mode-specific message it will also receive. Depending on the mode in which it was launched, the application will receive (via the Process object) either
MSG_GEN_PROCESS_CLOSE_APPLICATION
(for application mode) or
MSG_GEN_PROCESS_CLOSE_ENGINE
(for engine mode). There is no special shutdown message for shutting down to a state file; instead,
MSG_GEN_PROCESS_CLOSE_APPLICATION
is used.
When the system shuts down or task-switches, a different type of shutdown occurs. Applications (or other objects interested in this event) must register for notification on the notification list GCNSLT_SHUTDOWN_CONTROL (notification lists are described in the General Change Notification chapter). When the system shuts down or task-switches, the object will then receive a MSG_META_CONFIRM_SHUTDOWN
, at which time the object must call
SysShutdown()
.
ObjMarkDirty(), ObjSaveBlock()
Nearly all applications will save and restore their state so the user may shut down and return to precisely the same configuration he or she left. Saving of state is almost entirely contained within the system software; for the most part, only UI objects are saved to state files. You can, however, mark other object blocks and data for saving to a state file.
The state file for an application is a VM file containing object blocks. Only object blocks may be saved to the state file, though you can save LMem data by setting up object blocks with only data chunks in them. (Create the blocks with
MemAllocLMem()
, passing type LMEM_TYPE_OBJ_BLOCK, then simply use
LMemAlloc()
to allocate data chunks.) For more information about managing LMem and data chunks, see the Local Memory chapter. You can also save an extra data block to the state file using MSG_GEN_PROCESS_CLOSE_APPLICATION
and
MSG_GEN_PROCESS_OPEN_APPLICATION
. In the close message, you can return the handle of an extra block to be saved to the state file; in the open message, the handle of the extra block is given to you, and you can restore this data as necessary. See the reference information for these messages under
GenProcessClass
for more information.
When a state file is saved, the system recognizes and saves only the dirty (modified) objects and chunks. Later, when state is restored, the system merges the changes in the state file with the original object blocks, resulting in the state that was saved.
For individual objects or entire object blocks to be saved to the state file, they must be marked dirty. Generic objects automatically mark themselves dirty at the appropriate times, so you don't have to worry about them. To mark other objects dirty, use the routine
ObjMarkDirty()
. Each object which has been marked dirty will be saved to a state file when appropriate. If you want to save an entire object block to the state file, you can call
ObjSaveBlock()
on the block; the system will save the entire block, not just the dirty chunks.
State files are dealt with at only two times: First, when the system starts up, it will check for the existence of application state files. If a state file exists, the system will attempt to load the application belonging to it; after loading the application's resources, it will merge the state changes with the default settings to restore the original state.
The second time state files are used is when the system shuts down. A simple shutdown (called a "detach") is invoked only by the UI and is not abortable. When a detach occurs, the system shuts down all geodes as cleanly and quietly as possible, saving them to state files. Only certain geodes will respond in extreme cases, offering the user the option of delaying the detach or cancelling an operation in progress. An example of this is the GEOS spooler; if one or more jobs are actively printing or queued for printing, the spooler will ask the user whether the job should continue and the detach be delayed, or whether the job should be aborted or delayed until the next startup. The spooler can not abort the detach in any case.
Another type of detach is called a "quit." Any geode may invoke a quit, which is actually a two-step detach. A quit will first notify all other geodes that the system will soon be detaching; other geodes then have the chance to abort the quit if they want. For example, if a terminal program were downloading a file and received a quit notification, it could ask the user whether she wanted to abort the quit or the download. If the user wanted to finish the download, she would abort the quit; if she wanted to quit, she would abort the download. The system would then either shut down via a normal detach or stop the quit sequence.
When a geode is first launched, no state file exists for it. The state file is not created until the geode is actually detached to a state file. If a geode is restored from a state file, the file will exist until the geode is detached again. A geode that gets closed (not detached to state) will remove any state file it may have created during a previous detach. A geode that is detached to state will create or modify its state file as appropriate.
The state of an application (how it was launched) is reflected in the
GAI_states
field of the GenApplication object. To retrieve the application's state, send it
MSG_GEN_APPLICATION_GET_STATE
. It will return a value of
ApplicationStates
. The most frequent use of this message is by applications that need to know whether a "quit" is underway when their process objects receive the
MSG_GEN_PROCESS_CLOSE_APPLICATION
message; the process object will query the GenApplication and see if it is in the AS_QUITTING state.
In addition to the above state-saving functionality, the kernel provides two routines that translate handles between the state file and memory.
ObjMapSavedToState()
takes the memory handle of an object block and returns its corresponding state file VM block handle.
ObjMapStateToSaved()
takes the state file VM block handle and returns the corresponding memory block handle, if any.
If your application's documents are VM files, it is a very simple matter to save document state. In fact, if you use the GenDocument and document control objects, they will take care of document state saving for you. Be sure that the VM file has the VMA_BACKUP flag set in its
VMAttributes
; then you can simply call
VMUpdate()
on the document file. (Note--do not use
VMSave()
instead; it will erase the backup and lock in the user's changes to the document.) If you are not using GEOS VM files, it is up to you how and if you will save the document's state. For more information about GEOS VM files, see the VM chapter.
Often, geodes will have to use other geodes. For example, a communications program will use the Serial Driver, and a draw application will use the Graphic Object Library. Normally, this is taken care of by the compiler and the linker when you include a library or driver in your .goc and .gp files.
Other times, however, an application will have to load libraries or drivers on the fly and then free them some time later. This section describes how to load, use, and free libraries and drivers.
GeodeUseLibrary(), GeodeFreeLibrary()
Libraries are always referenced by their file names or by their geode handles. It's easiest, however, to use the file name of the library when loading it--the system will locate the library for you. It's unusual to need to load a library for use with your geode; in almost all cases it's easiest to include the library in your .goc and .gp files and have the system load and link the library automatically. (To do this, include the library's interface definition file in your code file and list the library's geode name in your geode parameters file.)
If you need to load a library dynamically, though, use
GeodeUseLibrary()
. This routine takes the protocol numbers expected of the library (see Protocol Numbers
) and the library geode's filename. It will locate and load the library if not already loaded. If the library is already loaded, it will increment the library's reference count.
When you are done using a library loaded with
GeodeUseLibrary()
, you must free the library's instance with
GeodeFreeLibrary()
.
GeodeUseDriver(), GeodeInfoDriver(), GeodeGetDefaultDriver(), GeodeSetDefaultDriver(), GeodeFreeDriver()
Drivers are referenced by either their permanent names or their geode handles. Most drivers used by applications will be loaded automatically by the kernel; the application must have the driver's permanent name specified in its .gp file. Should an application need to use a driver not included in its parameters file, however, it can do so with the routines described below.
When you need to use a driver, the
GeodeUseDriver()
routine will locate and load it, adding it to the active geodes list. You must pass the desired driver geode's filename as well as the expected protocol levels of the driver. The routine will return the driver's geode handle.
If you load a driver dynamically, you must free it with
GeodeFreeDriver()
when your geode shuts down or otherwise finishes using the driver.
If you know a driver's geode handle, you can easily retrieve information about it with the routine
GeodeInfoDriver()
.
This returns a structure of type
DriverInfoStruct
, which contains the driver's type (
DriverType
), the driver's attributes, and a far pointer to the driver's strategy routine. Many driver types have an expanded information structure, of which
DriverInfoStruct
is just the first field. Video driver information structures, for example, also contain dimensions and color capabilities (among other things) of the particular devices they drive. The driver information structure is shown below.
typedef struct {
void (*DIS_strategy)();
DriverAttrs DIS_driverAttributes;
DriverType DIS_driverType;
} DriverInfoStruct;
The
DIS_strategy
field of the structure contains a pointer to the driver's strategy routine in fixed memory. After the driver has been loaded, its strategy routine is called directly with a driver function name.
The
DIS_driverAttributes
is an attribute record of type
DriverAttrs
, the flags of which are shown below:
DriverExtendedInfo
structure.
The
DIS_driverType
contains the type of driver described by the information structure. The types that may be specified in this field are listed below:
When you want a driver to perform one of its functions, you must call its strategy routine. The strategy routine typically takes a number of parameters, one of which is the function the driver should perform. The
DriverInfoStruct
contains a far pointer to the strategy routine; your application should store this far pointer and call it directly any time one of the driver's functions is needed. However, because the driver may be put in a different location each time it's loaded, you should not save the pointer in a state file. Note that this scheme of accessing drivers directly can only be implemented in assembly language. Some drivers may provide library interfaces as well as their standard driver interface; this allows routines to be written in C.
GEOS
maintains default drivers for the entire system. The types of default drivers are described by
GeodeDefaultDriverType
; all the types are shown below. They are called default drivers because the default for each category of driver used by the system is stored in the GEOS.INI file. GEOS will, upon startup, load in the default driver of each category.
To retrieve the default that GEOS is using, call the routine
GeodeGetDefaultDriver()
with the appropriate driver type (a member of the type
GeodeDefaultDriverType
). This routine will return the geode handle of the default driver of that type.
To set a new default driver for a specified driver type, use
GeodeSetDefaultDriver()
. This routine takes a geode handle and a driver type and sets the system default for that type. Typically, system defaults will be set only by the Preferences Manager application.
Creating a library geode is a simple step beyond creating a normal application. Libraries can have their own process threads or not; most libraries do not, though some will. To create a library geode, you have to do four things:
Defining a Library-the sound.gp File shows an example of the three changes you must make to the geode's parameters file to make it a library.
Code Display 7-1 Defining a Library--the sound.gp File
# The Sound Library has the following three lines in its geode parameters file # (sound.gp) different from how they might appear in an application.
# # The permanent name of the geode might normally be sound.app if it is an # application. A library typically has "lib" as its name extension. # name sound.lib
# # Declare the type of the geode to be a library. Applications typically have a # "process" aspect; many libraries do not. Libraries must also be declared to # have a "library" aspect. (Note that the Sound Library is exceptional in that # it has both a driver aspect and a library aspect.) # type driver, library, single
# # Export the geode's entry point routine so the kernel may call it when the # library is loaded. This routine must be defined somewhere in the geode's code. # Note that it is exported with the "entry" line rather than the standard # "export" line; this is to distinguish the exported routine as the entry # point rather than a typical routine. # entry SoundEntry
The system provides a number of utility routines for getting information and setting attributes of geodes. These are loosely organized throughout the following sections.
GeodeGetAppObject()
GEOS
offers a routine for retrieving the optr of an application's GenApplication object.
GeodeGetAppObject()
takes the process handle of the Process object of the application. It returns the optr of the application's GenApplication object.
GeodeFind(), GeodeFindResource(), GeodeDuplicateResource(), GeodeGetInfo(), GeodeGetProcessHandle(), GeodeGetCodeProcessHandle(), ProcInfo()
GeodeFind()
returns a geode's handle when given a permanent name and attributes to search on. GEOS will search the active geode list for any geode with the given name and the proper attributes set or clear.
GeodeFindResource()
locates a given resource within a geode's file. It must be passed the file handle of the geode, the number of the resource in the file, and an offset within the resource at which the file read/write position should be placed. This routine may only be used on open geode files, and it returns the base offset and size of the resource. You will probably not need to use this routine.
To load a resource into a new block of memory, you can call
GeodeDuplicateResource()
.
GeodeGetInfo()
takes a geode handle, a
GeodeGetInfoType
parameter, and a buffer appropriate for the return information. It returns the appropriate values as specified in the
GeodeGetInfoType
parameter. (This parameter specifies what type of information is sought about the geode; the routine can return the geode's attributes, geode type, release level, protocol, token ID, or permanent name.) The possible values of
GeodeGetInfoType
are shown below:
GeodeType
).
GeodeGetProcessHandle()
returns the geode handle of the current process (the owner of the current thread). Another routine,
GeodeGetCodeProcessHandle()
, returns the handle of the geode that owns the code block from which it was called.
ProcInfo()
returns the thread handle of the first thread of a given process.
GeodeAllocQueue(), GeodeFreeQueue(), GeodeInfoQueue(), GeodeFlushQueue(), ObjDispatchMessage(), QueueGetMessage(), QueuePostMessage(), GeodeDispatchFromQueue()
The following routines allocate and manage event queues. These routines are rarely called by applications as event queues are automatically managed for each thread and application.
GeodeAllocQueue()
allocates an event queue and returns its handle.
GeodeInfoQueue()
returns the number of events in a given event queue.
GeodeFreeQueue()
frees an event queue allocated with the routine
GeodeAllocQueue()
. It must be passed the handle of the queue to be freed (unhandled events still in the queue will be discarded).
GeodeFlushQueue()
flushes all events from one queue and synchronously places them all in another queue (events may not simply be tossed out).
QueueGetMessage()
combined with
ObjDispatchMessage()
removes the first event from the given event queue and handles it via a callback routine. A far pointer to the callback routine in memory must be passed. Typically these will be used only by the assembly
ObjMessage()
routine used by the kernel; some other applications of this routine may be used, though. For example, the sound driver uses a note queue unassociated with objects and messages. The callback routine therefore gets the "event" (note) and pretends it's handling a message.
QueuePostMessage()
adds an event to the specified queue.
Every GEOS geode and VM file has both a release level and a protocol level as extended attributes of the file. These two items help ease the transitions for both programmer and user when changes are made to applications, libraries, drivers, system software, etc. To control release and protocol numbers, use the GREV tool and a REV file as described in the Grev section of the Using Tools chapter.
The release number is a
ReleaseNumber
structure which consists of four components: The
RN_major
and
RN_
minor
numbers are the most significant. The
RN_
change
and
RN_
engineering
numbers are less significant and are used primarily to indicate non-released or running upgrade types of changes to the geode.
The
ReleaseNumber
of a geode or VM file is stored in the file's FEA_RELEASE extended attribute, and its structure is shown below:
typedef struct {
word RN_major;
word RN_minor;
word RN_change;
word RN_engineering;
} ReleaseNumber;
The contents of the release number are up to the particular geode and are product-specific. Release numbers are not used by GEOS for compatibility checking or any other validation of files, though they are used during installation procedures.
To retrieve the release number of a given geode, use the routine
GeodeGetInfo()
. Release levels should be set at compile time and are not changeable at run-time.
The protocol number is a structure of type
ProtocolNumber
stored in the file's FEA_PROTOCOL extended attribute. Each GEOS geode and data file has a protocol level associated with it. The protocol level is used for compatibility checking for both geodes and documents.
The
ProtocolNumber
structure consists of two parts, the major protocol and the minor protocol. This structure is shown below:
typedef struct {
word PN_major;
word PN_minor;
} ProtocolNumber;
Differences in protocol levels indicate incompatibilities between two geodes, between a geode and its document format, or between a geode and its state file format. If the major protocols are different, the two items are not compatible at all (unless special provisions are made). If the minor protocol is greater than expected, some incompatibility may exist but should not affect the program. You should increment a geode's or document's protocol whenever a change is made.
If a change to a library is upward-compatible, only the minor protocol needs to be incremented. For example, if a library acquires a new function but the library's entry points are undisturbed, the minor protocol should be incremented and the major protocol left as is. If the new function causes relocation of the entry point numbers, however, the major protocol must also be incremented.
An application's protocol must be increased whenever a change will affect the application's state files. If you make a change to an application, for example, and the user has old state files, either the changes in the application can be replaced with the old information, or the state file will cause an incompatibility with unpredictable results. If the change to the application is simply functional, increment the minor protocol. If the change is to any part of a UI resource or to any other item saved to a state file, increment the major protocol. State files will be loaded if minor protocols are different and will be ignored if major protocols are different.
If an application's document format changes, you should make sure the new documents are not loaded by old applications or vice versa (unless you take the necessary conversion steps). When opening a document, you can check its protocol by checking the document file's extended attribute FEA_PROTOCOL. If the protocol level needs to be changed (after conversions have been done, of course), you can change them by setting FEA_PROTOCOL. (See the File System chapter.)
A few examples of when minor and major protocols should be incremented follow. Keep in mind that this list is by no means exhaustive.
GeodePrivAlloc(), GeodePrivFree(), GeodePrivWrite(), GeodePrivRead()
Every geode in the system has a "private data" area, a space set aside in its core block. This private data is used primarily by library geodes, when each of the library's clients uses its own copy of a particular data structure that gets manipulated by the library. The private data mechanism is used in the GEOS implementation of
malloc()
, for example (though you need not know this to use
malloc()
).
Private memory may be allocated, written to, read from, and freed by the library. The library does not have to allocate a block for each geode and maintain its own handle table; the use of the
GeodePriv...()
routines automatically manages this.
GeodePrivAlloc()
reserves a given number of contiguous words for the library within the private data of all geodes in the system. The memory space is reserved but is not actually allocated for a given geode until it is used (written to); this is done for optimization purposes. This routine will return a tag pointing to where the reserved words begin. This tag is used when reading, writing, or freeing the private data. If the memory could not be allocated, the routine will return zero.
GeodePrivWrite()
and
GeodePrivRead()
write to and read from the private data space. They take similar parameters: a geode handle, the tag as returned by
GeodePrivAlloc()
, the total number of words to be written or read, and a pointer to a locked or fixed buffer. In
GeodePrivWrite()
, the buffer will be passed containing the words to be written; in
GeodePrivRead()
, the buffer will be passed empty and returned containing the words read.
Typically, the geode handle passed will be zero; this indicates that the current process (which will be the library's current client) will be the owner of the private data affected. Because the library code will be executing in the thread of a given application, the application geode will be the only one having its private data affected. Thus, a library can use the same code to store different data for each geode that uses it; neither the library nor the geode needs to know that other geodes are also using the same routines.
GeodePrivFree()
frees a given number of words from all geodes' private data. It needs to be passed only the number of words to be freed and the tag as returned by
GeodePrivAlloc()
.
Every geode can have an icon associated with it. Typically, only applications will have special icons; other geodes (libraries and drivers) generally use one of the system icons or the icon of the application they're primarily used by.
An application's icon is stored in two places: It is defined and stored within the application's extended attributes in its .GEO file. It is also stored in a database file maintained by the UI, called the token database .
The GEOS development kit includes an icon editor tool so you can easily create icons of various color and resolution characteristics and install them into your applications.
Managing the Token Database File
TokenOpenLocalTokenDB(), TokenCloseLocalTokenDB()
The token database is stored in its own file. Each entry represents a single icon or series of icons that can be used with any number of files. The token database file is managed by the UI and the kernel; you should have no reason to access its internals directly, and most applications will never need to use any of the token routines except to install their document icons.
Some systems may have shared token database files; this is controlled by the INI file key
sharedTokenDatabase
in the
paths
category. Most often a shared database file exists on a network drive and may be supplemented with a local token database. By default, if a shared database file exists, it will be opened read-only in addition to the read/write local file.
You can open and close only the local database file, however, with the routines
TokenOpenLocalTokenDB()
and
TokenCloseLocalTokenDB()
.
Every GEOS file has a token. The token is an index into the token database. When GeoManager scans a directory, it grabs the token from each file and searches through the token database file for it. If a match is found, GeoManager selects the proper icon and displays it; if no match is found or if the file's token is invalid, GeoManager will launch the application in engine mode and request that it install its token in the database.
For non-GEOS files, GeoManager uses the three extension characters of the file's name as a pseudo-index. For each extension (e.g. .COM, .EXE, .DOC, .BAT, etc.), GeoManager uses a single icon. Which icon is used can be set in the GEOS.INI file if a user wishes, but GeoManager will normally select the default DOS icon (of which there are two: one for executables and one for non-executables).
The index into the token database consists of two parts and is of type
GeodeToken
. This structure contains four text characters as well as the manufacturer ID number of the geode's manufacturer. The structure's definition is shown below:
typedef struct {
TokenChars GT_chars;
ManufacturerID GT_manufID;
} GeodeToken;
typedef char TokenChars[TOKEN_CHARS_LENGTH];
This structure is created and filled automatically in the geode's
.geo
file header by the Glue linker, which takes the values from the geode's geode parameters (
.gp
) file. The two fields used from the
.gp
file are
tokenchars
for the four characters and
tokenid
for the manufacturer ID.
A
GeodeToken
structure in the token database file can also be filled in when GeoManager scans a directory. If the header of a particular application's
.geo
file does not have a recognized token, GeoManager will launch the application in "engine" mode, loading its GenApplication object. It will request that the application object then install its icon into the token database file (
GenApplicationClass
knows how to do this).
The token database file contains one entry for each token that has been installed. Each time a new token and icon is encountered, a new entry is added by the UI. This happens automatically when GeoManager scans a directory.
TokenDefineToken(), TokenGetTokenInfo(), TokenLookupMoniker(), TokenLoadMonikerBlock(), TokenLoadMonikerChunk(), TokenLoadMonikerBuffer(), TokenRemoveToken(), TokenLoadTokenBlock(), TokenLoadTokenChunk(), TokenLoadTokenBuffer(), TokenLockTokenMoniker(), TokenUnlockTokenMoniker(), TokenGetTokenStats(), TokenListTokens()
In nearly all cases, you will create your application's icon with the icon tool and not worry about it again. However, the following routines allow you to add, change, access, and remove entries from the token database.
TokenLoadTokenBlock()
,
TokenLoadTokenChunk()
, and
TokenLoadTokenBuffer()
load a
TokenEntry
structure into memory (a newly allocated block, a newly allocated chunk, or a locked buffer). The
TokenEntry
structure contains information about the token, the geode's release number, and the geode's protocol number. This structure does not actually contain the monikers used for the icon.
TokenLookupMoniker()
gets the specific moniker of a token entry given the display type (CGA, EGA, VGA, etc.), the entry's
GeodeToken
structure, and search flags. If the moniker is found, the entry identifier (database group and ID numbers) of the moniker are returned. You can use these return values to lock the moniker into memory (see below).
TokenLockTokenMoniker()
locks a moniker into memory given its entry identifier. This routine returns a pointer to a locked block and the chunk handle of the chunk containing the locked moniker. A moniker should always be locked before it is drawn; this keeps it from moving in memory while it is being accessed.
The routine
TokenUnlockTokenMoniker()
unlocks a previously locked moniker when given the moniker's segment address. This unlocks the entire block, not just the individual moniker.
TokenLoadMonikerBlock()
,
TokenLoadMonikerChunk()
, and
TokenLoadMonikerBuffer()
load a specific moniker into memory from the token database file (into a newly allocated block, a newly allocated chunk, or a locked buffer). It takes the same parameters as
TokenLookupMoniker()
but returns the handle and chunk handle of the loaded moniker. If using this routine, simply lock the memory block rather than using
TokenLockTokenMoniker()
.
TokenGetTokenInfo()
finds a token when passed its
tokenchars
and
tokenid
and returns the token's flags. If no token exists with the passed characteristics, it will return an error flag.
TokenDefineToken()
adds a new token and its moniker list to the token database. If the given token already exists, the new one will replace the old one. The token identifier (
tokenchars
,
tokenid
), handle and chunk handle of the moniker list, and flags of the new token must be passed.
TokenRemoveToken()
removes a given token and its moniker list from the token database file. It returns only a flag indicating whether the token was successfully removed or not.
TokenListTokens()
returns a list of the tokens in the token database. It is passed three arguments:
TokenRangeFlags
, which specifies which tokens should be returned. The following flags are available:
TokenListTokens()
allocates a global memory block and copies all the specified tokens into that block. It leaves a blank space at the beginning of the block; this space is the size specified by the second argument. The rest of the block is an array of
GeodeToken
structures.
TokenListTokens()
returns a dword. The lower word of the return value is the handle of the global memory block; the upper word is the number of
GeodeToken
structures in that block.
Almost all users enjoy configuring their systems to their own tastes, whether it's setting the background bitmap or choosing a default font. Most applications will, therefore, want to provide a way for the user to choose and save various options for the application.
Generic objects have this capability built in. If you set them up for saving options, they will automatically set and maintain the saved options, making sure the options are set properly whenever the application is launched. However, sometimes you will want to set additional options not managed by UI objects. This is not difficult to do in GEOS: You can have your application save its options directly to the local initialization file, GEOS.INI.
All appropriate generic UI objects have the ability to save their options. For example, a properties GenInteraction could save which of its options are on and which are off when the user presses a "save options" trigger.
To save generic object options, you have to do two basic things:
Saving Generic Object Options shows an example of how objects should be declared for saving their options.
Code Display 7-2 Saving Generic Object Options
/* The GenApplication object must declare a GCN list of the type appropriate for * the options being saved (or both types if the application uses both types). * This GenApplication declares a list of objects whose options do not have to be * loaded at startup. */
@object GenApplicationClass SampleApp = {
GI_visMoniker = "Sample Application";
GI_comp = SamplePrimary; /* Primary window object is the only child. */
gcnList(MANUFACTURER_ID_GEOWORKS, GAGCNLT_WINDOWS) = SamplePrimary;
/* The above list is to declare windowed objects that must appear
* when the application is opened and made usable. */
gcnList(MANUFACTURER_ID_GEOWORKS, GAGCNLT_SELF_LOAD_OPTIONS) =
SampleController;
/* The above list is used for generic objects that save their
* options but do not need their options loaded at startup. */
}
/* Some applications that have generic objects save their own options might not * have a special Options menu but may just have a trigger somewhere for saving * options. In any case, the "save options" trigger sends MSG_META_SAVE_OPTIONS to * its GenApplication object. If you use an Options menu with GIGT_OPTIONS_MENU * set, this will automatically be built into the menu. * The SampleOptionsMenu is a child of SamplePrimary, not shown. */
@object GenInteractionClass SampleOptionsMenu = {
GI_comp = SampleToolbox, SampleToolControl, SampleSaveOptsTrigger;
GII_visibility = GIV_POPUP; /* Make it a menu. */
ATTR_GEN_INTERACTION_GROUP_TYPE = (GIGT_OPTIONS_MENU);
}
/* The other objects (controllers) are not shown here. Just the "save options" * trigger, which sends MSG_META_SAVE_OPTIONS to the GenApplication object. */
@object GenTriggerClass SampleSaveOptsTrigger = {
GI_visMoniker = `S', "Save Options";
GTI_destination = SampleApp;
GTI_actionMsg = MSG_META_SAVE_OPTIONS;
}
GEOS can use multiple initialization files in network situations, but only the local GEOS.INI is editable. The local GEOS.INI (referred to hereafter simply as GEOS.INI or "the INI file") is read first into a moveable, swappable buffer, and it contains the path names of any other INI files to be used. The other INI files are subsequently loaded into other buffers. When the kernel searches for a specific entry in one of the INI files, it looks first in the local INI's buffer and then in any others in the order they were loaded. When it reaches what it wants, it stops searching. Thus, multiple entries are allowed but are not used, and the local INI file has precedence over all others.
The GEOS.INI file has a very specific format and syntax. It is segmented into categories , each of which contains several keys that determine how GEOS will perform. (For an example of an INI file, see Example GEOS.INI File Entries .) Note that typically the GEOS.INI file should not be accessed directly because its format is subject to change.
A category is essentially a group of keys. Certain geodes will work with their own categories, and certain categories will be used by several geodes. For example, the
system
category is used by the kernel and the UI to determine several of the default system settings. Each category is set in GEOS.INI by putting its name within square brackets. The text within the brackets is case-insensitive and ignores white space, so [My category], [mycategory], and [MY CATEGORY] are all equivalent.
A key is any setting that the system or an application can recognize and assign a value to. A single key may exist in several different categories; since both category and key define an entry, the entries will be unique if either key or category is different. Keys may be text strings, integers, or Boolean values, and every key is identified by its name, which is an ASCII string. Data may also be read from and written to the file in binary form; the kernel will automatically convert this into ASCII hexadecimal for storage and will revert it to binary during retrieval.
Both category names and key fields are called "entries." Each entry may exist on a single line or may cover several lines. An entry that contains carriage returns is technically known as a "blob." Each blob will automatically be enclosed in curly braces when it is written into the file. Any blob that contains curly braces will automatically have backslashes inserted before the closing braces so GEOS doesn't mistake them for the blob delimiters.
Comments may be added to the INI file by putting a semicolon at the beginning of the line. The file has several standard categories and keys that can be set within them. These are detailed in the INI File chapter .
Code Display 7-3 Example GEOS.INI File Entries
; The category "system" is used by the kernel and the UI to set certain system ; defaults such as the number of handles, the default system font and size, and ; the types of memory drivers to be loaded.
[system]
; The handles key is assigned an integer that determines the number of handle ; spaces allocated for the system's Handle Table. handles = 2500
; The font key is assigned a character string that represents a file name ; or a list of file names separated by spaces. ; The listed file(s) will be read in as the font driver geode. font = nimbus.geo
; The memory key is assigned a blob of text containing all the names of the memory
; drivers available to the system.
memory = {
disk.geo
emm.geo
xms.geo
}
; The category "MyApp's Category" is set for example only. It is used by the MyApp ; application.
[My App's Category]
; The myappHiScore key is an integer key set by the MyApp application. myappHiScore = 52
; The myappBoolean key is a Boolean value. Booleans are case-insensitive, so True, ; true, and TRUE are all equated to "true". This is actually a Boolean value and ; is translated by the read and write routines that work with Boolean values. myappBoolean = true
; The myappHiName is a text string that, in this case, contains carriage returns,
; backslash characters, and curly brace characters. The original text looked like
; this:
; this is a multi-line
; blob of text with curly
; brace ({,}) characters }} in it
; It is automatically given backslashes in front of the closing braces, and it
; is automatically surrounded with curly braces.
myappHiName = {this is a multi-line
blob of text with curly
brace ({,\}) characters \}\} in it}
InitFileSave(), InitFileRevert(), InitFileGetTimeLastModified(), InitFileCommit()
Because the INI file is common to all geodes and to all threads in the system, only one thread at a time may access it. This synchronization is handled by the kernel whenever a routine to read from or write to the INI file is used. Additionally, the INI file is loaded into a buffer when the system is first run; all operations on the INI file actually work on this buffer, and the buffer may be flushed to disk only by the kernel.
There are, however, four routines that work directly on the INI file. One saves the file, another reverts it from the last save, the third checks the time the file was last modified, and the fourth commits any pending INI file changes to disk.
To save the local GEOS.INI, use the routine
InitFileSave()
; this saves the changes to the backup file. It requires no parameters and returns an error flag if the file could not be saved.
InitFileRevert()
reverts the GEOS.INI file to its state the last time it was backed up. This routine takes no parameters, and it returns an error flag if the revert can not be accomplished.
InitFileGetTimeLastModified()
returns the system counter's value stored the last time GEOS.INI was modified.
InitFileCommit()
takes all the changes made since the file was last modified and flushes them to disk. This commits all the changes and should be used only from the kernel. It should not be used by applications.
InitFileWriteData(), InitFileWriteString(), InitFileWriteStringSection(), InitFileWriteInteger(), InitFileWriteBoolean()
GEOS provides five routines to write to the INI file, one for each of the allowable data types. Each of these routines will first gain exclusive access to the local INI buffer, then locate the appropriate category for writing. After writing the key and its value, the routine will relinquish exclusive access to the buffer, allowing other threads to write into it. Writing a category or key that does not exist in the local INI file will add it to the file. If you use a routine to write keys or categories to the INI file, be sure to first convert the strings to the acceptable ASCII hex format using InitFileMakeCanonicKeyCategory().
Each of these routines takes at least three arguments: The category is specified as a null-terminated character string; a pointer to the string is passed. The key name is also specified as a null-terminated character string; again, a pointer to the string is passed. The third parameter is specific to the routine and contains the value to which the key will be set.
InitFileWriteData()
writes a number of bytes into the INI buffer, and it takes four parameters. The additional parameter is the size of the data. The data will be converted into ASCII hexadecimal when the file is saved and will be converted back when the key is read.
InitFileWriteString()
takes a pointer to the null-terminated character string to be written. If the character string contains carriage returns or line feeds, it will automatically be converted into a blob.
InitFileWriteStringSection()
writes a new string section (a portion of a blob) into the specified entry. The specified entry must be a blob already, and the string section will be appended to the blob. A string section is a line of a blob delineated by line feeds or carriage returns.
InitFileWriteInteger()
takes as its third argument the integer to be written.
InitFileWriteBoolean()
takes a Boolean value. A zero value represents false, and any nonzero value represents true. When looking at the INI file with a text editor, the Boolean value will appear as a text string of either "true" or "false"; it will, however, be interpreted as a Boolean rather than a text string.
InitFileReadDataBuffer(), InitFileReadDataBlock(), InitFileReadStringBuffer(), InitFileReadStringBlock(), InitFileEnumStringSection() InitFileReadStringSectionBuffer(), InitFileReadStringSectionBlock(), InitFileReadInteger(), InitFileReadBoolean()
When you want to check what a key is set to in the INI file, you should use one of the
InitFileRead...()
routines. These search the local INI file first and then each of the additional INI files in the order they were loaded. They will return the first occurrence of a given key, so if the key exists in both your local INI file and another INI file, these routines will return only the local value.
All of these routines take at least two parameters. The first is the category of the entry to be retrieved; this is stored as a null-terminated ASCII string, and a pointer to the string is passed. The second is the key of the entry. This, too, is stored as a null-terminated ASCII string, and a pointer to the string is passed.
InitFileReadBoolean()
returns the Boolean value of the given key. If the key is set "false," a value of zero (FALSE) will be returned. If the key is set "true," a nonzero value will be returned (-1, the constant TRUE).
InitFileReadInteger()
returns the integer value of the given key.
InitFileReadDataBuffer()
and
InitFileReadDataBlock()
both return the data bytes stored in the given key. The first, however, takes the address of a buffer already allocated and puts the data into the buffer. The second allocates a new block on the heap and puts the data into it. If you don't know the size of the data, you should use
InitFileReadDataBlock()
.
InitFileReadStringBuffer()
and
InitFileReadStringBlock()
both return the null-terminated string stored in the given key. In both cases, curly braces will be stripped off of blobs and backslash characters will be removed if appropriate. The first, however, takes the address of a buffer already allocated and puts the string in the buffer. The second allocates a new block on the heap and returns the string in it. If you don't know the approximate size of the string, use
InitFileReadStringBlock()
.
InitFileReadStringSectionBuffer()
and its counterpart
InitFileReadStringSectionBlock()
both return a null-terminated section of the string stored in the given key. A string section is defined as any number of contiguous printable ASCII characters and is delimited by carriage returns or line feeds. These routines take the number of the string section desired and return the section (if it exists).
InitFileReadStringSectionBuffer()
takes the address of a buffer already allocated on the heap and returns the string section in the buffer.
InitFileReadStringSectionBlock()
allocates a new block on the heap and returns the string section in it. You should use this routine if you don't know the approximate size of the string section.
InitFileEnumStringSection()
enumerates the specified blob, executing a specified callback routine on each string section within the blob.
InitFileDeleteEntry(), InitFileDeleteCategory(), InitFileDeleteStringSection()
Besides reading and writing data, you can delete categories and keys that were previously entered.
InitFileDeleteEntry()
takes pointers to both the null-terminated category name and the null-terminated key name and deletes the entry.
InitFileDeleteCategory()
takes only a pointer to the null-terminated category name and deletes the entire category, including all the keys stored under it.
In addition, you can delete a single string section from a specified blob.
InitFileDeleteStringSection()
takes the category and key names of the blob as well as the index of the string section, and it deletes the string section. If the string section does not exist or if either the key or category can not be found, the routine will return an error flag.
The kernel provides a number of routines that fulfill general needs for system utilities. These range from setting the system's date and time to retrieving the amount of swap memory used to shutting down the system in order to execute a DOS-based program.
System Statistics and Utilities
TimerGetDateAndTime(), TimerSetDateAndTime(), TimerGetFileDateTime()
The system clock reflects the current date and time of day.
TimerGetDateAndTime()
takes a pointer to a
TimerDateAndTime
structure and fills it with the current year, month, day, day of week, hour, minute, and second.
TimerGetFileDateTime()
is a similar function--it gets the current date and time, returning them in structures used when working with files.
TimerSetDateAndTime()
sets the current date and time to the values in the passed structure. It is unusual for any application other than the Preferences Manager to change the system clock.
TimerStart(), TimerStop(), TimerSleep(), TimerGetCount()
If your geode needs an action to happen on a timed basis, you will want to use a timer. GEOS lets you set up timers that will call a routine or send a message after a given interval has passed. GEOS offers four types of timers:
All timers except sleep timers are created and started with the routine
TimerStart()
, and continual timers can be destroyed with
TimerStop()
.
TimerStop()
may also be used to prematurely stop a one-shot timer, though if the one-shot sent a message, the message could be in the recipient's queue even after
TimerStop()
was called.
Sleep timers are created and started with
TimerSleep()
, and they do not need to be stopped.
An additional routine,
TimerGetCount()
, returns the current system counter. The system counter contains the number of ticks counted since GEOS was started.
SysStatistics(), SysGetInfo(), SysGetConfig(), SysGetPenMode(), SysGetDosEnvironment()
Occasionally, a geode will need to query the kernel about the state or configuration of the system. GEOS offers five routines for this:
SysStatistics()
returns a structure of type
SysStats
. This structure contains information about how busy the CPU is, how much swap activity is going on, how many context switches occurred in the last second, how many interrupts occurred in the last second, and how many runnable threads exist. You can not set or otherwise alter this structure.
SysGetInfo()
returns a particular statistic about the current system status. It can return a number of different statistics including the CPU speed, the total number of handles in the handle table, the total heap size, the total number of geodes running, and the size of the largest free block on the heap. Most of the information your application can request with this routine can be garnered from the Perf performance meter; it is easiest to use Perf when debugging, though a few geodes will need to know this information.
SysGetConfig()
returns information about the current system's configuration including the processor type and machine type. It also returns flags about the particular session of GEOS including whether this session was started with Swat, whether a coprocessor exists, and other information.
SysGetPenMode()
returns TRUE if GEOS is currently running on a pen-based machine.
SysGetDosEnvironment()
returns the value of a DOS environment variable. It is passed a string representing the variable name and returns a string representing the value.
DosExec(), SysShutdown()
There are two ways for a geode to shut down GEOS. The first,
DosExec()
, shuts the system down to run a program under DOS, returning after the DOS program has finished--unless a task-switch driver is in use, in which case the system will create a new task and cause the task-switcher to switch to the new task.
The second,
SysShutdown()
, forces the system to shut itself down completely. Neither of these routines is commonly used by anything other than the kernel, GeoManager, special "launcher" programs, or the UI. Their use by other libraries or applications is discouraged unless absolutely necessary.
DosExec()
takes several parameters including the pathname of the DOS program to be run, arguments for the program, an optional disk handle of the disk that contains the program to be run, the optional directory and disk handle in which the program should be executed, and a record of
DosExecFlags
. If the return value is nonzero, an error occurred in loading the DOS program, and you can use
ThreadGetError()
to check what error occurred. Note that
DosExec()
always returns. Applications should
not
rely on
DosExec()
shutting the system down; if a task switcher is present, GEOS will be swapped out rather than shut down.
SysShutdown()
causes GEOS to exit in one of several ways. This routine should be passed a shutdown mode. If the mode is SST_CLEAN, SST_RESTART, SST_SUSPEND, or SST_CLEAN_FORCED, the routine will return; otherwise, it will not return and the shutdown will commence. If SST_CLEAN is passed, the shutdown may be aborted after
SysShutdown()
returns. You can have
SysShutdown()
cause GEOS to reboot itself after shutting down (as the Preferences Manager application does for certain preferences settings), but this starts GEOS fresh. This routine is very rarely used by anything other than the UI, the kernel, or the Preferences Manager application.
If something else (typically the UI or task switcher) shuts the system down, objects that register for shutdown notification will receive
MSG_META_CONFIRM_SHUTDOWN
. The application should call
SysShutdown()
with the mode SST_CONFIRM_START; this allows the object to have exclusive rights for asking the user to confirm the shutdown (when the object is finished with the user interaction, it can call
SysShutdown()
with SST_CONFIRM_END to release exclusive access). This is useful if your application or library has an ongoing operation and wants to verify the shutdown with the user.
GEOS has two versions of its system software. The normal version as shipped retail is the "non-error-checking" version. The other, used for debugging applications and other geodes, is called the "error-checking" version, or EC version . Nearly all components of the system software exist in both versions--the kernel, the UI, libraries, drivers, etc.
Together, Swat and the EC version provide extensive and superb debugging power. The EC version allows you to call special error-checking routines to check the integrity of handles, resources, memory, files, and other things. These error-checking routines all begin with EC; for more information, see their entries in the Routine Reference Book. In addition to the full error checking provided by the EC versions of the system geodes, you can add your own error-checking code to your programs.
Adding EC Code to Your Program
When compiling your code, you can include certain extra lines in either the EC version or the non-EC version of your program. For example, during debugging you want your program to check the validity of each memory handle before locking the memory block; for the final version of your program, however, you don't want this validity check because it slows down your program. You can easily add a few instructions that will be compiled only into the EC version.
GEOS provides five macros, listed below, for version-specific instructions. These macros must be treated as statements; they may not be used in expressions.
FatalError()
with a specified error code. The call to
FatalError()
will not be included in the non-EC version.
FatalError()
will be called. If the condition is met,
FatalError()
will be called.
ECCheckBounds()
routine. If the pointer is out of bounds,
ECCheckBounds()
will call
FatalError()
. This macro may only add the bounds check to the EC version.An example of use of these macros is shown in EC Macros .
/* This code display shows only the usage of these macros; assume that each * line shown below exists within a particular function or method. */
/* The EC macro adds a line of code to the EC version. Its format is
* EC(line) where line is the line of code to be added.
* Note that the NEC macro is similar. */
EC( @call MyErrorDialogBox::MSG_MY_ERR_PRINT_ERROR(); )
NEC( @call MyErrorDialogBox::MSG_MY_ERR_PRINT_NO_ERROR(); )
/* The EC_ERROR macro adds a call to FatalError() to the EC version. Its format is
* EC_ERROR(code) where code is the error code to be called. */
EC_ERROR(ERROR_ATTR_NOT_FOUND)
/* The EC_ERROR_If macro is similar to EC_ERROR but is conditional. Its format is
* EC_ERROR_IF(test, code) where test is a Boolean value and code is the
* error code to be called. */
lockVariable = MyAppCheckIfLocked(); /* TRUE if inaccessible. */
EC_ERROR_IF(lockVariable, ERROR_ACCESS_DENIED) /* Error if inaccessible. */
/* The EC_BOUNDS macro adds a call to ECCheckBounds() to the EC version.
* Its format is
* EC_BOUNDS(addr) where addr is the address to be checked. */
myPointer = MyAppGetMyPointer();
EC_BOUNDS(myPointer)
SysGetECLevel(), SysSetECLevel(), SysNotify(), EC...(), CFatalError(), CWarningNotice()
While using Swat and the EC version, you can set and retrieve the current level of error checking employed. The level is set with a record of flags, each of which determines whether a certain type of checking is turned on. This record is of type
ErrorCheckingFlags
.
You can retrieve it with
SysGetECLevel()
and set it with
SysSetECLevel()
.
SysNotify()
puts up the standard error dialog box--white with a black frame. This routine is available in both the EC and the non-EC versions of the kernel. This is the box used by the kernel to present unrecoverable errors to the user. You can also call it up and allow the user any of five options: retry, abort, continue, reboot, or exit. Usually, this dialog box is used only for errors, but it can be used for other user notification as well. (Note that with the "exit" and "reboot" options, this routine will
not
return. Note also that only certain combinations of the five options are supported.)
The
CFatalError()
and
CWarningNotice()
routines provide run-time and compile-time error or warning messages. The
CFatalError()
routine calls the kernel's
FatalError()
function, which puts up an error dialog box and, in Swat, causes Swat to hit a breakpoint so you can debug the error.
CWarningNotice()
may be put in error-checking code to make the compiler put up a compile-time warning note.
Applications do not usually need to communicate with each other. Most applications will interact only with the kernel and with libraries; such applications don't even notice if any other applications are running. However, a few applications will need to communicate with other applications.
For example, many desktop-managers provide a "print file" command; the user selects a file's icon, then chooses this command to print out the appropriate file. The desktop manager will have no way of knowing how to print the file; after all, the file could have been created by an application which hadn't even been written when the desktop manager was installed. The desktop manager therefore causes the appropriate application to be started. It then instructs the application to print the file.
Because GEOS is an object-oriented system, there is a natural model for inter-application communication. When one application needs to contact another, it can simply send a message; thus, sending information or instructions to another application is not, in principle, different from sending it from one object to another within an application.
The GEOS Inter-Application Communications Protocol (IACP) specifies how to open communications with another application, and how to send messages back and forth.
There is a major difference between sending a message within an application, and sending one to a different application. When you send a message from one object to another within an application, you know that the recipient exists, and you know the optr of that recipient. This makes it easy to send messages.
When you send a message to another application, however, you do not (at first) know any optrs to that application. In fact, you may not even know that the application is running. Often, all you will know is something like, "I want to send a message to
SpiffyWrite
".
GEOS uses a client-server model of inter-application communication. Every
GeodeToken
corresponds to a server. Whenever an application is launched, GEOS checks to see if there is a
server-list
corresponding to the application's token. If there is, GEOS adds the app's Application object to the server-list; if there is not, GEOS creates a server-list and adds the Application object to that list.
For example, suppose the user launches a single copy of
SpiffyWrite
; this application has a manufacturer-ID of MANUFACTURER_ID_SPIFFYWARE, and the token characters "SWRI". GEOS will check if there's a server-list for that
GeodeToken
. Let us suppose there isn't such a list; GEOS will automatically create one, and add
SpiffyWrite
's Application object to that list. The Application object is now said to be a
server
for the list.
Now let us suppose another application needs to contact
SpiffyWrite
; for example, perhaps a desktop program needs to print a
SpiffyWrite
file. It tells the kernel that it would like to be a
client
on the list for the token "{MANUFACTURER_ID_SPIFFYWARE, "SWRI"}". GEOS will check to see if a server-list for that token exists. If so, it will add the client to that list; this will cause a notification message (MSG_META_IACP_NEW_CONNECTION) to be sent to every server for that list.
Once a client is linked to a server, it can send a message to the server list. It does this by encapsulating a message, then passing the encapsulated message to the server-list. GEOS will dispatch the message to every server for the list; the server objects will receive it just like any ordinary message. (It actually passes the encapsulated message to the server object as an argument to
MSG_META_IACP_PROCESS_MESSAGE
; the server can then dispatch the message to the final recipient.)
This establishes the link between the applications. The client can pass an optr to the servers by putting it in the encapsulated message; a server object can then send messages straight to a particular object.
When a client no longer needs to communicate with a server, it unregisters itself from the server list. GEOS then sends a notification message to every server object.
Customarily, whoever allocates a global resource must also free it. For example, if a client might pass information to the server by allocating a global block, writing the data to the block, and passing the block to the server. The server should notify the client when the server is finished with that data; the client can then free the block. Similarly, if a server allocates a block to pass information to a client, the server should free the block.
There may be several servers for a given server list. For example, if three copies of
SpiffyWrite
were running at once, each of their Application objects would be a server for the same server-list. Furthermore, any object can make itself a server for any list. All servers will receive copies of every message sent to the server list. To distinguish between different servers for a list, every server for a list is assigned a distinct
ServerNumber
. If it chooses to, a client can specify that a message be sent only to the server with a specific number.
GenApplicationClass
is built to support IACP automatically. If a server or client object is subclassed from
GenApplicationClass
, most of the work of supporting IACP is done transparently to the application writer. The following capabilities are built in:
MSG_META_IACP_PROCESS_MESSAGE
, the Application object automatically dispatches the message to the appropriate location.
IACPShutdownAll()
to shut down all IACP links it has open, whether it is a client or a server on those links.
IACPShutdown()
for that connection. It also forwards this message to all Document objects, so a Document object will know to close itself if the IACP connection was the only reference to it. Again, the Application object does this whether it is a client or a server.
IACPShutdownAll()
to shut down all IACP links the Application object has open, whether it is a client or a server on those links. You can subclass this message if you need to take some additional action when the IACP connections are severed.IACPSendMessage(), IACPSendMessageToServer()
Either a client or a server may send messages over an IACP link. Both clients and servers use the same technique. The message sender encapsulates a message, and passes the encapsulated message to
IACPSendMessage()
.
IACPSendMessage()
dispatches the message to every object on the other side of the link. For example, if a client passes a message to
IACPSendMessage()
, that message will be dispatched to every server object for the specified list.
IACPSendMessage()
is passed five arguments:
EventHandle
of an encapsulated message.
TravelOption
for that message.
IACPSide
enumerated type. This tells whether the message is being sent by a client or a server. If you pass the value IACPS_CLIENT, the message will be dispatched to all servers; if you pass IACPS_SERVER, the message will be dispatched to all clients.
The message will be dispatched to all geodes on the other side of a link. Note that a client need not send the message to the server object per se. It can use the travel options field to direct the message anywhere within the server object's geode. It can also specify the optr of the recipient when it encapsulates the message; in this case, it should pass a
TravelOption
of -1.
Every time the encapsulated message is successfully handled, the "completion message" will be dispatched. Typically, the completion message is addressed to the object that called
IACPSendMessage()
, instructing it to free global resources that had been allocated for the message.
The routine returns the number of messages that were dispatched. This lets the sender know how many completion messages to expect, and lets it properly initialize all reference counts to global resources.
A client may choose to send a message to a specific server. It can do this by calling
IACPSendMessageToServer()
. This takes almost the same arguments as
IACPSendMessage()
. However, instead of being passed an
IACPSide
value, it is passed a server number. GEOS will dispatch a single copy of the message to the specified server.
IACPSendMessageToServer()
returns the number of times the message was dispatched. This will ordinarily be one; however, if the specified server is no longer registered, it will be zero.
Any object can register as a client for an IACP server list. When an object is a client, it can send messages to the server list, which will pass them along to the servers for that list.
IACPConnect(), IACPCreateDefaultLaunchBlock()
To register as a client for a list, call the routine
IACPConnect()
. When you call this routine, you specify which server list you are interested in. If there is no such server list running, you can instruct the kernel to start up the server list, as well as one of the default applications for that list.
IACPConnect()
is passed five arguments:
GeodeToken
of the list for which you want to register.
IACPConnectFlags
. The following flags are available:
AppLaunchBlock
, with the ALB
_appMode
field set to MSG_GEN_PROCESS_OPEN_APPLICATION; you must also set IACPCF_SERVER_MODE to IACPSM_USER_INTERACTIBLE.
IACPServerMode
enumerated type. This type specifies how the client expects the server to behave. Currently, only two types are supported:
MemHandle
of an
AppLaunchBlock
. If the server you specify is not running, GEOS will launch the application specified by the
AppLaunchBlock
. If you pass a null
MemHandle
, GEOS will return an error if no server is running.
IACPConnect()
will write the number of servers on the list to that word.
IACPConnect()
returns a word-sized
IACPConnection
token. You will need to pass that token when you call another IACP routine to use the connection. It will also return the number of server objects on the list; it returns this value by writing it to the address indicated by the pointer passed.
If the server list you indicate is not currently running, IACP may do one of two different things. If you pass a null handle as the third argument,
IACPConnect()
will fail. It will return the error value IACP_NO_CONNECTION, and indicate that there are no servers on the specified list.
If you pass an
AppLaunchBlock
,
IACPConnect()
will examine that launch block to see what application should be launched to act as a server. The
AppLaunchBlock
should specify the location and name of the application to open. If the ALB
_appRef.AIR_diskHandle
field is non-zero,
IACPConnect()
will look in the specified disk or standard path for an application with the right
GeodeToken
; otherwise, it will look in the standard places for an application.
Note that if you pass a launch block to
IACPConnect()
, you may
not
alter or free it afterwards. If the application is created, the block you pass will be its launch block; if not, the kernel will free the block automatically. In any event, the caller no longer has access to the block.
If
IACPConnect()
launches an application, the caller will block until that application has been created and registers for the server list. If the application does not register for that list, the caller will never unblock. You must therefore make sure that you are launching the right application for the list. Note that every application object will automatically register for the server list which shares its token.
To create a launch block, you should call
IACPCreateDefaultLaunchBlock()
. This routine is passed a single argument, which specifies how the application will be opened. That argument must be MSG_GEN_PROCESS_OPEN_APPLICATION (the application will be opened as a standard, user-interactible application); MSG_GEN_PROCESS_OPEN_ENGINE (the application will be opened in engine mode, i.e. with no user interface); or MSG_GEN_PROCESS_OPEN_CUSTOM (which has an application-specified meaning).
IACPCreateDefaultLaunchBlock()
allocates a launch block and sets up its fields appropriately. As created, the launch block will have the following characteristics:
_diskHandle
field) will be SP_DOCUMENT.
_appRef.
AIR
_diskHandle
will be zero);
IACPConnect()
will attempt to find the application on its own.
_dataFile
will be blank).
_genParent
will be null).
_extraData
will be zero).
_userLoadAckOutput
and
ALB
_userLoadAckMessage
will be null.)
IACPCreateDefaultLaunchBlock()
returns the handle of the newly-created launch block. Once the block is created, you can alter any of its fields before passing the launch block to
IACPConnect()
. (Once you pass the launch block to
IACPConnect()
, you may not alter it any more.)
Often a client will want the server to open a specific document. For example, if a desktop-manager is implementing a "print-file" command, it will need to open a server application, instruct it to open the file to be printed, and then instruct it to print the file. To make the server open a document, pass the document's name in ALB
_dataFile
. The server will open the file when you register, and close it when you unregister.
IACPShutdown(), IACPShutdownAll()
If an application no longer needs to interact with a particular server list, it should call
IACPShutdown()
. This routine is also used by servers which wish to remove themselves from a server list. This section describes how the routine is used by clients; Being a Server
describes how it is used by servers.
The routine is passed two arguments:
IACPConnection
token for the link which is being shut down.
IACPShutdown()
sends MSG_META_IACP_LOST_CONNECTION to all objects on the other side of the link; that is, if a client calls
IACPShutdown()
, all servers on the list will be sent this message.
IACPShutdownAll()
closes all IACP links for the application which calls it. The Application object automatically calls this routine when the application is exiting.
Every time an application is launched, its Application object automatically registers as a server for the server list that shares its
GeodeToken
. The Application class has default handlers for all the notification messages IACP sends to the server objects.
If you wish, you can have another object act as a server. However, if you do this, you will have to do more of the work yourself. While the notification messages are defined for
MetaClass
, and thus can be handled by any class of object,
MetaClass
does not come with handlers for these messages; if the server is not subclassed from
GenApplicationClass
, you will have to write the handlers yourself. This is discussed below.
IACPRegisterServer(), IACPUnregisterServer(), IACPGetServerNumber()
You will not generally need to register and unregister a server object explicitly. As noted above, when an application is launched, the application object is automatically registered as a server for the list with its
GeodeToken
; when the application exits, the Application object is automatically unregistered from that list.
However, you may wish to explicitly register an object as a server. For example, you might want your application object to be a server on a list with a different
GeodeToken
; or you might want to register a non-Application object as a server. In this case, you will need to explicitly register and unregister the object.
To register an object as a server, call
IACPRegisterServer()
. This routine is passed the following arguments:
GeodeToken
of the list for which you are registering as a server.
GenApplicationClass
).
IACPServerMode
enumerated type. This type specifies how the client expects the server to behave. Currently, only two types are supported:
IACPServerFlags
. Currently, only two flags are supported: IACPSF_MULTIPLE_INSTANCES indicates that multiple copies of the application might be running at once (Every multi-launchable application should set this flag.); IACPSF_MAILBOX_DONT_ASK_USER asks the Mailbox library not to notify the user of new messages for this application.
IACPRegisterServer()
registers the object as a server for the appropriate list; it creates the server list if necessary.
To unregister an object as a server, call
IACPUnregisterServer()
. This routine is passed two arguments: the
GeodeToken
of the server list, and the optr of the server. The object will be removed immediately from the server list. Note, however, that the server list might have already dispatched some messages to the server being removed; these messages might be waiting on the server object's queue, and thus the server object might get some IACP messages even after it calls
IACPUnregisterServer()
. One way to deal with this is to have the server object send itself a message, via the queue, immediately after it calls
IACPUnregisterServer()
. When the object receives this message, it will know that it has no more IACP messages on its queue.
Every server object on a given server list has a unique
server number
. This server number will not change while the server is attached to the list. A server object can find out its server number by calling
IACPGetServerNumber()
. This routine takes two arguments: the
IACPConnection
and the optr to the server object. It returns the object's server number.
MSG_META_IACP_PROCESS_MESSAGE, IACPProcessMessage(), MSG_META_IACP_NEW_CONNECTION, MSG_META_IACP_LOST_CONNECTION
Every server and client object must be able to handle certain messages.
GenApplicationClass
comes with handlers for these messages, so you need not write them yourself. However, if you will be using some other kind of object as the server, you must handle the messages yourself. You may also choose to have your application object subclass any of these messages; in that case, you should generally have your handler use
@callsuper
.
When a server or client sends an IACP message, the kernel passes the encapsulated message to each object on the other side of the link. It does this by sending the message MSG_META_IACP_PROCESS_MESSAGE to each object. This message comes with three arguments:
msgToSend
EventHandle
of the encapsulated message.
topt
TravelOption
for that message.
completionMsg
EventHandle
of any message to be sent after
msgToSend
has been dispatched. (This field may be set to zero, indicating that there is no completion message.)
The recipient of MSG_META_IACP_PROCESS_MESSAGE should call
IACPProcessMessage()
. This routine is passed four arguments: the optr of the object calling the routine, and the three arguments passed with MSG_META_IACP_PROCESS_MESSAGE.
IACPProcessMessage()
dispatches both encapsulated messages properly.
Remember, if the client or server is subclassed from
GenApplicationClass
, all of this is done for you. You need only write a handler for the message if the client or server object is not from a subclass of
GenApplicationClass
.
Whenever a client registers on an IACP list, the kernel sends MSG_META_IACP_NEW_CONNECTION to all servers on that list. This message comes with three arguments:
appLaunchBlock
AppLaunchBlock
which the server passed to
IACPConnect()
.
justLaunched
IACPConnectionWhen an object (either client or server) removes itself from an IACP connection, the kernel sends MSG_META_IACP_LOST_CONNECTION to all objects on the other side of the link. This message has two parameters:
connection
serverNum
Whenever a new client is attached to a server list, MSG_META_IACP_NEW_CONNECTION is sent to every server object. This message comes with three arguments:
appLaunchBlock
GenApplicationClass
handler examines the launch block to see if the application should open a document.
justLaunched
true
(i.e. non-zero).
connection
When a client is removed from a server list, every server object is sent
MSG_META_IACP_LOST_CONNECTION
. Similarly, when a server is removed, every client object is sent
MSG_META_IACP_LOST_CONNECTION
. The message comes with two arguments:
IACPConnection
serverNum
serverNum
. If a client left the list (and the message is being sent to servers), this argument will be set to zero.