(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either express or implied.  
The entire risk as to the use of this information is assumed by the user.



Creating Multiple Processes With Re-entrant Code

by Michael Sinz

With the Amiga and its multitasking Exec, a whole new class of
programming solutions is available for your applications. The
Amiga allows you to divide up your program into multiple
processes that do their work independently and asynchronously.
For example, a spread-sheet program could have a background
process which performs recalculation of the cells.  This feature
would allow the application to utilize the time between
keystrokes to pre-calculate the value of the cell that is being
worked or other cells -- with minimal code and none of the fancy
tricks that would be required to accomplish the same thing on
other systems.

Of course, the Amiga's multitasking abilities also allow you to
easily build "multi- window" type applications, too.  This
includes products such as word processors that let you edit more
than one document at a time,  spread-sheets that let you have
more than one sheet open at a time, etc.  

For instance, a multi-window word processor can be written as a
basic "single" document handler.  By making the application
re-entrant (i.e., no global or static variables) multiple
documents can be opened by  simply adding a new process for each
context, or window, required.  This would give your application
the ability to start a document printing while the user works
onsome other document in a different window.

The example program listed below shows you how this can be done.
The example will open a window with the standard system gadgets
and a gadget that displays the task address.  If you click on the
gadget that  displays the task address, another window will open
with the same program running.  This one is a new process but no
new code has been loaded!

To accomplish this feat, the program will check to see if there
is a copy of itself already running whenever it is started.  If
there is, then the program signals the running code telling it to
start the new copy, and then exits. In this way, multiple copies
run using only one instance of the code.

The program listing is rather long.  To help you understand it,
here is a list of all the functions in the program.  There are
eight in all:

o  MyOpenWindow()		o  Do_More()
o  MyCloseWindow()		o  MyCreateProc()
o  Do_Example()			o  StartMain()
o  myMain()			o  main()

The program starts up a copy of itself with the function
StartMain() and then waits for messages from the Do_More()
function.  If a message arrives from Do_More(), then either the
user started another copy of the program from the CLI or the user
clicked on the task address gadget.  In either case, the function
MyCreateProc() is called to restart the program without loading
up any new code.

The functions myMain(),  Do_Example(),  MyOpenWindow() and
MyCloseWindow() just serve as a test case here - you could drop
in code of your own instead.  Don't forget, your code must be
re-entrant.  Variables which are not dynamically allocated at
run-time are not allowed.

The functions Do_More(),  StartMain() and MyCreateProc() are the
ones that do the multi-processing work.  Do_More() sends the
signal that the user wants to start the code another time.
StartMain() waits for the signal and, if received, it calls the
function MyCreateProc().  StartMain() is also responsible for
keeping track of how many processes the user has started - it
will not exit until all the processes are finished.  The
MyCreateProc() function actually does the work of beginning a new
process using the same re-entrant code.
 
Note that the program runs another copy of itself as a new
process, not just as a task, so you can make calls to dos.library
in your code.  If the program only restarted itself as a
task,then the using dos.library routines (such as printf() )
would cause a system crash.


/*
 * Example of how to start multiple processes from within the
 * same executable...
 *
 * This example assumes ANSI C and has been written and tested
 * with Lattice C 5.02 and 5.04...
 */

/*
 * MakeFile used to make MultiProc...
 *

#
# MakeFile for MultiProc
#

CFLAGS= -b1 -cfirstq -ms0 -rr1 -v -w

OBJS=	MultiProc.o

LIBS=	LIB:lcsr.lib

.c.o:
	@LC $(CFLAGS) $*

MultiProc: $(OBJS)
	@BLink FROM LIB:c.o $(OBJS) TO MultiProc LIB $(LIBS) SMALLDATA SMALLCODE

 *
 *
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <graphics/gfx.h>
#include <graphics/regions.h>
#include <graphics/rastport.h>
#include <devices/timer.h>

#include <proto/all.h>

#include <string.h>
#include <stdio.h>

/* Lattice method of removing CTRL-C checking... */
int CXBRK(VOID) { return(0); }

/*
 * These library bases are shared...
 *
 * Note that you can not share the IEEE math libraries as they
 * need to set up some state information on a per-task basis.
 */
extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase       *GfxBase;

/*
 * Structure that is a fake SegList for the code.  It will contain
 * a single JMP instruction to the code we wish to start.
 */
struct CodeHdr
{
    ULONG SegSize;
    ULONG NextSeg;
    UWORD JumpInstr;
    APTR  Function;
};

/*
 * Convert a normal pointer (CPTR or APTR) into a BCPL pointer (BPTR)
 */
#define TO_BPTR(x) ((BPTR)(((ULONG)(x)) >> 2))

/*
 * My message type for sending message between my processes and
 * the main process...
 */
struct MyMSG
{
struct Message msg;
       BPTR    lock;        /* A directory lock for the new process */
       APTR    UserData;    /* Data that is passed to the process   */
       SHORT   type;        /* My type recognition... */
};

#define MSG_TYPE_NEW 1
#define MSG_TYPE_DIE 2

/*
 * Constant task and port names...  These should be unique for each
 * of your applications...
 */
static char MyMessagePort[]="Sample Multi-Proc Port";
static char MyProcessName[]="Sample Process Name";

/*
 * This function will return TRUE if it was able to send a message
 * to start another task...
 *
 * The lock should be the default directory you wish the new process
 * to have.  If it is NULL, it will get the starting directory...
 * The lock will be DupLock()ed and the copy will be sent to the
 * new process.  So, the lock you pass to this function can be
 * UnLock()ed after you call it.
 *
 * UserData is a 32-bit value that can be whatever you define...
 */
SHORT Do_More(BPTR lock,APTR UserData)
{
register struct MyMSG   *msg;
register struct MsgPort *port;
register struct MsgPort *MyPort;
register        SHORT   flag=FALSE;

    if (MyPort=CreatePort(NULL,NULL))
    {
        if (msg=AllocMem(sizeof(struct MyMSG),MEMF_PUBLIC|MEMF_CLEAR))
        {
            msg->msg.mn_ReplyPort=MyPort;
            msg->msg.mn_Length=sizeof(struct MyMSG);
            msg->UserData=UserData;
            msg->lock=lock;
            msg->type=MSG_TYPE_NEW;

            /*
             * Once we find the port, we don't want it to go away
             * so we prevent anyone else from running...
             *
             * Since this only happens during the starting of a process
             * this is a low overhead Forbid()
             */
            Forbid();
            if (port=FindPort(MyMessagePort)) /* Ok, send the message... */
            {
                PutMsg(port,(struct Message *)msg);
                WaitPort(MyPort);
                GetMsg(MyPort);
                flag=TRUE;
            }
            Permit();
            FreeMem(msg,(ULONG)(msg->msg.mn_Length));
        }
        DeletePort(MyPort);
    }
    return(flag);
}

/**********************************************************************/

/*
 * Simple example program that opens a window with a gadget
 * If the gadget is pressed, it sends the signal to start another
 * copy of the program.  If the close gadget is pressed, the window
 * shuts down.
 *
 * Note that any code that is to be multi-started from within the
 * the same code MUST be re-entrant.  That means that global and
 * static values *MUST* be constants and not be changed...
 *
 * The text in the gadget displays the process ID (the process
 * structure pointer as found by FindTask(NULL)
 */

#define MORE_GADGET 1

/*
 * This is the stack size...  Since you are a process and may do
 * AmigaDOS calls, the ABSOLUTE minimum would be around 2000.
 * A good recommended minimum is 4000...
 */
#define EXAMPLE_STACK 4000L

/*
 * This is the priority that the started code will run at...
 */
#define EXAMPLE_PRI 0

/*
 * Some global strings...
 */
static char WindowTitle[10]="MultiProc";

static char fontnam[11]="topaz.font";
static struct TextAttr TOPAZ80={fontnam,TOPAZ_EIGHTY,0,0};

struct MyGadget
{
struct Gadget    Gadget;
struct IntuiText IntuiText;
struct Image     Image;
};

/*
 * Note that to become reentrant we must allocate the gadget
 * structures rather than use global structures since gadgets
 * are data elements that are modified as they are used by
 * Intuition.  Items such as the actual text strings and border
 * vectors and image data can be shared as they are read-only
 */
struct Window *MyOpenWindow(char *MyID)
{
register struct Window    *w=NULL;
register struct MyGadget  *gad;
         struct NewWindow nw;

    if (gad=AllocMem(sizeof(struct MyGadget),MEMF_PUBLIC|MEMF_CLEAR))
    {   
        nw.LeftEdge=0;
        nw.TopEdge=22;
        nw.Width=200;
        nw.Height=50;
        nw.DetailPen=3;
        nw.BlockPen=1;
        nw.IDCMPFlags=CLOSEWINDOW|GADGETUP;
        nw.Flags=WINDOWCLOSE|WINDOWDRAG|WINDOWDEPTH|SIMPLE_REFRESH|NOCAREREFRESH;
        nw.FirstGadget=&(gad->Gadget);
        nw.CheckMark=NULL;
        nw.Title=WindowTitle;
        nw.Screen=NULL;
        nw.BitMap=NULL;
        nw.Type=WBENCHSCREEN;

        gad->Gadget.LeftEdge=4;
        gad->Gadget.TopEdge=11;
        gad->Gadget.Width=nw.Width-8;
        gad->Gadget.Height=nw.Height-13;
        gad->Gadget.Flags=GADGHCOMP|GADGIMAGE;
        gad->Gadget.Activation=RELVERIFY;
        gad->Gadget.GadgetType=BOOLGADGET;
        gad->Gadget.GadgetRender=(APTR)&(gad->Image);
        gad->Gadget.GadgetText=&(gad->IntuiText);
        gad->Gadget.GadgetID=MORE_GADGET;

        gad->IntuiText.TopEdge=(gad->Gadget.Height - 8) >> 1;
        gad->IntuiText.FrontPen=0;
        gad->IntuiText.DrawMode=JAM1;
        gad->IntuiText.ITextFont=&TOPAZ80;
        gad->IntuiText.IText=MyID;
        gad->IntuiText.LeftEdge=(gad->Gadget.Width-
                             IntuiTextLength(&(gad->IntuiText))) >> 1;

        gad->Image.Width=gad->Gadget.Width;
        gad->Image.Height=gad->Gadget.Height;
        gad->Image.PlaneOnOff=3;

        /*
         * So, if the window opens, we use the UserData field to
         * keep track of the memory we allocated for the gadget
         * and other related items...
         */
        if (w=OpenWindow(&nw)) w->UserData=(UBYTE *)gad;
        else FreeMem(gad,sizeof(struct MyGadget));
    }
    return(w);
}

/*
 * We need to not only close the window but also free the memory
 * that we had allocated.  Since the UserData field of the window
 * structure points to this data we just need to extract that pointer
 * so that we may free the memory after the window is closed...
 */
VOID MyCloseWindow(struct Window *w)
{
register UBYTE *gad;

    if (w)
    {
        gad=w->UserData;
        CloseWindow(w);
        if (gad) FreeMem(gad,sizeof(struct MyGadget));
    }
}

/*
 * This is the example code
 *
 * The UserData that is passed comes from the startup message...
 *
 * Note that since this code could be running multiple times, it
 * must not count on ANY global variables other than CONSTANTS.
 * In other words, it must be re-entrant.
 */
VOID Do_Example(APTR UserData)
{
register struct Window       *w;
register struct IntuiMessage *msg;
register struct Gadget       *gad;
register        SHORT        flag=TRUE;
register        char         *t;
register        char         c;
register        ULONG        task;
                char         MyID[32];

    strcpy(MyID,"I am 0x00000000");
    task=(ULONG)FindTask(NULL);
    t=&(MyID[strlen(MyID)]);
    while (task)                   /* Generate the HEX string */
    {                              /* for the task's address. */
        c=(task & 15);
        task=task >> 4;
        *--t+=c + (c > 9 ? 7 : 0);
    }

    if (UserData) strcat(MyID," +");

    if (w=MyOpenWindow(MyID))
    {
        while (flag)
        {
            WaitPort(w->UserPort);
            while (msg=(struct IntuiMessage *)GetMsg(w->UserPort))
            {
                if (msg->Class==CLOSEWINDOW) flag=FALSE;
                else if (msg->Class==GADGETUP)
                {
                    gad=(struct Gadget *)msg->IAddress;
                    switch (gad->GadgetID)
                    {
                    case MORE_GADGET: Do_More(NULL,(APTR)1L);
                                      break;
                    }
                }
                ReplyMsg((struct Message *)msg);
            }
        }
        MyCloseWindow(w);
    }
}

/**********************************************************************/

/*
 * This is the font-end to the code that you have....
 *
 * When your code is called, your current directory will be
 * that of where the main code was called from.
 *
 * Note that __saveds is much like a GetA4()
 */
static VOID __saveds myMain(VOID)
{
register struct MyMSG    *msg;
register struct MsgPort  *port;
register        BPTR     lock;

    /*
     * First, we need to get the startup-message...
     */
    port=&(((struct Process *)FindTask(NULL))->pr_MsgPort);
    WaitPort(port);
    msg=(struct MyMSG *)GetMsg(port);

    /*
     * Now, CD to the startup directory...
     */
    lock=CurrentDir(msg->lock);

    /*
     * Do your code here...
     * You are a process with a current directory but no CIS/COS
     * Since you are a process, you may do disk I/O
     *
     * We also pass the user data field from the startup message...
     */
    Do_Example(msg->UserData);

    /*
     * CD back to where we started...
     */
    CurrentDir(lock);

    /*
     * Now to signal that we are done.  This lets the main task
     * know that we have finished and he may exit.  The main task
     * keeps track of how many tasks are running and when the last
     * one exits, he may then exit...
     *
     * If we can't get this memory, we are in BIG trouble...
     * If we can't get this memory no one will know we are done...
     */
    Forbid();
    ReplyMsg((struct Message *)msg);

    /*
     * We don't Permit() here since we wish to drop out of this
     * task berfore the message gets delivered since the message
     * tells the main task that we are done...
     */

    /*
     * Since this task then is gone, the Forbid() is thus no
     * longer in effect...
     */
}

/*
 * This is my little process start code that sends a message to
 * the process if it got started...
 */
static struct MsgPort *MyCreateProc(struct MsgPort *MyPort,BPTR seglist,BPTR lock,APTR UserData)
{
register struct MsgPort *proc=NULL;
register struct MyMSG   *msg;

    if (msg=AllocMem(sizeof(struct MyMSG),MEMF_PUBLIC|MEMF_CLEAR))
    {
        msg->msg.mn_ReplyPort=MyPort;
        msg->msg.mn_Length=sizeof(struct MyMSG);
        msg->type=MSG_TYPE_DIE;
        msg->UserData=UserData;

        if (proc=CreateProc(MyProcessName,EXAMPLE_PRI,seglist,EXAMPLE_STACK))
        {
            /*
             * Now that the process is running, we need to make a
             * copy of the lock and send the message...
             */
            msg->lock=DupLock(lock);
            PutMsg(proc,(struct Message *)msg);
        }
        else
        {
            /*
             * Since there was no process, we need to deallocate
             * the message we were goin to send...
             */
            FreeMem(msg,(ULONG)(msg->msg.mn_Length));
        }
    }
    return(proc);
}

/*
 * The reason argc is being passed is to give us an indication of
 * if we are a CLI-started program.  For this example, we will
 * display different state information if started from a CLI...
 *
 * Note that the "if (argc) printf(..." statements are just for
 * this example and normally you would not do these...
 */

#define EXAMPLE 1

static VOID StartMain(int argc)
{
register struct MsgPort *port;
register struct CodeHdr *p;
register struct MyMSG   *msg;
register        BPTR    b_pointer;
register        BPTR    StartingLock;
register        SHORT   count=1;

    /*
     * Get a copy of the current directory lock...
     *
     * This lock will be passed to the first process that
     * starts...
     */
    b_pointer=CurrentDir(NULL);
    StartingLock=DupLock(b_pointer);
    CurrentDir(b_pointer);

    /*
     * Allocate the fake seglist.  Since it must be longword
     * aligned, it can not be static in the code nor can it be
     * allocated on the stack (without assembly code, that is)
     */
    if (p=AllocMem(sizeof(struct CodeHdr),MEMF_PUBLIC|MEMF_CLEAR))
    {
        /*
         * 0x4EF9 is the 680x0 JMP instruction...
         */
        p->SegSize=sizeof(struct CodeHdr);
        p->JumpInstr=0x4EF9;
        p->Function=(APTR)myMain;   /* The code to start... */

        /*
         * We need a BPTR to the NextSeg field of the SegList
         * for CreateProc()...  So, this is what we make...
         */
        b_pointer=TO_BPTR(&p->NextSeg);

        /*
         * Ok, now for some critical sections...
         * We need to check if we are running somewhere else.
         * When doing this check, we need to make sure that someone
         * does not slip in at that time so the whole thing is done
         * in a Forbid()
         */
        Forbid();
        if (Do_More(StartingLock,NULL))
        {

#ifdef EXAMPLE
            Permit();
            if (argc) printf("Found another MultiProc running...\n");
            Forbid();
#endif EXAMPLE

        }
        else if (port=CreatePort(MyMessagePort,NULL))
        {
            if (MyCreateProc(port,b_pointer,StartingLock,NULL))
            {
                while (count)
                {
                    /*
                     * We now are safe from intervention...
                     * So, we let the system run...
                     */
                    Permit();

#ifdef EXAMPLE
                    if (argc) printf("Now at %d processes...\n\nWaiting for message...\n",count);
#endif EXAMPLE

                    WaitPort(port);

                    /*
                     * In the following section of code, we need to balance
                     * the Permit()/Forbid() pairs...  This Forbid() is for
                     * just this purpose.  As you can see, it is imediate
                     * Permit() if needed...
                     */
                    Forbid();

                    while (msg=(struct MyMGS *)GetMsg(port))
                    {
                        /*
                         * Ok, we are in now, and have our public port
                         * and we know we are the only ones with it so
                         * we now will let the system run...
                         */
                        Permit();

                        switch (msg->type)
                        {
                        case MSG_TYPE_NEW:
                             /*
                              * Check if we were given a starting lock.  If not,
                              * we set it to the same as the first starting lock...
                              */
                             if (!(msg->lock)) msg->lock=StartingLock;

                             /*
                              * Try to start a process with the values
                              * passed in the starting message...
                              */
                             if (MyCreateProc(port,b_pointer,msg->lock,msg->UserData))
                             {
                                 /*
                                  * Count the fact that the process has
                                  * started...
                                  */
                                 count++;
                             }
                             /*
                              * Reply the message to the person
                              * asking to create this...
                              */
                             ReplyMsg((struct Message *)msg);

#ifdef EXAMPLE
                             if (argc) printf("\nStarted a process:\t");
#endif EXAMPLE

                             break;

                        case MSG_TYPE_DIE:
                             /*
                              * Free the directory lock that we
                              * gave the to the process that just
                              * died...
                              */
                             if (msg->lock) UnLock(msg->lock);

                             /*
                              * Count the fact that another process
                              * has died...
                              */
                             count--;

                             /*
                              * Since we sent this message, we will
                              * have to deallocate it...
                              */
                             FreeMem(msg,(ULONG)(msg->msg.mn_Length));

#ifdef EXAMPLE
                             if (argc) printf("\nA process ended:\t");
#endif EXAMPLE

                             break;
                        }

                        /*
                         * At this point, we don't want anyone to send a message
                         * to us.  This is because we may be in the middle of
                         * cleaning up and going away.  So, with this Forbid()
                         * we prevent any new messages until after we check for
                         * them and also check to see if we are about to exit.
                         * If we do exit, the message port is deleted before
                         * the next Permit() and thus we are safe from any
                         * stray messages...
                         */
                        Forbid();
                    }
                }
            }
            DeletePort(port);

#ifdef EXAMPLE
            Permit();
            if (argc) printf("Exiting...\n");
            Forbid();
#endif EXAMPLE

        }
        /*
         * Now that we have either started and exited or sent the message to
         * the other running MultiProc, we may let others back in...
         */
        Permit();

        FreeMem(p,p->SegSize);
    }

    if (StartingLock) UnLock(StartingLock);
}

/*
 * The main code should do whatever is needed to initialize the few
 * global parameters...  For this example, we don't even need GfxBase
 * but it is here to just keep us company...
 */
VOID main(int argc,char *argv[])
{
    /*
     * Ok, so we need to startup...  Now open the libraries that
     * everyone is going to use...  (Note: Not the IEEE math
     * libraries.  See above.)
     *
     * Also, setup the copy of the current directory lock
     */
    if (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",33L))
    {
        if (GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",33L))
        {
            /*
             * This is the main code that starts the multiple tasks
             */
            StartMain(argc);

            /*
             * Now clean up...
             */
            CloseLibrary((struct Library *)GfxBase);
        }
        CloseLibrary((struct Library *)IntuitionBase);
    }
}

