Copyright (c) Hyperion Entertainment and contributors.

Application Library

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
WIP.png This page is currently being updated to AmigaOS 4.x. Some of the information contained here may not yet be applicable in part or totally.

Introduction

The Application Library is a multipurpose auxiliary library that provides various functions related to the development and use of applications. The very concept of application is a relatively recent addition to AmigaOS. Before, the system only distinguished between different types of program on a very low level, seeing them as either tasks or processes. This distinction might have been useful in the past when tasks (which require fewer resources in return for not being able to access DOS functions) could improve system performance. But it can hardly make a difference on today’s hardware so the trade-offs are no longer worth it. Nowadays it makes more sense to discriminate between programs that operate without the user even noticing (e.g. drivers, handlers, filesystems and other background services), and genuine full-blown applications with GUI and all.

AmigaOS alone cannot make such a distinction: it uses the Application Library as a mediator through which applications introduce themselves to the system. This process is called application registration, during which the application receives a unique identifier and is added to a public list among other applications. Once registered, the application can make use of the library’s many features:

  • It can send/receive messages to/from other registered applications. The library supports a set of common control messages (commands) such as those telling an application to quit, iconify or bring its window to front. But it also allows custom messages designed for an application’s particular needs; in this respect the Application Library provides an alternative to ARexx control.
  • It can use PrefsObjects, an XML-based, object-oriented system for handling program preferences. Before AmigaOS 4.x no real standard existed for storing preferences: some developers used icon tooltypes, some used proprietary formats, text or binary. The Application Library provides a format that is human-readable and easily editable in a simple text editor; that is comprehensive enough to cover even very complex settings structures; and that is fully controllable via the library, without the need to laboriously implement data parsing and verification.
  • It can notify the user about, for example, completed tasks via automatic pop-up messages. These represent a practical, less obtrusive alternative to traditional requesters.
  • It can easily create and manage lists of recently-used documents.
  • It can register as a unique application, preventing other instances of itself from running.
  • It can show its icon or display the current program state in taskbar-like applications, such as AmiDock.
  • It can control the behaviour of screen-blankers. Applications that don’t want to be disturbed may prevent the blanker from kicking in, or tell other applications to “keep quiet”.

Library opening chores

Just like other AmigaOS libraries, the Application Library must be opened before it is used. Further, at least one of its interfaces must be obtained, depending on the functionality you require. The Application Library has two interfaces, called “application” and “prefsobjects”. You always need to obtain the “application” interface because it provides access to most library functions including application registration. You’ll only need to open the “prefsobjects” interface if you intend to make use of the PrefsObjects preferences system.

struct Library *ApplicationBase = NULL;
struct ApplicationIFace *IApplication = NULL;
struct PrefsObjectsIFace *IPrefsObjects = NULL;
 
if ( (ApplicationBase = IExec->OpenLibrary("application.library", 52)) )
{
   IApplication = (APTR)IExec->GetInterface(ApplicationBase, "application", 1, NULL);
   IPrefsObjects = (APTR)IExec->GetInterface(ApplicationBase, "prefsobjects", 1, NULL);
}
 
if ( !ApplicationBase || !IApplication || !IPrefsObjects )
{
   /* handle library opening error */
}

Note that there is no interface called “main” like older, single-interface libraries have.

When your application has run its course, don’t forget to clean up and close both the library and its interface(s):

IExec->DropInterface((struct Interface *)IPrefsObjects);
IExec->DropInterface((struct Interface *)IApplication);
IExec->CloseLibrary(ApplicationBase);

Registering the application

Application registration is a simple process during which a program informs AmigaOS that it should be treated as an application, and provides some basic information about itself: the program name, an associated URL address, or a short description. Also, certain application-related parameters can be set at registration time (although some of these may be provided or changed later). Registration typically takes place at program startup; unregistration is normally done at the end of program runtime.

Application identifiers

A successfully registered application receives a numeric identifier, which in this documentation will be referred to as appID. Other registered applications can obtain the appID from the library and use it to communicate with the respective application. AppIDs are unique numbers: the Application Library generates them incrementally on a per-registration basis. They are never used again during the same AmigaOS session, which prevents programs from incidentally addressing the wrong application after the original appID holder unregisters.

