home *** CD-ROM | disk | FTP | other *** search
/ Fish 'n' More 1 / FishNMoreVol1.bin / more / programming / startups / writingreentrantc < prev    next >
Text File  |  1988-08-01  |  12KB  |  344 lines

  1.  
  2.  
  3.             C Tips and Tricks --- Writing Reentrant C Programs
  4.             ==================================================
  5.                          by Carolyn Scheppner
  6.  
  7.  
  8.  
  9.      The Workbench 1.3 RESIDENT command loads one copy of a command and
  10. adds it to DOS's resident list, making pre-loaded commands available for
  11. shared access by multiple processes.  The CLI and Workbench do not yet
  12. make use of resident commands, but rather load a new copy of a command
  13. each time it is called.  The Amiga Shell, however, looks for and uses
  14. commands which have been made resident.  Resident commands make efficient
  15. use of memory and reduce memory fragmentation.  In addition, the quick
  16. response of resident commands can significantly speed up command line
  17. work and system performance in general.
  18.  
  19.  
  20.      Unfortunately, most existing Amiga programs are not reentrant and will
  21. not work properly if one resident copy is used by more than one process,
  22. because they contain global and static variables which would be reused by
  23. code is entered.  This can be dangerous on successive calls to the code
  24. and disastrous if more than one process is using the code at the same time.  
  25.  
  26.  
  27.      The following small program, Test.c, demonstrates many of the problems
  28. which can keep C code from being reentrant.  The comments in the code are
  29. somewhat specific to programs linked with Astartup.obj and LIBRARY Amiga.lib,
  30. LC.lib, but the problems and questions are relevant for any type of Amiga
  31. development.  
  32.  
  33.  
  34.  
  35. /*===========================================================================*/
  36.  
  37. /* Test.c - A small program with big problems if made resident
  38.  * Use -v on LC2, link with Astartup.obj ... LIBRARY Amiga.lib, LC.lib
  39.  */
  40. #include <exec/types.h>
  41. #include <exec/memory.h>
  42. #include <libraries/dos.h>
  43. #include <workbench/startup.h>
  44.  
  45. /* If this is a global in startup code, we'll be in trouble if
  46.  *  Workbench ever starts using the resident list.
  47.  */
  48. extern struct WBStartup *WBenchMsg;
  49.  
  50. /* A global message string - won't be modified and can be shared if resident */
  51. UBYTE *usage = "Usage: test filename\n";
  52.  
  53. /* Initialized to 0 now - but will they be when next process uses the code? */
  54. LONG  file = 0L;
  55. ULONG IconBase = 0L;
  56. BOOL  FromWb = 0;
  57.  
  58.  
  59. main(argc,argv)
  60. int argc;
  61. char **argv;
  62.    {
  63.    FromWb = argc ? FALSE : TRUE;
  64.  
  65.    /* If this was a Workbench application, we'd be using extern WBenchMsg
  66.     * to get Workbench args.
  67.     * We'll assume CLI-only program for this small example.
  68.     */
  69.    if(FromWb)  exit(RETURN_FAIL);
  70.  
  71.    /* Any invocation of the code could fail at any one of these three points.
  72.     * In addition, if two processes use the code at once, each one's value
  73.     *  for file will wipe out the previous value.
  74.     */
  75.  
  76.    /* 1 - Did we get a filename ? */
  77.    if(argc != 2)   cleanexit(usage,RETURN_OK);  
  78.  
  79.    /* 2 - Can we open the file ?
  80.     *
  81.     * Argv array and buffer may be a reused area in the startup module.
  82.     * If another process is using this code, we may have overwritten
  83.     *   their argv array and buffer.
  84.     */
  85.    file = Open(argv[1],MODE_OLDFILE);
  86.    /* We may have just overwritten another process's file handle
  87.     *  with our own, or with 0 if we failed to open our file.
  88.     */
  89.    if(!file)  cleanexit("Can't open file\n",RETURN_FAIL);  
  90.       
  91.    /* 3 - Can we open a library ? */   
  92.    IconBase = OpenLibrary("icon.library",0);
  93.    /* This global is necessary, but can cause problems in cleanup */
  94.    if(!IconBase)  cleanexit("Can't open icon.library\n",RETURN_FAIL);
  95.  
  96.    Delay(100);
  97.  
  98.    /* Problems here if two processes using code:
  99.     *  Printf() may reference a global _stdout set up by startup code.
  100.     *  And argv buffer may have been reused by another process.
  101.     *  Whose argv and stdout will we get ?
  102.     */ 
  103.    printf("filename = %s\n", argv[1]);
  104.    
  105.    cleanup();
  106.    exit(RETURN_OK);
  107.    }
  108.  
  109. cleanexit(s,err)
  110. UBYTE *s;
  111. int err;
  112.    {
  113.    /* Another printf, possibly referencing a reused global _stdout */
  114.    if(*s)  printf(s);
  115.    cleanup();
  116.    exit(err);
  117.    }
  118.  
  119. cleanup()
  120.    {
  121.    /* What if we aborted before opening icon.library ?
  122.     * If another process opened it, we are going to Close it.
  123.     * It might even get expunged while the other process is using it.
  124.     */
  125.    if(IconBase)  CloseLibrary(IconBase);
  126.  
  127.    /* Whose file are we closing ?
  128.     * Or maybe our open failed and we zero'd out someone else's handle.
  129.     */
  130.    if(file)      Close(file);
  131.    }
  132.  
  133. /* end */
  134.  
  135. /*===========================================================================*/
  136.  
  137.  
  138.  
  139.                           Making Test.c Reentrant
  140.                           =======================
  141.  
  142.  
  143.      Despite all of its problems, Test.c can be made reentrant with a 
  144. bit of recoding and some new reentrant startup code -  Rstartup.asm.   
  145. Rstartup.asm is a new version of Astartup.asm which is reentrant if 
  146. no globals other than _DOSBase and _SysBase are referenced.  The classic
  147. Astartup code contained global variables for stdio handles and the WBStartup
  148. message, and also used a static memory area for the argv array and strings. 
  149. Rstartup dynamically allocates the argv memory, and only sets up the
  150. global variables when conditionally assembled as an Astartup replacement
  151. for non-reentrant code.  Rstartup.asm can be conditionally assembled to
  152. produce Astartup and TWstartup replacements as well as Resident-Only
  153. versions without globals.  All Rstartups are smaller than their predecessors
  154. due to code consolidation and allocation of the argv array and buffer.
  155.  
  156.  
  157.      Now that we have reentrant startup code for our program, we must modify
  158. the C code so that it is reentrant.  System library functions are reentrant.
  159. Amiga.lib csupport and exec_support functions are almost all reentrant.
  160. (The random number functions use a static seed variable.  The stdio functions
  161. such as printf, puts, and getchar reference the globals stdin and stdout
  162. and cannot be used in reentrant code.)  Our Test.c program only calls
  163. Amiga.lib functions, system functions, and its own subroutines.  If we
  164. modify Test.c according to the following rules, we can make it reentrant.
  165. Then we can link it with an Rstartup to produce a "pure" executable which
  166. can be made resident.
  167.  
  168.  
  169.                       Rules for Reentrant Code
  170.                       ========================
  171.  
  172.       - Make no direct or indirect (printf, etc) references to the
  173.         globals _stdin, _stdout, _stderr, _errno, or _WBenchMsg.
  174.  
  175.       - For stdio use either special versions of printf and getchar
  176.         that use Input() and Output() rather than _stdin and _stdout,
  177.         or use fprintf and fgetc with Input() and Output() file handles.
  178.  
  179.       - Workbench applications must get the pointer to the WBenchMsg
  180.         from argv rather than from a global extern WBenchMsg.
  181.  
  182.       - Use no global or static variables within your code.  Instead,
  183.         put all former globals in a dynamically allocated structure, and
  184.         pass around a pointer to that structure.  The only acceptable
  185.         globals are constants (message strings, etc) and global copies
  186.         of Library Bases to resolve Amiga.lib references.  Your code
  187.         must return all OpenLibrary's into non-global variables,
  188.         copy the result to the global library base only if successful,
  189.         and use the non-globals when deciding whether to Close any
  190.         opened libraries.  
  191.  
  192.  
  193. /*===========================================================================*/
  194.  
  195. Here's Test.c, recoded for reentrancy if linked with Rstartup.
  196.  
  197. /* Reentrant Test.c
  198.  * Use -v on LC2, link with Rstartup.obj ... LIBRARY Amiga.lib, LC.lib
  199.  */
  200.  
  201. #include <exec/types.h>
  202. #include <exec/memory.h>
  203. #include <libraries/dos.h>
  204. #include <workbench/startup.h>
  205.  
  206. /* No global extern for WBenchMsg - it will be passed in argv */
  207.  
  208. /* A global message string - won't be modified and can be shared if resident */
  209. UBYTE *usage = "Usage: test filename\n";
  210.  
  211. /* We still need global library bases, but will handle them differently */
  212. ULONG IconBase = 0L;
  213.  
  214.  
  215. /* All of our other old globals (file, FromWb) go in a structure we define.
  216.  * Structure also contains an entry for each library we will open.
  217.  * And variables for input or output filehandles if we need them.
  218.  * And pointer to WBStartup message for Workbench applications.
  219.  * You can also consolidate any other dynamic allocations such as buffers
  220.  *    by putting them in this structure.
  221.  */
  222.  
  223. struct  Variables
  224.    {
  225.    LONG   file;
  226.    BOOL   FromWb;
  227.    ULONG  iconbase;
  228.    LONG   output;
  229.    struct WBStartup *wbenchmsg;
  230.    };
  231.  
  232.  
  233.  
  234. main(argc,argv)
  235. int argc;
  236. char **argv;
  237.    {
  238.    /* Non-static variables within any function, including main(), are OK
  239.     *  in reentrant programs - they are local variables on process stack.
  240.     */
  241.    struct Variables *var;   /* a local pointer for our Variables strcuture */
  242.  
  243.  
  244.    /* First we allocate and clear our variables structure */
  245.    var = (struct Variables *) 
  246.       AllocMem(sizeof(struct Variables),MEMF_PUBLIC|MEMF_CLEAR);
  247.  
  248.    if(!var)
  249.       {
  250.       /* Exit here if we can't allocate var structure.
  251.        * Note use of fprintf() to Output() for error message.
  252.        */
  253.       fprintf(Output(),"Not enough memory\n");
  254.       exit(RETURN_FAIL);
  255.       } 
  256.  
  257.    /* Rest of code can assume that var structure has been allocated.
  258.     * Initialize the variables in our Variables structure.
  259.     */
  260.    var->FromWb = argc ? FALSE : TRUE;
  261.    if(var->FromWb)  var->wbenchmsg = (struct WBStartup *)argv;
  262.    
  263.    /* We'll still assume CLI-only program for this small example.
  264.     * But you've seen how to get the WBenchMsg pointer.
  265.     */
  266.    if(var->FromWb)  exit(RETURN_FAIL);
  267.  
  268.    /* We have no stdout global, so we'll store the Output() handle */
  269.    var->output = Output();
  270.    
  271.    /* Any invocation could still fail at any one of these three points.
  272.     * But now it's OK because our cleanup is safe. 
  273.     */   
  274.  
  275.    /* 1 - Did we get a filename ?  
  276.     *
  277.     * Argc is only on stack and is OK for reentrant programs.
  278.     * Note that we now pass the var ptr to all of our subroutines.
  279.     * I usually pass it as last (or only) argument.
  280.     */
  281.    if(argc != 2)   cleanexit(usage,RETURN_OK,var);  
  282.  
  283.    /* 2 - Can we open the file ?
  284.     *
  285.     * We initialize our local file variable.
  286.     * Argv is now reentrant because Rstartup dynamically allocates buffers.
  287.     */
  288.    var->file = Open(argv[1],MODE_OLDFILE);
  289.    if(!var->file)  cleanexit("Can't open file\n",RETURN_FAIL,var);  
  290.       
  291.    /* 3 - Can we open a library ? 
  292.     *
  293.     * Very Important -  OpenLibrary is now returned into our local variable.
  294.     *   We only initialize the global base if we succeed.
  295.     */
  296.    var->iconbase = OpenLibrary("icon.library",0);
  297.    if(!var->iconbase)  cleanexit("Can't open icon.library\n",RETURN_FAIL,var);
  298.    IconBase = var->iconbase;
  299.  
  300.    Delay(100);
  301.  
  302.  
  303.    /* We now use fprintf() instead of printf() to write to "stdout" */
  304.    fprintf(var->output,"filename = %s\n", argv[1]);
  305.    
  306.    /* Pass var to our cleanup subroutine, then exit(n) */
  307.    cleanup(var);
  308.    exit(RETURN_OK);
  309.    }
  310.  
  311. cleanexit(s,err,var)
  312. UBYTE *s;
  313. int err;
  314. struct Variables *var;
  315.    {
  316.    /* We fprintf our error message */
  317.    if(*s)  fprintf(var->output,s);
  318.  
  319.    /* Clean up all opened and allocated things including var structure */
  320.    cleanup(var);
  321.  
  322.    exit(err);
  323.    }
  324.  
  325. cleanup(var)
  326. struct Variables *var;
  327.    {
  328.    /* We know we have a var structure or we wouldn't be here */
  329.    
  330.    /* No chance of closing a library we didn't open - we test our local. */
  331.    if(var->iconbase)  CloseLibrary(var->iconbase);
  332.  
  333.    /* And we close OUR file if we got it open */
  334.    if(var->file)      Close(var->file);
  335.       
  336.    /* And last - Remember to free the Variables structure itself */
  337.    FreeMem(var,sizeof(struct Variables));
  338.    }
  339.  
  340. /* end */
  341.  
  342.  
  343. /*===========================================================================*/
  344.