Copyright (c) Hyperion Entertainment and contributors.

Workbench Library

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Workbench Library

Workbench is the graphic user interface to the Amiga file system that uses symbols called icons to represent disks, directories and files. This article shows how to use workbench.library.

Workbench is both a system program and a screen. Normally it is the first thing the user sees when the machine is booted providing a friendly operating environment for launching applications and performing other important system activities like navigating through the Amiga's hierarchical filing system.

All application programs should be compatible with Workbench. There are only two things you need to know to do this: how to make icons for your application, data files and directories; and how to get arguments if your application is launched from Workbench.

The Info File

The iconic representation of Amiga filing system objects is implemented through .info files. In general, for each file, disk or directory that is visible in the Workbench environment, there is an associated .info file which contains the icon imagery and other information needed by Workbench.

Icons are associated with a particular file or directory by name. For example, the icon for a file named myapp would be stored in a .info file named myapp.info in the same directory.

To make your application program accessible (and visible) in the Workbench environment, you need only supply a .info file with the appropriate name and type. The are four main types of icons (and .info files) used to represent Amiga filing system objects.

Basic Workbench Icon Types
Workbench Icon Type Image Filing System Object Result When Icon Is Activated
Disk LibDiskIcon.png The root level directory Window opens showing files and subdirectories
Drawer LibDrawerIcon.png A subdirectory Window opens showing files and subdirectories
Tool LibToolIcon.png An executable file Application runs (i.e., an application)
Project LibProjectIcon.png A data file Typically, the application that created the data file runs and the data file is automatically loaded into it.

Icons can be created with the IconEdit program (in the Tools directory of the Extras disk), or by copying an existing .info file of the correct type. Icons can also be created under program control with PutDiskObject(). See Icon Library for a discussion of the icon library functions.

The icon type can be set and the icon imagery edited with the IconEdit program. For an executable file the icon type must be set to tool. For a data file the icon type must be set to project. Create icons for your application disk and directories too. For a directory, the icon is stored in a .info file at the same level where the directory name appears (not in the directory itself). The icon type should be set to drawer. The icon for a disk should always be stored in a file named disk.info at the root level directory of the disk. The icon type should be set to disk.

Workbench Environment

On the Amiga there are at least two ways to start a program running:

  • By activating a tool or project icon in Workbench (an icon is activated by pointing to it with the mouse and double-clicking the mouse select button.)
  • By typing the name of an executable file at the Shell (also known as the CLI or Command Line Interface)

In the Workbench environment, a program is run as a separate process. A process is simply a task with additional information needed to use DOS library.

By default, a Workbench program does not have a window to which its output will go. Therefore, stdin, stdout and stderr do not point to legal file handles. This means you cannot use stdio functions such as printf() if your program is started from Workbench unless you first set up a stdio window.

Some compilers have options or defaults to provide a stdio window for programs started from Workbench. Applications can use an auto console window for stdio when started from Workbench by opening "CON:0/0/640/200/auto/close/wait" as a file. An auto console window will only open if stdio input or output occurs. This can also be handled in the startup code module that comes with your compiler.

Argument Passing in Workbench

Applications started from Workbench receive arguments in the form of a WBStartup structure. This is similar to obtaining arguments from a command line interface through argc and argv. The WBStartup message contains an argument count and a pointer to a list of file and directory names.

One Argument

A program started by activating its tool icon gets one argument in the WBStartup message: the name of the tool itself.

Two Arguments

All project icons (data files) have a default tool field associated with them that tells Workbench which application tool to run in order to operate on the data that the icon represents. When the user activates a project icon, Workbench runs the application specified in the default tool field passing it two arguments in the WBStartup message: the name of the tool and the project icon that the user activated.

Multiple Arguments

With extended select, the user can activate many icons at once. (Extended select means the user holds down the Shift key while clicking the mouse select button once on each icon in a group, double-clicking on the last icon.) If one of the icons in a group activated with extended select is an application tool, Workbench runs that application passing it the name of all the other icons in the group. This allows the user to start an application with multiple project files as arguments. If none of the icons in a group activated with extended select is a tool icon, then Workbench looks in the default tool field of each icon in the order they were selected and runs the first tool it finds.

WBStartup Message