Apart from the numeric appID an application can be referred to by its name (that is, a string-type identifier). As programs can get registered in an arbitrary order, it is the name identifier that other applications must use to retrieve the correct appID. To construct a unique name, the Application Library uses a special naming scheme that combines the application name with a related URL identifier (for example, the domain name of the application’s homepage). The latter is optional but it’s recommended to provide it at registration time, to avoid possible application name conflicts. The same naming scheme is used by the library’s PrefsObjects system to create the name of the preferences file.

Registration

Now let’s say we have a program called Best App made by the fictitious software company SuperCoders, Inc. The piece of C code to handle its registration might look like this:

uint32 appID;
 
appID = IApplication->RegisterApplication("BestApp",
           REGAPP_URLIdentifier, "supercoders.com",
           REGAPP_Description, "The best application there is, really",
           TAG_DONE);
 
if (!appID)
{
   /* report registration error and quit */
}
 
/*
    do whatever your program does
 */
 
IApplication->UnregisterApplication(appID, NULL);

Note that we’ve had to alter the application name to “BestApp”; it is because the Application Library doesn’t allow spaces in application names. According to the naming scheme, the application will now become registered under the name “BestApp.supercoders.com”. (Should a previous instance of BestApp be already running, the library will automatically append an instance counter to the application name: the second instance will therefore be called “BestApp_1.supercoders.com”, the third will register as “BestApp_2.supercoders.com”, and so on.)

The REGAPP_Description tag in the registration function tells the system what Best App is all about; it’s just an example of an optional parameter that can be provided. There are parameters that can only be applied at registration time, others may be set or changed later using the function SetApplicationAttrs(). Please refer to the Application Library autodoc for a complete list and description of registration/configuration parameters and their corresponding tags.

During registration you may also indicate certain features your application will support. In particular, you may inform the system that your application opens a dedicated Preferences window, that it can be iconified, that it can create documents, or that it supports printing documents. This information is not required but it can be used by other applications, which may decide to query about the availability of a certain feature before they send a control message. Set the following boolean tags to TRUE in the RegisterApplication() call if your application supports the respective feature (the default value is FALSE):

  • REGAPP_HasPrefsWindow
  • REGAPP_HasIconifyFeature
  • REGAPP_CanCreateNewDocs
  • REGAPP_CanPrintDocs

Unique applications

A program can register as a unique application, thus only allowing one instance of itself to run. While the multitasking nature and tradition of AmigaOS would suggest not imposing such limits, there can be good reasons to do so. For example, the developer of a song player might decide to make his/her program a unique application because the user would most likely gain nothing from playing several songs at the same time. Multiple program instances would only compete for screen space and system resources, possibly jeopardizing OS performance on lower-specification computers.

The following function call registers our Best App as a unique application:

appID = IApplication->RegisterApplication("BestApp",
           REGAPP_URLIdentifier, "supercoders.com",
           REGAPP_Description, "The best application there is, really",
           REGAPP_UniqueApplication, TRUE,
           TAG_DONE);

If the user now tries to launch a second instance of the program, it will fail on RegisterApplication() and the library will send a special message to the first instance informing it about the attempt. It is the developer’s responsibility to react to this message in a sensible way. Do not show error messages here: the user doesn’t need to know (or care) that an application is unique, so an error message would scold them for doing nothing wrong. The recommended behaviour is to bring the first instance to view and activate its window.

Application attributes

An application is described by a set of attributes. There are attributes that:

  • determine the application's behaviour
  • describe the application's feature set
  • describe the current application state

Most of the attributes are specified at registration time. Some of them cannot be altered once they are set, while others are freely changeable after registration.

Finding applications

The Application Library maintains a list of all registered applications. Certain special-purpose programs – let’s call them application managers – will also keep track of applications registering and unregistering. Nevertheless, most programs won’t ever have to do anything like this. If the need arises to talk to another application, they can simply find this particular application in the system and then start sending messages to it.

