Figure 6-0

Display 6-0

SwatDisplay 6-0

Table 6-0
Advanced Topic

Not many 
applications will need 
to use 
inter-application 
communication.
Figure 6-1 IACP Clients and Servers

A client can encapsulate a message, then use an IACP routine to instruct the 
kernel to send the message to the servers for a server list. There may be several 
servers on any given server list.
SOURCES: sound.gp

6Applications and Geodes

Revision:   

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:

u	Geodes
The Geodes section describes how libraries, drivers, and applications are 
launched, used, and shut down. It also discusses the general sections of 
a geode and the geode's executable file.

u	Creating Icons
The Creating Icons section describes the structure of the Token 
Database, how icons are stored, managed, and created.

u	Saving User Options
The Saving Options section describes how to work with the GEOS.INI file.

u	General System Utilities
The General System Utilities section describes several different 
mechanisms provided by GEOS that you may find useful in your 
application or other geode.

u	The Error-Checking System Software
The Error-Checking System Software describes the differences between 
the debugging and standard versions of the system software. There are 
two versions of nearly every part of the system software including the 
kernel, the UI, and most libraries and drivers.

u	IACP: Inter-Application Communication Protocol
The IACP section describes how applications can communicate with each 
other and pass data back and forth. This protocol works for applications 
that are not necessarily loaded and running; thus, an application can 
change another application's data dynamically-the recipient 
application is automatically started if it is not already running. (This is 
an advanced topic; most programmers will not need to read this section.)

	6.1	Geodes

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:

u	process
Most of your geodes will have this aspect. A geode that is a process has 
an initial event-driven thread started for it by the kernel. All applications 
and some libraries will have this aspect.

u	library
This aspect indicates that the geode exports entry points for other geodes 
to use. Typically, these entry points describe where either object classes 
or code for routines is within the geode. A library has a special routine 
(the library's entry point) that is called by the system when the library or 
one of its clients is loaded or in the process of being unloaded.

u	driver
This aspect indicates that the geode hides the details of some device or 
similarly changeable thing from the system. A driver has a special 
structure that defines its type, and it has a single entry point called a 
strategy routine. All calls to the driver pass through this strategy routine.

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.

	6.1.1	Geode Components and Structures

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.

u	Core Block Handle
The core block contains its own memory handle, filled in when the geode 
is loaded into memory.

u	Geode Attributes
Each geode has a record of type GeodeAttrs. The geode attributes are 
described below in section 6.1.1.1 on page 245.

u	Release and Protocol Levels
Each geode can have release and protocol levels associated with it to 
ensure compatibility between different versions. Release and protocol 
levels are discussed in section 6.1.8.2 on page 262.

u	Geode Name
Each geode has a geode name and extension specified in the geode 
parameters (.gp) file.

u	Geode Token
Every geode has a token associated with it in the token database. The 
token describes the geode's icon and is a structure of type GeodeToken. 
Tokens and icons are discussed in section 6.2 on page 265.

u	Geode Reference Count
The geode's reference count is stored in the core block. See above for a 
discussion of the reference count and how it's managed.

u	Geode File Handle
The file handle of the geode identifies the open GEO file so the kernel can 
locate, load, and access its various resources.

u	Geode Process Handle
Each geode that has a parent process will have the handle of the process 
in its core block. This is the handle of the parent process' core block. (The 
parent process is the owner of the thread that loaded the geode. It is 
notified when the geode exits, if the exiting geode is a process.)

u	Handle of the Next Geode
Because geode core blocks are stored in a linked list, each core block must 
contain a reference to the next geode in the list.

u	Handle of Private Data Space
Each geode that is not the kernel can have a "private data space." This 
private data space is discussed in section 6.1.9 on page 264.

u	Resource, Library, and Driver Information
Each geode that imports or exports library or driver information must 
keep the import and export specifics available. Additionally, each geode 
must keep track of the resources it owns. All this information is stored in 
tables referenced from within the core block.

	6.1.1.1	Geode Attributes

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.