When Workbench loads and starts a program, its sends the program a WBStartup message containing the arguments as summarized above. Normally, the startup code supplied with your compiler will place a pointer to WBStartup in argv for you, set argc to zero and call your program.

The WBStartup message, whose structure is outlined in <workbench/startup.h>, has the following structure elements:

struct WBStartup
{
    struct Message      sm_Message;     /* a standard message structure */
    struct MsgPort *    sm_Process;     /* the process descriptor for you */
    BPTR                sm_Segment;     /* a descriptor for your code */
    LONG                sm_NumArgs;     /* the number of elements in ArgList */
    char *              sm_ToolWindow;  /* reserved for future use */
    struct WBArg *      sm_ArgList;     /* the arguments themselves */
};

The fields of the WBStartup structure are used as follows.

sm_Message
A standard Exec message. The reply port is set to the Workbench.
sm_Process
The process descriptor for the tool (as returned by CreateProcess())
sm_Segment
The loaded code for the tool (returned by LoadSeg())
sm_NumArgs
The number of arguments in sm_ArgList
sm_ToolWindow
Reserved (not currently passed in startup message)
sm_ArgList
This is the argument list itself. It is a pointer to an array of WBArg structures with sm_NumArgs elements.

Workbench arguments are passed as an array of WBArg structures in the sm_ArgList field of WBStartup. The first WBArg in the list is always the tool itself. If multiple icons have been selected when a tool is activated, the selected icons are passed to the tool as additional WBArgs. If the tool was derived from a default tool, the project will be the second WBArg. If extended select was used, arguments other than the tool are passed in the order of selection; the first icon selected will be first (after the tool), and so on.

Each argument is a struct WBArg and has two parts: wa_Name and wa_Lock.

struct WBArg
{
    BPTR   wa_Lock; /* a lock descriptor */
    BYTE * wa_Name; /* a string relative to that lock */
};

The wa_Name element is the name of an AmigaDOS filing system object. The wa_Name field of the first WBArg is always the name of your program and the wa_Lock field is an AmigaDOS Lock on the directory where your program is stored.

If your program was started by activating a project icon, then you get a second WBarg with the wa_Name field containing the file name of the project and the wa_Lock containing an AmigaDOS Lock on the directory where the project file is stored.

If your program was started through extended select, then you get one WBArg for each icon in the selected group in the order they were selected. The wa_Name field contains the file name corresponding to each icon unless the icon is for a directory, disk, or the Trashcan in which case the wa_Name is set to NULL. The wa_Lock field contains an AmigaDOS Lock on the directory where the file is stored. (For disk or drawer icons the wa_Lock is a lock on the directory represented by the icon. Or, wa_Lock may be NULL if the icon type does not support locks.)

Workbench Locks Belong to Workbench
You must never call UnLock() on a wa_Lock. These locks belong to Workbench, and Workbench will UnLock() them when the WBStartup message is replied by your startup code. You must also never UnLock() your program's initial current directory lock (i.e., the lock returned by an initial CurrentDir() call). The classic symptom caused by unlocking Workbench locks is a system hang after your program exits, even though the same program exits with no problems when started from the Shell.

You should save the lock returned from an initial CurrentDir(), and CurrentDir() back to it before exiting. In the Workbench environment, depending on your startup code, the current directory will generally be set to one of the wa_Locks. By using CurrentDir(wa_Lock) and then referencing wa_Name, you can find, read, and modify the files that have been passed to your program as WBArgs.

Example of Parsing Workbench Arguments

The following example will display all WBArgs if started from Workbench, and all Shell arguments if started from the Shell.

/* prargs.c
**
** PrArgs.c - This program prints all Workbench or Shell (CLI) arguments.
*/
#include <exec/types.h>
#include <workbench/startup.h>
#include <proto/dos.h>
 