“Finding an application” basically means obtaining its appID from the library. To do this you need to know at least one of the following:

  • the application name, ie. the one under which it was registered via RegisterApplication();
  • the application name identifier, ie. the unique combination of the application’s name, instance number (should there be more instances running) and URL identifier – see Application identifiers above;
  • the pathname pointing to the program file on disk, e.g. “Work:Utils/BestApp”.

Based on this information, the respective piece of code that will find our BestApp in the system might look like this:

uint32 appID;
 
/* if you only know the application name */
appID = IApplication->FindApplication(FINDAPP_Name, "BestApp", TAG_DONE);
 
/* if you know the application name identifier */
appID = IApplication->FindApplication(FINDAPP_AppIdentifier, "BestApp.supercoders.com", TAG_DONE);
 
/* if you specifically want to talk to the second running instance */
appID = IApplication->FindApplication(FINDAPP_AppIdentifier, "BestApp_1.supercoders.com", TAG_DONE);
 
/* if you know the pathname to the program file */
appID = IApplication->FindApplication(FINDAPP_FileName, "Work:Utils/BestApp", TAG_DONE);

Once you have obtained the appID you can start communicating with the respective application.

Messaging

Messages are used extensively in AmigaOS: the inner workings of Exec or Intuition actually involve a good deal of message passing. But it’s not just the operating system that needs to communicate. Modern software applications often want to talk to other running applications. Regardless of whether this communication will, in real use, entail a simple command-driven action or an intricate exchange of data, AmigaOS provides the necessary means: inter-program communication is supported on the low level (through Exec Library’s messages and ports) as well as on the high level (using the ARexx-language scripting features). The Application Library has introduced yet another means of communication, which can be seen as lying somewhere in between the two levels.

Provided that you know its appID, you can send messages to any running application that is ready to accept them. Basic application control can be achieved via a set of predefined messages that correspond to common program commands and functions (such as New, Open, Print, Iconify or Quit). But the library also supports custom messages, thus allowing for more sophisticated control or information exchange. Furthermore, apart from this “invisible”, abstract communication taking place between application ports, you can use the library to provide real and visible information in the form of pop-up notification messages. At the same time, the extent and complexity of messaging is fully up to the programmer: your application will only send/receive messages that you will implement. Registration alone does not magically turn on any features.

Note
Before we get any further with Application Library messages, it must be made clear that they should not be confused with the similarly-named AppMessages. The latter are a completely different breed, governed by the Workbench Library. They represent a specific way of communication between the Workbench desktop environment and running applications. It’s strictly one-way communication because Workbench can send AppMessages to applications but applications cannot send AppMessages to Workbench. (If you want to learn more about the use of AppMessages, consult the Workbench Library section of the AmigaOS documentation wiki).

Data structures

Rather than introduce yet another system for communication, the Application Library builds upon the existing Exec messaging framework. Programmers will, therefore, find working with Application Library messages rather familiar. For instance, the library uses data structures that are in fact extensions of the standard Exec message structure. Also, the usual procedure of GetMsg() and ReplyMsg() takes place when processing incoming Application Library messages (see section Message handling below) – although the library implements its own method for sending messages.

The following three structures are defined for carrying Application Library message data:

struct ApplicationMsg
{
	struct Message msg;
	uint32 senderAppID;  // the appID of the sender application
	uint32 type;         // identifies the type of message
};
 
struct ApplicationOpenPrintDocMsg
{
	struct ApplicationMsg almsg;
	STRPTR fileName;
};
 
struct ApplicationCustomMsg
{
	struct ApplicationMsg almsg;
	STRPTR customMsg;
};

As you can see, structure ApplicationMsg contains a standard struct Message, the other fields are used for Application Library-specific data. The other two structures are mere extensions of the basic one: ApplicationOpenPrintDocMsg is used by certain control messages that require a pointer to a filename; the ApplicationCustomMsg structure is used for custom messages.

Upon message arrival you identify the message by reading the “type” field of the respective data structure. Similarly, if you want to send a message to an application you specify it in the “type” field.