GA_PROCESS
This geode has a process aspect and therefore an initial 
event-driven thread.

GA_LIBRARY	This geode has a library aspect and therefore exports routines 
(and possibly object classes).

GA_DRIVER	This geode has a driver aspect and therefore has a driver table, 
in which the strategy routine is specified.

GA_KEEP_FILE_OPEN
This geode must have its .GEO file kept open because its 
resources may be discardable or are initially discarded.

GA_SYSTEM	This geode is a privileged geode and is almost certainly a 
system-used driver. These geodes have special exit 
requirements.

GA_MULTI_LAUNCHABLE
This geode may be loaded more than once and therefore may 
have more than one instance of its core block in memory.

GA_APPLICATION
This geode is a user-launchable application.

GA_DRIVER_INITIALIZED
This flag is set if the geode has had its driver aspect initialized 
(if the driver's strategy routine has been initialized). This flag 
will be set dynamically by the kernel.

GA_LIBRARY_INITIALIZED
This flag is set if the geode has had its library aspect initialized 
(if the library's entry routine has been called). This flag will be 
set dynamically by the kernel.

GA_GEODE_INITIALIZED
This flag is set if all aspects of the geode have been initialized.

GA_USES_COPROC
This geode uses a math coprocessor if one is available.

GA_REQUIRES_COPROC
This geode requires the presence of a math coprocessor or a 
coprocessor emulator.

GA_HAS_GENERAL_CONSUMER_MODE
This geode may be run in the General Consumer (appliance) 
Mode.

GA_ENTRY_POINTS_IN_C
This geode has its library entry routine in C rather than 
assembly language.

	6.1.1.2	Geode Token

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 section 6.2 on page 265.

	6.1.2	Launching an Application

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:

u	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.

	GeodeLoad() will have to create two threads for the application: one for 
the process and one for the UI of the geode.

u	UserLoadApplication()
Used by most application launchers, this routine loads a geode from the 
standard GEOS application directory. (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:

u	Application mode launches the geode, loads all its active resources, 
builds its UI gadgetry, and sets it usable. The geode must be an 
application-that is, it must have its GA_APPLICATION attribute set.

u	Engine mode launches the geode but leaves its UI objects unusable (it 
never brings them on-screen). Engine mode is useful if you need to 
launch an application to grab information from it. GeoManager uses 
engine mode to launch an application, extract its icon, and put the icon 
in the token database. For efficiency, the application is never set usable, 
so its UI is never built.

u	Restore mode launches the geode from a saved state file, loading in 
resources and merging them with the default resources. This mode is 
invoked automatically by the UI if it is restoring the system or the geode 
from saved state. This mode is handled automatically by 
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.

	6.1.3	Shutting Down an Application

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" 
on page 250 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 section 5.4.6.5 of 
chapter 5).

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 "General Change Notification," Chapter 9). 
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().

	6.1.4	Saving and Restoring State

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.) 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 
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.

	6.1.5	Using Other Geodes

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.

	6.1.5.1	Using Libraries

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 section 
6.1.8.2 on page 262) 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().

	6.1.5.2	Using Drivers

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:

DA_FILE_SYSTEM
This flag indicates that the driver is used for file access.

DA_CHARACTER
This flag indicates that the driver is used for a 
character-oriented device.

DA_HAS_EXTENDED_INFO
This flag indicates that the driver has a 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:

DRIVER_TYPE_VIDEO
This is used for video drivers.

DRIVER_TYPE_INPUT
This is used for input (mouse, keyboard, pen, etc.) drivers.

DRIVER_TYPE_MASS_STORAGE
This is used for storage device drivers.

DRIVER_TYPE_STREAM
This is used for stream and port (parallel, serial) drivers.

DRIVER_TYPE_FONT
This is used for font rasterizing drivers.