int main(int argc, char **argv)
{
    struct WBStartup *argmsg;
    struct WBArg *wb_arg;
    int32 ktr;
    BPTR olddir;
    BPTR outFile;
 
    /* argc is zero when run from the Workbench,
    **         positive when run from the CLI.
    */
    if (argc == 0)
    {
    /* AmigaDOS has a special facility that allows a window  */
    /* with a console and a file handle to be easily created. */
    /* CON: windows allow you to use fprintf() with no hassle */
    if (ZERO != (outFile = IDOS->Open("CON:0/0/640/200/PrArgs", MODE_NEWFILE)))
        {
        /* In AmigaOS, argv is a pointer to the WBStartup message
        ** when argc is zero.  (run under the Workbench.)
        */
        argmsg = (struct WBStartup *)argv ;
        wb_arg = argmsg->sm_ArgList ;         /* head of the arg list */
 
        IDOS->FPrintf(outFile, "Run from the workbench, %ld args.\n",
                         argmsg->sm_NumArgs);
 
        for (ktr = 0; ktr < argmsg->sm_NumArgs; ktr++, wb_arg++)
            {
            if (ZERO != wb_arg->wa_Lock)
                {
                /* locks supported, change to the proper directory */
                olddir = IDOS->CurrentDir(wb_arg->wa_Lock) ;
 
                /* process the file.
                ** If you have done the CurrentDir() above, then you can
                ** access the file by its name.  Otherwise, you have to
                ** examine the lock to get a complete path to the file.
                */
                IDOS->FPrintf(outFile, "\tArg %2.2ld (w/ lock): '%s'.\n",
                                 ktr, wb_arg->wa_Name);
 
                /* change back to the original directory when done.
                ** be sure to change back before you exit.
                */
                IDOS->CurrentDir(olddir) ;
                }
            else
                {
                /* something that does not support locks */
                IDOS->FPrintf(outFile, "\tArg %2.2ld (no lock): '%s'.\n",
                                 ktr, wb_arg->wa_Name);
                }
            }
        /* wait before closing down */
        IDOS->Delay(500L);
        IDOS->Close(outFile);
        }
    }
    else
    {
    /* using 'tinymain' from lattice c.
    ** define a place to send the output (originating CLI window = "*")
    ** Note - if you open "CONSOLE:" and your program is RUN, the user will not
    ** be able to close the CLI window until you close the "CONSOLE:" file.
    */
    if (ZERO != (outFile = IDOS->Open("CONSOLE:", MODE_NEWFILE)))
        {
        IDOS->FPrintf(outFile, "Run from the CLI, %d args.\n", argc);
 
        for ( ktr = 0; ktr < argc; ktr++)
            {
            /* print an arg, and its number */
            IDOS->FPrintf(outFile, "\tArg %2.2ld: '%s'.\n", ktr, argv[ktr]);
            }
        IDOS->Close(outFile);
        }
    }
 
    return 0;
}

The Workbench Library

Workbench arguments are sent to an application when it is started. There are also special facilities that allow an application that is already running to get additional arguments. These special facilities are known as AppWindow, AppIcon and AppMenuItem.

An AppWindow is a special kind of window that allows the user to drag icons into it. Applications that set up an AppWindow will receive a message from Workbench whenever the user moves an icon into the AppWindow. The message contains the name of the file or directory that the icon represents.

An AppIcon is similar to an AppWindow. It is a special type of icon that allows the user to drag other icons on top of it. Like AppWindows, an application that sets up an AppIcon will receive a message from Workbench whenever the user moves another icon on top of the AppIcon. The message contains the name of the file or directory that the moved icon represents.

An AppMenuItem allows an application to add a custom menu item to the usual set of menu choices supported by Workbench. An application that sets up an AppMenuItem will receive a message from Workbench whenever the user picks that item from the Workbench menus.

When an application receives the messages described above, the message will include struct WBArg *am_ArgList containing the names (wa_Name) and directory locks (wa_Lock) of all selected icons that were passed as arguments by the user. This am_ArgList has the same format as the sm_ArgList of a WBStartup message.

The AppMessage Structure

When Workbench notifies an application of AppWindow, AppIcon, or AppMenuItem activity, it sends an AppMessage to the application's message port (from <workbench/workbench.h>):

    #define      AM_VERSION     1
 
    struct AppMessage {
        struct Message am_Message;    /* standard message structure */
        UWORD am_Type;                /* message type */
        ULONG am_UserData;            /* application specific */
        ULONG am_ID;                  /* application definable ID */
        LONG am_NumArgs;              /* # of elements in arglist */
        struct WBArg *am_ArgList;     /* the arguments themselves */
        UWORD am_Version;             /* will be AM_VERSION */
        UWORD am_Class;               /* message class */
        WORD am_MouseX;               /* mouse x position of event */
        WORD am_MouseY;               /* mouse y position of event */
        ULONG am_Seconds;             /* current system clock time */
        ULONG am_Micros;              /* current system clock time */
        ULONG am_Reserved[8];
    };

