Copyright (c) Hyperion Entertainment and contributors.

Executing External Programs

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Executing External Programs

The Amiga operating system provides flexible methods of launching programs from an application. The System() function is more flexible and powerful than the deprecated Execute() function. Improvements to the con-handler, CON:, allow programs to create "auto con" windows, which are console windows that will only appear if input is requested from or output is sent to that console.

The System Function

System() spawns a shell process to execute the command line which is passed to System() as an argument. The shell parses the command-line normally, just like command-lines typed directly into the shell's console. If it can, System() will pass the current path and directory to the programs it launches.

The System() function can execute external commands either synchronously or asynchronously. When System() is used synchronously, control returns to the calling program after the external program has completed. In this case, System() returns the external program's return code, or a -1 if the command could not be found or run. On the other hand, when System() initiates a program asynchronously, the program is no longer a concern for the caller. The operating system will take care of the cleanup. This is extremely useful for an application that must start up multiple programs on user demand, such as a hot key commodity. By default, System() starts programs synchronously. To launch a program asynchronously, use the SYS_Asynch tag with the data field set to TRUE.

With the System() call, it is easy to provide programs with specific input, output and error output handles. The tags SYS_Input, SYS_Output and SYS_ErrorOutput (defined in dos/dostags.h) are used to supply the input, output and error output file handles. A program can pass its own input, output and error handles to a synchronously launched program by passing the results of Input(), Output() and ErrorOutput() respectively. Note that with a synchronous System() call, the OS will not close these handles when the spawned process exits. In the case of an asynchronously launched program, the launching program normally must provide new IO handles since the system automatically closes these handles when the asynchronous process ends. Because AmigaDOS wants separate handles for input and output, System() will automatically create an output handle if it's passed a handle for SYS_Input and NULL for SYS_Output. This allows a program to open a CON: window for input and use it for both input and output, as System() will test to see if input is interactive and, if so, will attempt to open "*" for output to that console. If the input file handle is not interactive, System() opens "*" on the current console task.

Programs launched using System() are not restricted to the built-in, or Boot, shell. In the near future, System() will be able to take advantage of specially designed shells (the design requirements of these special shells have not yet been documented). Two tags, SYS_UserShell and SYS_CustomShell, specify which shell System() should use to execute the command-line. By using the SYS_UserShell tag with a tag value of TRUE, an application tells System() to send a command to the user's preferred shell rather that the boot shell (Note that the default user shell is the boot shell). If an application opens a shell for the user or executes the user's command-lines, it should tell System() to use the user shell. If an application requires consistent shell behavior, it must not use the user shell because the user can change the user shell. Another shell tag, SYS_CustomShell, allows an application to choose other shells besides the boot and user shells. This tag's data field should contain the custom shell's name as it appears in the system resident list.

Although it does offer many features, System() does have some limitations. First, because command paths currently only exist in the CLI structure, Workbench (non-CLI) processes have no paths. Consequently, when a Workbench process calls a program using System(), the shell has no path to search for that program (aside from the system default search path of C: and the current directory). A second limitation of the System() function involves CTRL-C/D/E/F handling. When a task opens a CON: window to provide a handle for a System()-launched command, that task is the owner of the handle. This has two effects. A System()-launched command running in that CON: window will only be able to receive CTRL-C/D/E/F signals while it is doing input or output to the window (i.e. when it has a pending read or write). If the task that owns the handle is still around, it receives CTRL-C/D/E/F signals whenever those keys are pressed in the CON: window.

The Con-handler

The con-handler (CON:) has many enhancements that allow programs to further customize their console windows. An application requests these new features by appending keywords to the end of the CON: specification string for Open(). These keywords may appear in any order after the title string in the CON: specification.

These new keywords are:

   AUTO            Don't open CON: window until/unless input or
                       output occurs
   CLOSE           Put a close gadget on the CON: window
   WAIT            Hold off Close until user clicks Close or types CTRL-\
   WINDOW  0xaddr  Use specified window (may be on a custom screen)
   SCREEN  name    Open on specified public screen

