home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 1: Amiga / FrozenFish-Apr94.iso / bbs / cbm / nduk-v37.lha / V37 / startups / WritingReentrantC < prev   
Text File  |  1991-11-19  |  12KB  |  351 lines

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