The AppMessage's am_Type field tells the application which type of AppObject the message is about. The field will be:

  • AMTYPE_APPWINDOW if the message is about an AppWindow,
  • AMTYPE_APPICON if the message is about an AppIcon, or
  • AMTYPE_APPMENUITEM if the message is about an AppMenuItem.

When an application creates an AppObject, it can assign the AppObject application specific data (most likely a pointer) and an ID. Workbench will pass an AppObject's data and ID back to the application when it sends an AppMessage about the AppObject. The AppMessage's am_UserData and am_ID fields hold the user data and the ID.

The am_NumArgs field tells how many icons were involved in the user's AppObject action. For an AppWindow or AppIcon, am_NumArgs is the number of icons the user dropped on the AppWindow or AppIcon. For an AppMenuItem, am_NumArgs represents the number of icons that were selected when the user selected this AppMenuItem. If no icons were selected during an AppMenuItem event or the user double-clicked on an AppIcon, am_NumArgs will be zero. Workbench does not send AppMessages if the user double-clicks an AppWindow.

The am_ArgList field is a pointer to a list of WBArgs (from <workbench/startup.h>) corresponding to each icon dropped (or selected). If there were no icons dropped or selected, this field will be NULL.

For future expansion possibilities, the AppMessage structure has a version number. The version number is #defined as AM_VERSION in <workbench/workbench.h>.

The am_MouseX and am_MouseY fields apply only to AppWindows and contain the coordinates of the mouse pointer when the user dropped the icon(s). These coordinates are relative to the AppWindow's upper left corner.

The am_Seconds and am_Micros fields represent the time that the event took place.

Any remaining fields are undefined at present and should be set to NULL.

Workbench Library Functions

AppWindows, AppIcons and AppMenuItems extend the user's ability to perform operations with the Workbench iconic interface. They all provide graphical methods for passing arguments to a running application. In order to manage AppWindows, AppIcons and AppMenuItems, the Amiga OS includes these Workbench library functions:

struct AppIcon *AddAppIconA( ULONG, ULONG, char *, struct MsgPort *, struct FileLock *,
  struct DiskObject *, struct *TagItem );
struct AppMenuItem *AddAppMenuItemA( ULONG, ULONG, char *, struct MsgPort *, struct *TagItem);
struct AppWindow *AddAppWindowA( ULONG, ULONG, struct Window *, struct MsgPort *, struct *TagItem);
 
BOOL RemoveAppIcon(struct AppIcon *);
BOOL RemoveAppMenuItem(struct AppMenuItem *);
BOOL RemoveAppWindow(struct AppWindow  *);

The functions AddAppMenuItemA(), AddAppWindowA() and AddAppIconA() have alternate entry points using the same function name without the trailing A. The alternate functions accept any TagItem arguments on the stack instead of from an array. See the listings below for examples.

An AppIcon Example

The example listed here shows how to create an AppIcon and obtain arguments from Workbench when the user drops other icons on top of it. The AppIcon will appear as a disk icon named "TestAppIcon" on the Workbench screen. (All AppIcons appear on the Workbench screen or window.)

For convenience, this example code uses GetDefDiskObject() to create the icon imagery for the AppIcon. Applications should never do this. Use your own custom imagery for AppIcons instead.

/* appicon.c
/* Works from the Shell (CLI) only */
 
#include <exec/types.h>          /* Need this for the Amiga variable types  */
#include <workbench/workbench.h> /* This has DiskObject and AppIcon structs */
#include <workbench/startup.h>   /* This has WBStartup and WBArg structs    */
#include <exec/libraries.h>      /* Need this to check library versions     */
 
#include <proto/icon.h>          /* Icon (DiskObject) function prototypes   */
#include <proto/exec.h>          /* Exec message, port and library functions*/
#include <proto/workbench.h>     /* AppIcon function protos                 */
 
struct Library *IconBase;
struct Library *WorkbenchBase;
struct IconIFace *IIcon;
struct WorkbenchIFace *IWorkbench;
 