DRIVER_TYPE_OUTPUT
This is used for output drivers other than video and printer 
drivers.

DRIVER_TYPE_LOCALIZATION
This is used for drivers that facilitate internationalization.

DRIVER_TYPE_FILE_SYSTEM
This is used for file system drivers.

DRIVER_TYPE_PRINTER
This is used for printer drivers.

DRIVER_TYPE_SWAP
This is used for the system's memory-swapping drivers.

DRIVER_TYPE_POWER_MANAGEMENT
This is used for devices that have power management systems.

DRIVER_TYPE_TASK_SWITCH
This is used for devices or systems that have task switchers.

DRIVER_TYPE_NETWORK
This is used for special networks that require driver 
functionality.

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.

GDDT_FILE_SYSTEM
GEOS may use several file system drivers during execution. A 
file system driver allows GEOS to work on a given DOS (or 
substitute file access system). The primary driver is considered 
the default driver.

GDDT_KEYBOARD
The system may only use one keyboard driver during 
execution; typically, keyboards can differ from country to 
country. Not all systems will have a keyboard.

GDDT_MOUSE
The mouse should be usable when the system comes up on the 
screen; the user does not have to manually load a mouse driver 
with each execution of GEOS. Not all systems will have a 
mouse.

GDDT_VIDEO
GEOS must know what type of video driver to use prior to 
attempting to display itself.

GDDT_MEMORY_VIDEO
The Vidmem driver is a video driver that draws to memory (i.e. 
to bitmaps). It is used primarily for printing but also for editing 
bitmaps.

GDDT_POWER_MANAGEMENT
A few machines use hardware power management systems 
(most notably some palmtops and some notebook and laptop 
machines). In order for GEOS to handle the power management 
hardware properly, it must load the driver on startup. (Most 
machines will not use this type of driver.)

GDDT_TASK	Some systems will use a task switcher; in order for GEOS to be 
a task in one of these, it must have a default task driver.

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.

	6.1.6	Writing Your Own Libraries

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:

u	Declare the geode to be a library.
In the geode parameters (.gp) file, you must declare the type of the geode 
to be library.

u	Define an entry point routine.
Create a routine that initializes the library; this routine is called the 
"entry point routine" and is called each time the library is loaded.

u	Export the entry point routine.
Add another line in the geode parameters (.gp) file to export the library's 
entry point routine.

u	Set the geode's permanent name properly.
Most libraries have the extension of their permanent name be "lib." This 
helps identify it as a library when using Swat. The permanent name is 
defined in the geode parameters (.gp) file.

Code Display 6-1 shows an example of the three changes you must make to 
the geode's parameters file to make it a library.

Code Display 6-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



	6.1.7	Working with Geodes

The system provides a number of utility routines for getting information and 
setting attributes of geodes. These are loosely organized throughout the 
following sections.

	6.1.7.1	Accessing the Application Object

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.

	6.1.7.2	General Geode Information

GeodeFind(), GeodeFindResource(), 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.

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:

GGIT_ATTRIBUTES
This indicates the geode's attributes should be returned.

GGIT_TYPE	This indicates the geode's type should be returned (type 
GeodeType).

GGIT_GEODE_RELEASE
This indicates the geode's release numbers should be returned.

GGIT_GEODE_PROTOCOL
This indicates the geode's protocol numbers should be 
returned.

GGIT_TOKEN_ID
This indicates the geode's token information should be 
returned.

GGIT_PERM_NAME_AND_EXT
This indicates the geode's permanent name and extender 
should be returned.

GGIT_PERM_NAME_ONLY
This indicates the eight characters of the geode's permanent 
name only should be returned, without the extender 
characters.

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.

	6.1.7.3	Managing Geode Event Queues

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. 

	6.1.8	Geode Protocols and Release Levels

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 "Grev" on page 444 of "Using 
Tools," Chapter 10 of the Tools Reference Manual.

	6.1.8.1	Release Numbers

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.

	6.1.8.2	Protocol Numbers

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 "File 
System," Chapter 17.)