Pop-up notifications (Ringhio messages)

As from the introduction of the Ringhio server in AmigaOS 4.1 Update 1, registered applications can inform the user via notifications displayed in a small pop-up box. These are sometimes called Ringhio messages because the server provides means through which the messages are communicated visually (in other words, Ringhio handles the actual display of messages sent by the Application Library).

Ringhio notification example

The pop-ups function similarly to requesters in that they show a text message; but unlike requesters, Ringhio messages do not require user interaction or acknowledgement. They just show up briefly and disappear – which makes them great for informing about less significant, matter-of-fact events such as that a certain task has been completed. This is especially helpful if the application is hidden or runs on a different screen, as the user is kept informed about something that is currently beyond his/her visual control.

Of all the types of Application Library messages, pop-up notifications are surely the easiest to program. They don’t require any setup or event handling; all it takes is a single function call:

uint32 result;
 
result = IApplication->Notify(appID,
               APPNOTIFY_Title, "Important Message",
               APPNOTIFY_Text, "Your socks need changing!",
               APPNOTIFY_PubScreenName, "FRONT",
               TAG_DONE);

The first parameter is your application’s appID received from the registration function. The APPNOTIFY_Title tag specifies a short heading for the pop-up box while APPNOTIFY_Text contains the actual message text. Certain limits to text length apply to ensure that the pop-up remains easy to read: 64 characters for the heading (title) and 128 characters for the text. This particular message will display on the frontmost public screen (as specified in the APPNOTIFY_PubScreenName tag), which may as well be the right setting for most applications. You can of course provide any other public screen name – or you can call Notify() without this tag and let the library use the default, which is the Workbench screen.

The result value shows whether the call was successful; 0 means that an error has occurred and Ringhio failed to display the pop-up. Depending on the significance of the message, you may want to react upon the failure and inform the user through other means of communication, such as a requester.

The look and position of the pop-up box is configurable from the Notifications editor located in the Prefs drawer on your system partition. This is Ringhio’s preferences editor: as we have explained above, it is Ringhio that is responsible for the actual display of notification messages. The pop-up box can also show a small custom image (like the one in the picture above) to make the message more easily identifiable as related to a particular application. The maximum size of the image is 32x32 pixels. It can be any format, provided that there is a datatype for it installed in the system. Being application-specific, these images logically cannot be configured system-wide through the Notifications editor; instead, they are specified as part of the Notify() call. Note that a full path to the image file must be provided:

IApplication->Notify(appID,
               APPNOTIFY_Title, "Important Message",
               APPNOTIFY_Text, "Your socks need changing!",
               APPNOTIFY_PubScreenName, "FRONT",
               APPNOTIFY_ImageFile, "PROGDIR:Images/BestApp.jpg",
               APPNOTIFY_BackMsg, "All right, ma!",
               TAG_DONE);

But what is this APPNOTIFY_BackMsg thing? It has been mentioned above that notification messages do not require user interaction: they display some text and go away. Nevertheless, Ringhio can send the application a custom message (called “back message” – hence the name of the tag) if the user has double-clicked on the message box. As the code snippet shows, this custom message takes the form of a text string (within the Application Library messaging context, custom messages are always text strings). It is sent to the same event stream, and is supposed to be processed within the same event loop, as other Application Library messages – see the Message handling section for how to go about it.

Whether receiving a “back message” would be useful for a particular application, and whether it would make sense to react upon it, is decided by the programmer. The reaction (if any – often none is needed) should be sensible and logical. Make sure not to misuse the feature! Note that Ringhio messages are not requesters, and double-clicking on the message box does not really equal pressing a requester button. Therefore, receiving the message could be interpreted as the user’s acknowledgement of what Ringhio has said, but never as a selection of an option. Do not use Ringhio to request input via the back-message feature: the text of the message should be a statement, not a question or an offer of choice. Considering this logic, the double click means “I understand”; it doesn't mean “Yes” or “No”.