int main(int argc, char **argv)
{
  struct DiskObject   *dobj=NULL;
  struct MsgPort    *myport=NULL;
  struct AppIcon   *appicon=NULL;
  struct AppMessage *appmsg=NULL;
 
  LONG dropcount=0L;
  ULONG x;
  BOOL success=0L;
 
  IconBase = IExec->OpenLibrary("icon.library", 50);
  WorkbenchBase = IExec->OpenLibrary("workbench.library", 50);
 
  IIcon = (struct IconIFace*)IExec->GetInterface(IconBase, "main", 1, NULL);
  IWorkbench = (struct WorkbenchIFace*)IExec->GetInterface(WorkbenchBase, "main", 1, NULL);
 
  /* Get the the right version of the Icon Library, initialize IconBase */
  if(IIcon != NULL && IWorkbench != NULL)
  {
    /* This is the easy way to get some icon imagery */
    /* Real applications should use custom imagery   */
    dobj = IIcon->GetDefDiskObject(WBDISK);
    if(dobj != 0)
      {
      /* The type must be set to NULL for a WBAPPICON */
      dobj->do_Type = NULL;
 
      myport = IExec->AllocSysObjectTags(ASOT_PORT, NULL);
      if(myport)
        {
        /* Put the AppIcon up on the Workbench window */
        appicon = IWorkbench->AddAppIconA(0L,0L,"TestAppIcon",myport,NULL,dobj,NULL);
        if(appicon)
          {
          /* For the sake of this example, we allow the AppIcon */
          /* to be activated only five times.                   */
          IDOS->Printf("Drop files on the Workbench AppIcon\n");
          IDOS->Printf("Example exits after 5 drops\n");
 
          while(dropcount<5)
            {
            /* Here's the main event loop where we wait for */
            /* messages to show up from the AppIcon         */
            IExec->WaitPort(myport);
 
            /* Might be more than one message at the port... */
            while(appmsg=(struct AppMessage *)IExec->GetMsg(myport))
              {
              if(appmsg->am_NumArgs==0L)
                {
                /* If NumArgs is 0 the AppIcon was activated directly */
                IDOS->Printf("User activated the AppIcon.\n");
                IDOS->Printf("A Help window for the user would be good here\n");
                }
              else if(appmsg->am_NumArgs>0L)
                {
                /* If NumArgs is >0 the AppIcon was activated by */
                /* having one or more icons dropped on top of it */
                IDOS->Printf("User dropped %ld icons on the AppIcon\n",
                                              appmsg->am_NumArgs);
                for(x=0;x<appmsg->am_NumArgs;x++)
                  {
                  IDOS->Printf("#%ld name='%s'\n",x+1,appmsg->am_ArgList[x].wa_Name);
                  }
                }
              /* Let Workbench know we're done with the message */
              IExec->ReplyMsg((struct Message *)appmsg);
              }
            dropcount++;
            }
          success = IWorkbench->RemoveAppIcon(appicon);
          }
        /* Clear away any messages that arrived at the last moment */
        while(appmsg = (struct AppMessage *)IExec->GetMsg(myport))
            IExec->ReplyMsg((struct Message *)appmsg);
        IExec->FreeSysObject(ASOT_PORT, myport);
        }
      IIcon->FreeDiskObject(dobj);
      }
 
  IExec->DropInterface((struct Interface*)IWorkbench);
  IExec->DropInterface((struct Interface*)IIcon);
 
  IExec->CloseLibrary(WorkbenchBase);
  IExec->CloseLibrary(IconBase);
 
  return 0;
}

An AppMenuItem Example

This example shows how to create an AppMenuItem. The example adds a menu item named "Browse Files" to the Workbench Tools menu. (All AppMenuItems appear in the Workbench Tools menu.) When the menu item is activated, the example program receives a message from Workbench and then attempts to start up an instance of the More program. (The More program is in the Utilities directory of the Workbench volume.)

The example starts up the More program as a separate, asynchronous process using the SystemTags() function. When the AppMenuItem has been activated five times, the program exits after freeing any system resources it has used.

/* appmenuitem.c
/* Works from the Shell (CLI) only */
 
#include <exec/types.h>          /* Need this for the Amiga variable types  */
#include <workbench/workbench.h> /* This has DiskObject and AppIcon structs */
#include <workbench/startup.h>   /* This has WBStartup and WBArg structs    */
#include <exec/libraries.h>
#include <dos/dostags.h>
 
#include <proto/dos.h>
#include <proto/exec.h>          /* Exec message, port and library functions*/
#include <proto/workbench.h>     /* AppMenuItem function protos             */
 
struct Library *WorkbenchBase;
struct WorkbenchIFace *IWorkbench;
 
int main(int argc, char **argv)
{
  struct MsgPort      *myport=NULL;
  struct AppMenuItem *appitem=NULL;
  struct AppMessage   *appmsg=NULL;
  LONG result, x, count=0L;
  BOOL success=0L;
  BPTR file;
 
  WorkbenchBase = IExec->OpenLibrary("workbench.library", 50);
  IWorkbench = (struct WorkbenchIFace*)IExec->GetInterface(WorkbenchBase, "main", 1, NULL);
 
  if (IWorkbench != NULL)
  {
    if(myport = IExec->AllocSysObjectTags(ASOT_PORT, NULL))
    {
    /* Add our own AppMenuItem to the Workbench Tools Menu */
    appitem = IWorkbench->AddAppMenuItemA(0L,                   /* Our ID# for item */
                    (ULONG)"SYS:Utilities/More",  /* Our UserData     */
                           "Browse Files",        /* MenuItem Text    */
                            myport,NULL);         /* MsgPort, no tags */
    if(appitem)
      {
      IDOS->Printf("Select Workbench Tools demo menuitem 'Browse Files'\n");
 
      /* For this example, we allow the AppMenuItem to be selected */
      /* only once, then we remove it and exit                     */
      IExec->WaitPort(myport);
      while((appmsg = (struct AppMessage *)IExec->GetMsg(myport)) && (count<1))
        {
        /* Handle messages from the AppMenuItem - we have only one  */
        /* item so we don't have to check its appmsg->am_ID number. */
        /* We'll System() the command string that we passed as      */
        /* userdata when we added the menu item.                    */
        /* We find our userdata pointer in appmsg->am_UserData      */
 
        IDOS->Printf("User picked AppMenuItem with %ld icons selected\n",
                                                appmsg->am_NumArgs);
        for(x=0;x<appmsg->am_NumArgs;x++)
           IDOS->Printf("  #%ld name='%s'\n",x+1,appmsg->am_ArgList[x].wa_Name);
 
        count++;
        if( file=Open("CON:0/40/640/150/AppMenu Example/auto/close/wait",
                         MODE_OLDFILE)  )     /* for any stdio output */
          {
          result=IDOS->SystemTags((UBYTE *)appmsg->am_UserData,SYS_Input,file,
                                                         SYS_Output,NULL,
                                                         SYS_Asynch,TRUE,
                                                         TAG_END);
          /* If Asynch System() itself fails, we must close file */
          if(result == -1) IDOS->Close(file);
          }
        IExec->ReplyMsg((struct Message *)appmsg);
        }
      success = IWorkbench->RemoveAppMenuItem(appitem);
      }
 
    /* Clear away any messages that arrived at the last moment */
    /* and let Workbench know we're done with the messages     */
    while(appmsg=(struct AppMessage *)IExec->GetMsg(myport))
      {
      IExec->ReplyMsg((struct Message *)appmsg);
      }
    IExec->FreeSysObject(ASOT_PORT, myport);
    }
 
  IExec->DropInterface((struct Interface*)IWorkbench);
  IExec->CloseLibrary(WorkbenchBase);
 
  return 0;
}

An AppWindow Example

This example shows how to create an AppWindow and obtain arguments from Workbench when the user drops an icon into it. The AppWindow will appear on the Workbench screen with the name "AppWindow" and will run until the window's close gadget is selected. If any icons are dropped into the AppWindow, the program prints their arguments in the Shell window.

/* appwindow.c */
/* Works from the Shell (CLI) only */
 
#include <exec/types.h>          /* Need this for the Amiga variable types  */
#include <workbench/workbench.h> /* This has DiskObject and AppWindow       */
#include <workbench/startup.h>   /* This has WBStartup and WBArg structs    */
#include <exec/libraries.h>      /* Need this to check library versions     */
 
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/workbench.h>
 
struct Library        *IntuitionBase;
struct Library        *WorkbenchBase;
struct IntuitionIFace *IIntuition;
struct WorkbenchIFace *IWorkbench;
 
int main(int argc, char **argv)
{
  struct MsgPort *awport;
  struct Window  *win;
  struct AppWindow *appwin;
  struct IntuiMessage *imsg;
  struct AppMessage *amsg;
  struct WBArg   *argptr;
 
  ULONG           winsig, appwinsig, signals, id = 1, userdata = 0;
  BOOL            done = FALSE;
  int             i;
 
  IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
  WorkbenchBase = IExec->OpenLibrary("workbench.library", 50);
 
  IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
  IWorkbench = (struct WorkbenchIFace*)IExec->GetInterface(WorkbenchBase, "main", 1, NULL);
 
  if (IIntuition != NULL)
  {
    if (IWorkbench != NULL)
    {
      if (awport = IExec->AllocSysObjectTags(ASOT_PORT, NULL))
         {
         if (win = IIntuition->OpenWindowTags(NULL,
                                  WA_Width, 200,        WA_Height, 50,
                                  WA_IDCMP, CLOSEWINDOW,
                                  WA_Flags, WINDOWCLOSE | WINDOWDRAG,
                                  WA_Title, "AppWindow",
                                  TAG_END))
          {
          if (appwin = IWorkbench->AddAppWindow(id, userdata, win, awport, NULL))
            {
            IDOS->Printf("AppWindow added... Drag files into AppWindow\n");
            winsig    = 1L << win->UserPort->mp_SigBit;
            appwinsig = 1L << awport->mp_SigBit;
 
            while (! done)
              {
              /* Wait for IDCMP messages and AppMessages */
              signals = IExec->Wait( winsig | appwinsig );
 
              if(signals & winsig)      /* Got an IDCMP message */
                {
                while (imsg = (struct IntuiMessage *) IExec->GetMsg(win->UserPort))
                  {
                  if (imsg->Class = CLOSEWINDOW)   done = TRUE;
                  IExec->ReplyMsg((struct Message *) imsg);
                  }
                }
              if(signals & appwinsig)   /* Got an AppMessage */
                {
                while (amsg = (struct AppMessage *) IExec->GetMsg(awport))
                  {
                  IDOS->Printf("AppMsg: Type=%ld, ID=%ld, NumArgs=%ld\n",
                           amsg->am_Type, amsg->am_ID, amsg->am_NumArgs);
                  argptr = amsg->am_ArgList;
                  for (i = 0; i < amsg->am_NumArgs; i++)
                    {
                    IDOS->Printf("   arg(%ld): Name='%s', Lock=%lx\n",
                             i, argptr->wa_Name, argptr->wa_Lock);
                    argptr++;
                    }
                  IExec->ReplyMsg((struct Message *) amsg);
                  }
                }
              }     /* done */
            IWorkbench->RemoveAppWindow(appwin);
            }
          IIntuition->CloseWindow(win);
          }
        /* Make sure there are no more outstanding messages */
        while(amsg = (struct AppMessage *)IExec->GetMsg(awport))
              IExec->ReplyMsg((struct Message *)amsg);
        IExec->FreeSysObject(ASOT_PORT, awport);
        }
      }
    }
 
    IExec->DropInterface((struct Interface*)IIntuition);
    IExec->DropInterface((struct Interface*)IWorkbench);
    IExec->CloseLibrary(IntuitionBase);
    IExec->CloseLibrary(WorkbenchBase);
 
    return 0;
}

Workbench and the Startup Code Module

Standard startup code handles the detail work of interfacing with the arguments and environment of Workbench and the Shell (or CLI). This section describes the behavior of standard startup modules such as the ones supplied with newlib and clib2.

The environment for a program started from Workbench is quite different from the environment for a program started from the Shell. The Shell does not create a new process for a program; it jumps to the program's code and the program shares the process with the Shell. Programs run under the Shell have access to all the Shell's environment, including the ability to modify that environment. (Programs run from the Shell should be careful to restore all values that existed on startup.) Workbench starts a program as a new DOS process, explicitly passing the execution environment to the program.

Workbench Startup

When the user activates a project or tool icon, the program is run as a separate process asynchronous to Workbench. This allows the user to take full advantage of the multitasking features of the Amiga. A process is simply a task with additional information needed to use DOS library.

When Workbench loads and starts a program, it sends the program a WBStartup message containing the arguments as described earlier. The WBStartup also contains a pointer to the new Process structure which describes the execution environment of the program. The WBStartup message is posted to the message port of the program's Process structure.

The Process message port is for the exclusive use of DOS, so this message must be removed from the port before using any DOS library functions. Normally this is handled by the startup code module that comes with your compiler so you don't have to worry about this unless you are writing your own startup code.

Standard startup code modules also set up SysBase, the pointer to the Exec master library, and open the DOS library setting up DOSBase. That is why Exec and AmigaDOS functions can be called by C applications without first opening a library; the startup code that applications are linked with handles this. Some special startups may also set up NIL: input and output streams, or may open a stdio window so that the Workbench applications can use stdio functions such as printf().

The startup code can tell if it is running in the Workbench environment because the pr_CLI field of the Process structure will contain NULL. In that case the startup code removes the WBStartup message from the Process message port with GetMsg() before using any functions in the DOS library.

Do Not Use the Process Message Port for Anything Else
The message port in a Process structure is for the exclusive use of the DOS library.

Standard startup code will pass the WBStartup message pointer in argv and 0 (zero) in argc if the program is started from Workbench. The startup code calls the application code that it is linked with as a function. When the application code exits back to the startup code, the startup code closes and frees all opens and allocations it made. It will then Forbid(), and ReplyMsg() the WBStartup message, notifying Workbench that the application Process may be terminated and its code unloaded from memory.

Avoid the DOS Exit() function
The DOS Exit() function does not return an application to the startup code that called it. If you wish to exit your application, use the exit function provided by your startup code (usually lower-case exit(), or _exit for assembler), passing it a valid DOS return code as listed in the include file <libraries/dos.h>.

Shell Startup

When a program is started from the Shell (or a Shell script), standard startup modules will parse the command line into an array of pointers to individual argument strings placing them in argv, and an argument count in argc.

If a program is started from the Shell, argc will always equal at least one and the first element in argv will always be a pointer to the command name. Other command line arguments are stored in turn. For example, if the command line was:

df0:myprogram "my file1" file2 ;this is a comment

then argc will be 3, argv[0] will be "df0:myprogram", argv[1] will be "my file1", and argv[2] will be "file2". Correct startup code will strip spaces between arguments and trailing spaces from the last argument and will also properly deal with quoted arguments with embedded spaces.

As with Workbench, standard startup code for the Shell sets up SysBase, the pointer to the Exec master library, and opens the DOS library setting up DOSBase. C applications that are linked with standard startup code can call an Exec or AmigaDOS functions without opening the library first.

The startup code also fills in the stdio file handles (_stdin, _stdout, etc.) for the application. Finally argv and argc, are pushed onto the stack and the application is called. When the application returns or exits back to the startup code, the startup code closes and frees all opens and allocations it has made for the application, and then returns to the system with the whatever value the program exited with.

Link your applications only with standard, tested startup code of some type such as the module supplied with your compiler. Startup code provides your programs with correct, consistent handling of Shell command line and Workbench arguments and will perform some initializations and cleanups which would otherwise need to be handled by your own code. Very small startups can be used for programs that do not require command line arguments.

A few words of warning for those of you who do not use standard startup code:

  • If you are started as a Workbench process, you must GetMsg() the WBStartup message before using any functions in the DOS library.
  • You must turn off task switching (with Forbid()) before replying the WBStartup message from Workbench. This will prevent Workbench from unloading your code before you can exit properly.
  • If you do your own command line parsing, you must provide the user with consistent and correct handling of command line arguments.

Function Reference

The following are brief descriptions of the functions in workbench.library. See SDK for details on each function call.

Workbench Library Functions
Function Description
AddAppIcon() Add an AppIcon to Workbench
AddAppMenuItem() Add an AppMenuItem to the Workbench Tools menu
AddAppWindow() Add an AppWindow to Workbench
RemoveAppIcon() Remove an AppIcon to Workbench
RemoveAppMenuItem() Remove an AppMenuItem to the Workbench Tools menu
RemoveAppWindow() Remove an AppWindow to Workbench