The additional CON: keywords BACKDROP, NODRAG, NOBORDER, NOSIZE, SIMPLE, and SMART, allow control of other attributes of a CON: window.

An AUTO/CLOSE/WAIT CON: window is perfect for C startup code and for asynchronous System() startup of arbitrary commands. Because of the auto con feature (AUTO), the system will never open the CON: window of a program that doesn't do any stdio (example: Calculator). If a command does stdio input or output, the window will not open until stdio occurs. If the window opens, the wait feature (WAIT) causes the window to stay open until the user types CTRL-\ (end of file) or clicks the Close gadget. The CLOSE keyword tells the con-handler to put a close gadget on the CON: window.

Example: CON:/0/0/640/200/My Title/AUTO/CLOSE/WAIT

The SCREEN keyword along with a public screen name allows an application to open a CON: window on that public screen. Alternately, an application can use the WINDOW keyword to attach a console to an already open Intuition window. The hex address of the Intuition window must follow the WINDOW keyword. This makes it possible for System()-launched programs to do their stdio in a window on a custom screen.

System() Example

The example code, SystemTest.c, provides two simple subroutines for executing external commands, and demonstrates the following features:

  • Synchronous System() command execution using the calling program's Input()/Output().
  • Synchronous System() command execution in a custom screen window.
  • Asynchronous System() command startup with an AUTO/CLOSE/WAIT window.
  • OpenScreenTags and OpenWindowTags for a window.
/* SystemTest.c 
 *
 * Demonstration of System(), AUTO CON, and custom screen CON
 */
 
#include <exec/types.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <intuition/intuition.h>
#include <graphics/displayinfo.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/intuition.h>
 
/* our function error codes */
#define SYSTEMFAIL      (-1L)
#define WINDOWFAIL      (-2L)
 
/* function prototypes */
LONG beginCommand(UBYTE *command);
LONG doCommand(UBYTE *command, BPTR other);
VOID checkResult(UBYTE *command, LONG result);
 
 
/* Formatted version string for the VERSION command */
UBYTE *vers = "\0$VER: SystemTest 53.1";
 