If the text string provided in the APPNOTIFY_BackMsg tag is an URL, the feature works rather differently. Instead of sending the message to the event stream, this particular URL is opened in your default web browser. Because the process is done through the Launch Handler, your system should be recent enough to have it (the Launch Handler was introduced in AmigaOS 4.1 Update 1). The following piece of code will open our Best App’s homepage if the user double-clicks on the Ringhio box. The last in the tag list, APPNOTIFY_CloseOnDC, causes the message box to disappear right after the double click.

IApplication->Notify(appID,
               APPNOTIFY_Title, "Important Message",
               APPNOTIFY_Text, "Your socks need changing!",
               APPNOTIFY_PubScreenName, "FRONT",
               APPNOTIFY_ImageFile, "PROGDIR:Images/BestApp.jpg",
               APPNOTIFY_BackMsg, "URL:http://www.supercoders.com",
               APPNOTIFY_CloseOnDC, TRUE,
               TAG_DONE);

Control messages

The Application Library allows registered applications to send messages that control other applications. There is a set of predefined messages (or rather, commands) that can be sent to a running application, telling it – for example – that it should come to front, quit, open a document, create a new one, and so on. As these actions are common program functions, the library offers a practical and easy-to-implement way to control applications externally. The following control messages (commands) are currently provided:

APPLIBMT_Quit
The application shall quit. If data could be lost, it is expected that a confirmation requester is displayed.
APPLIBMT_ForceQuit
Same as before but this time the application shall quit immediately, without asking for saving documents etc.
APPLIBMT_Hide
The application shall hide its interface and iconify on the Workbench screen.
APPLIBMT_Unhide
The application shall come back from the iconified state.
APPLIBMT_ToFront
The application window shall come to front. Programmatically that entails calling the ScreenToFront(), WindowToFront() and ActivateWindow() sequence.
APPLIBMT_OpenPrefs
The application shall open its preferences window.
APPLIBMT_NewBlankDoc
The application shall open a new, blank document.
APPLIBMT_OpenDoc
The application shall try to open a specific document. The name of the document is passed as part of the message data structure (struct ApplicationOpenPrintDocMsg: see Data structures above).
APPLIBMT_PrintDoc
The application shall try to print a specific document. The name of the document is passed as part of the message data structure (struct ApplicationOpenPrintDocMsg: see Data structures above).

An application will only react to messages that are implemented and supported. If you send a message to an application that is registered but does not perform any Application Library event handling, there will be no reaction at all. Further, many applications will only implement a subset of the available control commands: if a program does not have a Print function, an APPLIBMT_PrintDoc message will quite logically be ignored. There is no rule saying which or how many control messages should be implemented but you are asked for a minimum cooperation. In the near future, AmigaOS will have an application manager (third-party managers already exist) that will control running applications – much like the Exchange tool controls commodities. Therefore, registered applications should “play nice” and support at least the most common commands – APPLIBMT_Quit, APPLIBMT_Hide, APPLIBMT_Unhide and APPLIBMT_ToFront. Implement the rest according to your application’s features and needs.

Note
Providing the APPLIBMT_ForceQuit command as part of the Application Library messaging framework was surely meant well but there is an inherent risk: a malevolent application could hamper the use of other applications by sending them this message and making them quit prematurely, possibly causing loss of data. Therefore, implement support for APPLIBMT_ForceQuit in such a way that the user's data is never put in jeopardy!

Custom messages

In contrast to a control message (see above), a custom message has no meaning or purpose predefined by the library. It is a simple text string the actual meaning of which is determined by the application. There is some undeniable beauty in this concept. For instance, the application can define a set of “publicly available” commands that call a corresponding function whenever a particular command (text string) arrives from another application. This kind of external control is very easy to implement and represents a practical alternative to ARexx, while requiring less setup. Also, sender applications need not care about internals (such as knowing the ARexx port name) – all it takes is to find the receiver application and send a message to it.

The pointer to the message text string is contained in a special data structure, struct ApplicationCustomMsg.

Special messages

There are also some special messages that an application can receive from the library or another running application:

APPLIBMT_Unique
If an application is registered as unique and the user attempts to start a second instance of the program, the attempt will fail and the library will send an APPLIBMT_Unique message to the first instance. All unique applications should listen for this message and react to it: not doing so might leave the user puzzled as to why the program hasn’t started. The recommended reaction is to bring the first instance to view and focus. This may entail a couple of steps, like uniconifying (if the program is iconified at the moment), swapping screen (if the program runs on a dedicated screen), bringing the program window to front, and activating it. Basically, the APPLIBMT_Unique message should be treated in the same way as the APPLIBMT_ToFront message.
APPLIBMT_GameModeEntered
This message is sent to all currently running applications if another application enters the “game mode” – that is, a state in which it doesn’t want to be disturbed. Games or presentation programs are examples of applications that may want to run in the “game mode”. The message simply asks other applications to cooperate and “keep quiet”: not play sounds, open windows, requesters, etc. Please use the game-mode feature moderately and only send the APPLIBMT_GameModeEntered message when it is really needed.
APPLIBMT_GameModeLeft
This message informs all running application that the sender has left the “game mode”.

Message handling

You already know that messaging in AmigaOS takes place between message ports. Being a superset of standard Exec messages, Application Library notifications (be they predefined control messages, custom messages or special messages) are, too, sent to a message port. We’ll call this dedicated port – in order to distinguish it from other ports possibly used by the program – the notification port. The port is automatically created by the library at registration time and freed as part of the UnregisterApplication() call. Messages arriving at the port are meant to be processed within the program’s main event loop, together with other events (such as Intuition’s IDCMP messages). To process incoming messages you first need to obtain the notification port pointer from the library and then set the port’s signal bit. The signal bit is used to identify messages as Application Library notifications.

A simplified event loop code could look like the one below. Note that this example loop only waits for and processes Application Library messages. In real use you'll also want to handle other message types, such as input from the user interface (IDCMP events) or ARexx commands.