A few examples of when minor and major protocols should be incremented 
follow. Keep in mind that this list is by no means exhaustive.

u	If you add a data structure to a document format and older versions of 
the application will still be able to open the document, increment the 
document's minor protocol only.

u	If you change the instance data structure of an object that gets saved to 
the document, increment the major protocol, as the new methods will be 
using the wrong offsets to access data in the old object.

u	If you add a new class to your application and don't disturb any of the 
other classes' entry points (i.e. add the class at the end), increment the 
minor protocol only.

u	If you add a new class, disturbing entry points, increment the major 
protocol.

u	If you add or delete a resource, increment the major protocol.

u	If you add a chunk or object to a resource that gets saved to state files, 
increment the major protocol.

u	If you change an object's flags (e.g. mark it ignore-dirty), increment the 
major protocol. Otherwise, the flags will be restored from the state file 
and will override the changes you made.

	6.1.9	Temporary Geode Memory

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().

	6.2	Creating Icons

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.

	6.2.1	The Token Database

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.

	6.2.2	Managing the Token Database File

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:

u	A set of TokenRangeFlags, which specifies which tokens should be 
returned. The following flags are available:

TRF_ONLY_GSTRING
Return only those tokens which are defined with a GString.

TRF_ONLY_PASSED_MANUFID
Return only those tokens which match the passed 
manufacturer's ID.

u	A number of bytes to reserve for a header.

u	A manufacturer ID. This field is ignored if TRF_ONLY_PASSED_MANUFID 
was not passed.

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.

	6.3	Saving User Options

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.

	6.3.1	Saving Generic Object Options

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:

u	Create an Options menu
Typically, you will create a menu for user options. This menu should be 
declared of type GIGT_OPTIONS_MENU as shown in Code Display 6-2.

u	Use a special GCN list
All objects that want to save options must be put on a special GCN list in 
the application's GenApplication object. The list type should be either 
GAGCNLT_STARTUP_LOAD_OPTIONS (if the options should be loaded at 
the time of application startup) or GAGCNLT_SELF_LOAD_OPTIONS (if 
the options should be loaded when the object deems it necessary, typically 
when the object is first displayed).

Code Display 6-2 shows an example of how objects should be declared for 
saving their options.

Code Display 6-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;
}



	6.3.2	The GEOS.INI File

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.

	6.3.2.1	Configuration of the INI File

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 Code Display 6-3.) 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 9 of the 
Tools Reference Manual.

Code Display 6-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 "My App'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}



	6.3.2.2	Managing the INI File

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.

	6.3.2.3	Writing Data to the INI File

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.

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.

	6.3.2.4	Getting Data from the INI File

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.

	6.3.2.5	Deleting Items from the INI File

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.

	6.4	General System Utilities

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.

	6.4.1	Changing the System Clock

TimerGetDateAndTime(), TimerSetDateAndTime()

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. 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.

	6.4.2	Using Timers

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:

u	One-shot
A one-shot timer counts a certain number of "ticks." (There are 60 ticks 
in a second.) It will call a routine or send a message after the specified 
number of ticks have been counted.

u	Continual
A continual timer counts a certain number of ticks and then resets and 
counts again. Each time it reaches the specified number of ticks, it calls 
a routine or sends a message.

u	Millisecond
A millisecond timer is a one-shot timer that counts in milliseconds rather 
than ticks and calls a routine when time is up (it does not send a 
message).

u	Sleep
A sleep timer causes the thread that creates it to sleep for a certain 
number of ticks. The sleep timer is unlike the others in that it does not 
send a message or call a routine when time is up; it only awakens the 
sleeping thread.

u	Real-time
A real-time timer is passed the number of days since 1980, the hour, and 
the minute of the event. On hardware platforms that support such a 
thing, GEOS will wake up when the real-time timer goes off. (All other 
timers are ignored under these conditions.)

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.

	6.4.3	System Statistics and Utilities

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.

	6.4.4	Shutting the System Down

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.

	6.5	The Error-Checking Version

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. 

	6.5.1	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.