struct Library *IntuitionBase;
 
 
int main(int argc, char **argv)
{
    extern struct Library *DOSBase;
    struct Screen *scr = NULL;
    struct Window *win = NULL;
    ULONG penspecterm = ~0;
    LONG result;
    BPTR file;
    UBYTE *command;
    UBYTE buf[128];
 
    if(!(IntuitionBase = IExec->OpenLibrary("intuition.library", 50)))
    {
        IDOS->Printf("This example requires intuition.library V50 or higher\n");
        return RETURN_FAIL;
    }
 
    /* SYNCHRONOUS SYSTEM() WITH OUR INPUT/OUTPUT
     */
    IDOS->Printf("\n*** SystemTest: Synchronous System call 'dir libs:':\n");
    command = "dir libs:";
    result = doCommand(command,NULL);
    checkResult(command,result);
    IDOS->Printf("\n*** SystemTest: Synchronous System call of nonexistant command:\n");
    command = "badcommand";
    result = doCommand(command,NULL);
    checkResult(command,result);
 
    IDOS->Printf("\n*** SystemTest: Synchronous System call 'ask \"...Answer y now\"':\n");
    command =
        "ask \"Ready for CON: on a Custom Screen? Answer y now (should return 5):\"";
    result = doCommand(command,NULL);
    checkResult(command,result);
 
   /* SYNCHRONOUS SYSTEM() WITH CON: IN A CUSTOM SCREEN AND WINDOW
     */
    if(scr = IIntuition->OpenScreenTags(NULL,
                SA_Width, 640,
                SA_Height, 200,
                SA_Depth, 3,
                SA_DisplayID, HIRES_KEY,
                SA_Pens, &penspecterm,  /* Give us New Look */
                TAG_END))
        {
        if(win = IIntuition->OpenWindowTags(NULL,
                WA_CustomScreen, scr,
                WA_Flags, WINDOWDRAG|WINDOWCLOSE|ACTIVATE,
                WA_IDCMP, CLOSEWINDOW,
                WA_Top, 20,
                WA_Height, scr->Height - 20,
                WA_Title, "Custom Window",
                WA_ScreenTitle, "Custom Screen",
                TAG_END))
            {
            IUtility->SNPrintf(buf, sizeof(buf), "CON://///WINDOW0x%lx", win); /* adds window pointer */
            if(file = IDOS->Open(buf, MODE_OLDFILE))
                {
                command = "echo \"CLI commands on a custom screen!\"";
                result = doCommand(command,file);
                command = "dir libs:";
                result = doCommand(command,file);
                command = "echo \"( Click CLOSE gadget, or type CTRL-C )\"";
                result = doCommand(command,file);
                Wait(1 << win->UserPort->mp_SigBit | SIGBREAKF_CTRL_C);
                IDOS->Close(file);    /* Closes the window too */
                }
            else IIntuition->CloseWindow(win);
            }
        IIntuition->CloseScreen(scr);
        }
 
    IDOS->Printf("\n*** SystemTest: Synchronous System call 'ask \"...Answer y now\"':\n");
    command = "ask \"Ready for Asynchronous demo? Answer y now (should return 5):\"";
    result = doCommand(command,NULL);
    checkResult(command,result);
 
 
    /* ASYNCHRONOUS SYSTEM() WITH ON-DEMAND AUTO/WAIT CON:
     */
    IDOS->Printf("\n*** SystemTest: Asynchronous startup of 'Sys:Utilities/Clock':\n");
    command = "SYS:Utilities/Clock";
    result = beginCommand(command);
    checkResult(command,result);
 
    IDOS->Printf("\n*** SystemTest: Asynchronous startup of 'avail':\n");
    command = "avail";
    result = beginCommand(command);
    checkResult(command,result);
 
    IDOS->Printf("\nSystemTest exiting. Close Clock and Autocon window when you wish.\n");
 
    IExec->CloseLibrary(IntuitionBase);
    return RETURN_OK;
}
 
 
/*
 * Synchronous external command (wait for return)
 * Uses your Input/Output unless you supply other handle
 * Result will be return code of the command, unless the System() call
 * itself fails, in which case the result will be -1
 */
LONG doCommand(UBYTE *command, BPTR other)
{
    struct TagItem stags[4];
 
    stags[0].ti_Tag = SYS_Input;
    stags[0].ti_Data = other ? other : Input();
    stags[1].ti_Tag = SYS_Output;
    stags[1].ti_Data = other ? NULL: Output();
    stags[3].ti_Tag = TAG_END;
    return IDOS->System(command, stags);
}
 
 
 
 
/*
 * Asynchronous external command started with its own autocon Input/Output
 * This routine shows use of the SYS_UserShell tag as well.
 * Result will only reflect whether System() call itself succeeded.
 * If System() call fails, result will be -1L
 * We are using -2L as result if our Open of CON: fails
 */
UBYTE *autocon="CON:0/40/640/150/Auto CON Window Opens if Needed/auto/close/wait";
LONG beginCommand(UBYTE *command)
{
    struct TagItem stags[5];
    BPTR file;
 
    if(file = IDOS->Open(autocon, MODE_OLDFILE))
        {
        stags[0].ti_Tag = SYS_Input;
        stags[0].ti_Data = file;
        stags[1].ti_Tag = SYS_Output;
        stags[1].ti_Data = NULL;
        stags[2].ti_Tag = SYS_Asynch;
        stags[2].ti_Data = TRUE;
        stags[3].ti_Tag = SYS_UserShell;
        stags[3].ti_Data = TRUE;
        stags[4].ti_Tag = TAG_END;
        return IDOS->System(command, stags);
        }
    else return(WINDOWFAIL);
}
 
 
/*
 * Demo routine outputs result of System
 */
VOID checkResult(UBYTE *command, LONG result)
{
    if(result == SYSTEMFAIL)
        IDOS->Printf("*** SystemTest: could not start process for command\n");
    else if(result == WINDOWFAIL)
        IDOS->Printf("*** SystemTest: can't open con: for command\n");
    else
        IDOS->Printf("*** SystemTest: command (if synchronous) returned %ld\n",result);
}