void event_loop(uint32 appID)
{
 struct MsgPort              *notificationPort = NULL;
 struct ApplicationMsg       *appLibMsg = NULL;
 struct ApplicationCustomMsg *customMsg = NULL;
 uint32 appLibSignal = 0, sigGot = 0;
 BOOL   done = FALSE;
 
 /* Obtain pointer to Application Library's notification port and set the signal bit. */
 IApplication->GetApplicationAttrs(appID, APPATTR_Port, &notificationPort, TAG_DONE);
 appLibSignal = (1L << notificationPort->mp_SigBit);
 
 /* Go into the event loop. */
 while (!done)
  {
   /* Wait for a signal to arrive. */
   sigGot = IExec->Wait(appLibSignal);
 
   /* Process all Application Library messages. */
   if ( sigGot & appLibSignal )
    {
     /* Obtain pointer to the message. */
     while ( (appLibMsg = (struct ApplicationMsg *) IExec->GetMsg(notificationPort)) )
      {
       /* Identify the type of message. */
       switch (appLibMsg->type)
        {
         case APPLIBMT_Quit:
           done = TRUE;
         break;
 
         case APPLIBMT_ToFront:
           /* Make the program window come to front . */
         break;
 
         case APPLIBMT_Hide:
           /* Iconify the program. */
         break;
 
         case APPLIBMT_Unhide:
           /* Uniconify the program. */
         break;
 
         /*  Process the custom message as you like.
             Here we just use printf() to output the message text. */
         case APPLIBMT_CustomMsg:
           customMsg = (struct ApplicationCustomMsg *) appLibMsg;
           printf("The message text is: %s\n", customMsg-> customMsg);
         break;
 
         /*
             Process any other Application Library messages.
          */
 
        }
       /* Return the processed message to the sender so that it can be freed. */
       IExec->ReplyMsg( (struct Message *) appLibMsg);
      }
    }
  }

Like all Exec messages obtained via the GetMsg() function, Application Library messages must be replied to, i.e. returned to the sender after they have been processed. This is what the last command does in the code above. Remember that all message resources are freed after ReplyMsg() so should you need to use the message data (for example, the custom message text string) beyond the event loop, you must copy it to a memory storage of your own.

Also remember that the notifications are addressed to an abstract application – this makes them rather different from IDCMP messages, which are always sent to a particular window (Intuition is unaware of the application concept). Whereas Intuition stops sending IDCMP messages as soon as the window becomes closed/iconified, the Application Library keeps passing messages as long as the application is registered, regardless of program window state. So be smart in your code when processing Application Library notifications: check for the availability of the program window and perform uniconification when necessary. For example, if an APPLIBMT_ToFront command is sent to your iconified application, there is no window to come to front; you need to uniconify it first and only then call the ScreenToFront(), WindowToFront() and ActivateWindow() sequence. The mistake of referencing a non-existing window is as silly as it is easy to make!

Sending messages

While incoming notifications are processed pretty much like normal Exec messages, the Application Library implements its own method for message sending. It takes three simple steps to send a message to another application:

  1. Prepare the respective data structure: specify the type of message and supply the sender's identifier (appID).
  2. Find the receiver application.
  3. Send a message to it via the SendApplicationMsg() function.

For example, if you want to tell our BestApp application to quit, you send it an APPLIBMT_Quit message like this:

struct ApplicationMsg appMsg;    // message data structure
uint32 bestAppID;                // identifier of the receiver
 
/* Step 1: Prepare the message data structure. */
appMsg.senderAppID = appID;      // identifier of the sender
appMsg.type = APPLIBMT_Quit;     // type of message
 
/* Step 2: Find the receiver application. */
if ( (bestAppID = IApplication->FindApplication(FINDAPP_Name, “BestApp”, TAG_DONE)) )
 {
   /* Step 3: Send the message. */
   IApplication->SendApplicationMsg(appID,
                 bestAppID,
                 &appMsg,
                 APPLIBMT_Quit);
 }

Sending custom messages is done in a similar fashion, the only difference is that you must use a dedicated data structure instead of the generic struct ApplicationMsg. Let’s say that BestApp is in fact a media player, and that it has defined a set of commands for external control, one of them being “Start playback”. Now if another application wants to tell BestApp to start playing, it will need to send the custom message like this:

struct ApplicationCustomMsg customMsg;      // custom message data structure
uint32 bestAppID;                           // identifier of the receiver
 
/* Prepare the message data structure. */
customMsg.almsg.senderAppID = appID;        // identifier of the sender
customMsg.almsg.type = APPLIBMT_CustomMsg;  // type of message
customMsg.customMsg = "Start playback";     // message text
 
/* Find the receiver application. */
if ( (bestAppID = IApplication->FindApplication(FINDAPP_Name, “BestApp”, TAG_DONE)) )
 {
   /* Send the message. */
   IApplication->SendApplicationMsg(appID,
                 bestAppID,
                 (struct ApplicationMsg *) &customMsg,
                 APPLIBMT_CustomMsg);
 }

PrefsObjects

Introduction

Many applications are user-configurable so they need a way to store their settings; we traditionally use the term preferences or simply prefs in the AmigaOS context. It may come as a surprise that before version 4.x, AmigaOS had no real standard for storing preference data. Developers used various, often proprietary solutions: icon tooltypes, IFF-based formats, text files mimicking the Windows .ini format, or binary formats. Some of them are still very popular (tooltypes) or even used by the OS itself (system preferences are stored as IFF files). While this is not a place to go into detail and discuss their particular advantages and disadvantages, let’s mention two common drawbacks that ultimately led to the development of the Application Library PrefsObjects system:

  1. All of the solutions require you to implement your own preferences handling code (that is, routines for parsing, verification, and saving).
  2. None of the various formats used have ever provided a smart enough way to administer complex, structured settings.

PrefsObjects has largely been promoted as being XML-based and therefore human-readable and easy to edit. But using an industry standard format that is platform-independent and supports Unicode is not the biggest “selling point” of PrefsObjects. What makes it different from (and superior to) previous solutions is not the fact that it is XML-based but, rather, that it is object-oriented. Among other things, this property also helps address the two drawbacks mentioned above.

Imagine, for example, a program that uses a plug-in system. Both the main program and its plug-in modules are configurable. Instead of having ten or more individual preference files, you’ll likely want a single file containing settings for the program as well as for the plug-ins. This naturally introduces a certain hierarchy. Plus, the main program can never count on a particular structure of the prefs file: the contents and sequencing of data depends on which plug-ins are installed and on how (and when, if ever) they store their settings. You would have to take all of this into account when implementing your own prefs-handling routines, which means a good deal of work.

Object types

PrefsObjects tackles the task by seeing data as objects, and by providing common functions (methods) for object manipulation. Loading, adding, updating or removing preference objects (be they single items or entire clusters of settings) then becomes a matter of calling the respective function - “invoking the method on the object”, as we say in object-oriented programming. If you are acquainted with this philosophy (as used, for example, in Intuition’s BOOPSI framework), you will find working with PrefsObjects very easy and straightforward.

PrefsObjects distinguishes between the following object types:

dictionary A container to encapsulate other objects. The prefs file itself is an object of the dictionary type.
array A container to encapsulate other objects. The difference between a dictionary and an array is in object referencing: in arrays objects are referenced by an index, while in a dictionary they are referenced by a key string.
number An object to carry a long integer, double, or a boolean value.
string An object to carry a text string.
date An object to carry date and time information.
binary An object to carry arbitrary binary data.

The interface

It has been mentioned above in the Library opening chores section that the Application Library has two interfaces, called “application” and “prefsobjects”. If you want to implement your program preferences using the PrefsObjects system, you must obtain the “prefsobjects” interface: all the necessary functions (methods) are contained therein. Naturally, your application must be registered before you can make use of PrefsObjects.

The preferences file

The file that will be associated with your application as its preferences file is a standard XML document. All operations related to settings – retrieving, adding and changing –, as well as all operations related to the prefs file itself – creation, loading and saving – are performed solely through the library functions. Some operations can even be automated.

The beauty of using standard file format is that from now on programmers and users will be able to view and edit the preferences file by using the standard tool PrefsObjectsEditor (usage of this tool is out of the scope of this page).

Preference organization

It's recommended - but not mandatory - that a preferences file starts with a Dictionary. The advantage of using a Dictionary is that objects stored are identified by a name rather than just put there as they came out of the mind of the programmer. Using named object as several advantages:

  • preferences file can be extended without any pain even when adding preferences data in the middle of the file format (the same is also true when removing data);
  • it's easier to load and save because order does not matter (as long as dealing with data at the same root level);
  • it's easier to read/modify by user.

It's perfectly legal and accepted to put Dictionnary as value in a Dictionary this enables ability to model preferences data arborescence.

Binary data type should be avoided as much as possible because it's restrictive in its form and is not future proof as it does not enable addition or removal of the stored data without compromising compatibility.

Populating the file

Each of these Object type has a corresponding dispatcher to handle operations on this kind of object. The following table indicates the mapping:

PrefsObjects type mapping
Type PrefsObjects dispatcher name
dictionary PrefsDictionary
array PrefsArray
number PrefsNumber
string PrefsString
date PrefsDate
binary PrefsBinary


Automation

By default, the prefs file is stored in and loaded from the ENV: and ENVARC: system directories. The default location can nevertheless be overridden at registration time. For example, your application may want to use its own subdirectory in the ENV(ARC): path. This is done by passing the REGAPP_ENVDir tag-value pair in the RegisterApplication() call. The following line of code will cause saving the prefs file into “ENV:BestApp/” and “ENVARC:BestApp/”.

           REGAPP_ENVDir, "BestApp",

The default naming scheme for the prefs file uses the application name combined with the URL identifier. Also, the .xml extension is appended at the end of the file name to identify the document format. So if our application registered under the name of “BestApp” and with the URL identifier “supercoders.com”, the default preferences file name will be “BestApp.supercoders.com.xml”. This yields a unique file name but might look a little quirky so the library provides a way to override the default naming scheme. This is done by passing the REGAPP_ CustomPrefsBaseName tag-value pair in the RegisterApplication() call. The following line of code will cause saving the prefs file as "BestApp.xml":

           REGAPP_CustomPrefsBaseName, "BestApp",