EC	This macro adds the specified line of code to the EC version 
only. When the non-EC version is compiled from the same code, 
the line will be left out.

EC_ERROR	This macro halts the execution of the EC version of a program 
by calling FatalError() with a specified error code. The call to 
FatalError() will not be included in the non-EC version.

EC_ERROR_IF
This macro works like EC_ERROR, above, but it also allows you 
to set a condition for whether FatalError() will be called. If 
the condition is met, FatalError() will be called.

NEC	This macro adds the specified line of code to the non-EC version 
but leaves it out of the EC version. (It is the converse of the EC 
macro.)

EC_BOUNDS	This macro checks the validity of a specified address (pointer) 
by calling the 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 Code Display 6-4.

Code Display 6-4 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)



	6.5.2	Special EC Routines

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.

	6.6	Inter-Application 
Communication

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.

	6.6.1	IACP Overview

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.

	6.6.2	GenApplicationClass Behavior

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:

u	The Application Object automatically registers itself as a server for the 
appropriate list when it is launched in application mode. If it is launched 
in engine mode, it registers itself when it receives 
MSG_META_APP_STARTUP. If the last client-connection to the 
application is closed, and the Application is not currently running in 
application mode, the Application object will shut down automatically.

u	When the Application object registers itself as a server for its own list, it 
sends itself MSG_GEN_APPLICATION_IACP_REGISTER. An application 
may subclass this if it wants to take other action at this time (e.g. 
registering itself for other lists). Similarly, when an Application object 
unregisters itself from its own server list, it sends itself 
MSG_GEN_APPLICATION_IACP_UNREGISTER.

u	The Application object automatically handles the MSG_META_IACP- 
messages appropriately. In particular, when the kernel passes an 
encapsulated message to an Application object with 
MSG_META_IACP_PROCESS_MESSAGE, the Application object 
automatically dispatches the message to the appropriate location.

u	An Application object will refuse to quit as long as any client has an open 
IACP connection to it. (It can, however, be forcibly detached; this happens 
when the system is shut down, as noted below.). In such a case, the 
Application object will automatically call IACPShutdownAll() to shut 
down all IACP links it has open, whether it is a client or a server on those 
links.

u	When a link is closed, IACP automatically sends 
MSG_META_IACP_LOST_CONNECTION to all objects on the other side of 
the link. When an Application object receives this message, it waits until 
all remaining messages from the link have been handled; it then calls 
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.

u	If the Application object is forcibly detached, it sends itself 
MSG_GEN_APPLICATIONIACP_SHUTDOWN_ALL_CONNECTIONS. The 
default handler for this message will call 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.

	6.6.3	Messages Across an IACP Link

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:

u	The token for the IACP link.

u	The EventHandle of an encapsulated message.

u	The TravelOption for that message.

u	An encapsulated completion message; this will be dispatched once each 
time the first message has been successfully handled.

u	A member of the 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.

	6.6.4	Being a Client

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.

	6.6.4.1	Registering as a Client

IACPConnect(), IACPCreateDefaultLaucnhBlock()

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:

u	The GeodeToken of the list for which you want to register.

u	A set of IACPConnectFlags. The following flags are available:

IACPF_OBEY_LAUNCH_MODEL
This indicates that if GEOS should follow the launch model, 
which will specify whether the user should be presented a 
dialog box, asking the user whether an existing application 
should be used as the server, or a new application launched. If 
you set this flag, you must pass an AppLaunchBlock(), with 
the ALB_appMode field set to 
MSG_GEN_PROCESS_OPEN_APPLICATION; you must also set 
IACPF_SERVER_MODE to IACPSM_USER_INTERACTIBLE.

IACPF_CLIENT_OD_SPECIFIED
This flag indicates that you will specify what object will be the 
new client. If you do not set this flag, the sending geode's 
Application object will be registered as the client.

IACPF_FIRST_ONLY
If you pass this flag, the client will be connected to only the first 
server on the server list. Any messages sent by the client to the 
server list will be passed only to that one server.

IACPSM_SERVER_MODE
This field is three bits wide; it holds a member of the 
IACPServerMode enumerated type. This type specifies how 
the client expects the server to behave. Currently, only two 
types are supported:

IACPSM_NOT_USER_INTERACTIBLE
This is equal to zero. It indicates that the server object need not 
interact with the user.

IACPSM_USER_INTERACTIBLE
This is equal to two. It indicates that the server should be able 
to interact with the user like any normal application.

u	The 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.

u	The optr of the client object. This is ignored if 
IACPF_CLIENT_OD_SPECIFIED was not passed.

u	A pointer to a word. 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:

u	The application's initial working directory (i.e. the launch block's 
ALB_diskHandle field) will be SP_DOCUMENT.

u	No application directory will be specified in the launch block (i.e. 
ALB_appRef.AIR_diskHandle will be zero); IACPConnect() will attempt 
to find the application on its own.

u	No initial data file will be specified (i.e. ALB_dataFile will be blank).

u	The application will determine its own generic parent (i.e. 
ALB_genParent will be null).

u	No extra data word will be passed to the application (i.e. ALB_extraData 
will be zero).

u	There will be no output descriptor (i.e. ALB_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.

	6.6.4.2	Unregistering as a Client

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; section 6.6.5 of chapter 6 describes how it is used 
by servers.

The routine is passed two arguments:

u	The IACPConnection token for the link which is being shut down.

u	An optr. This must be null if the routine is being called by a client.

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.

	6.6.5	Being a Server

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.

	6.6.5.1	Registering and Unregistering a Server

IACPRegisterServer(), IACPUnregisterServer()

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:

u	The GeodeToken of the list for which you are registering as a server.

u	The optr of the object which is registering as a server. This object must 
be able to handle the MSG_META_IACP- messages appropriately (this is 
built into GenApplicationClass).

u	A member of the IACPServerMode enumerated type. This type 
specifies how the client expects the server to behave. Currently, only two 
types are supported:

    IACPSM_NOT_USER_INTERACTIBLE
This is equal to zero. It indicates that the server object will not 
interact directly with users.

    IACPSM_USER_INTERACTIBLE
This is equal to two. It indicates that the server will be able to 
interact with the user like any normal application.

u	A set of IACPServerFlags. Currently, only one flag is supported: 
IACP_MULTIPLE_INSTANCES, indicating that multiple copies of the 
application might be running at once. (Every multi-launchable 
application should set this flag.)

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 
GeodeToken of the server list, and the optr to the server object. It returns 
the object's server number.

	6.6.5.2	Non-Application Servers and Clients

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 	The EventHandle of the encapsulated message.

topt	The TravelOption for that message.

completionMessage
The 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
This is the handle of the AppLaunchBlock which the server 
passed to IACPConnect().

justLaunched
This is a Boolean value. If the server's application has just been 
launched specifically to be a server, this will be true (i.e. 
non-zero).

IACPConnection
This is the token for the IACP connection.

When 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	The token for the IACP list.

serverNum	If a server removed itself from the list (and this message is 
being sent to clients), this field will have the number of the 
server that removed itself. If a client removed itself from the 
list, this field will be zero.

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
This is the handle of the application's launch block. The default 
GenApplicationClass handler examines the launch block to 
see if the application should open a document.

justLaunched
If the application was just launched specifically to be a server, 
this field will be true (i.e. non-zero).

connection	This is the token for the IACP 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
This is the token for the IACP connection.

serverNum	If a server left the list (and the message is being sent to clients), 
this argument will hold its serverNum. If a client left the list 
(and the message is being sent to servers), this argument will 
be set to zero.
