2.9 NLM Startup

When the NLM first loads, an initialization function (_Prelude) performs the following tasks:

_Prelude is part of the prelude object file. If the environment is set up correctly, linking in the object file and calling the _Prelude function are both automatic and transparent. For more information about prelude object files, see Prelude Object Files.

2.9.1 Reentrant NLMs

A reentrant NLM can be loaded multiple times, but the server keeps only a single image of the NLM code in memory, rather than a code instance for each load.

Nonreentrant NLMs call the startup function _Prelude each time they are loaded. Reentrant NLMs, on the other hand, call _Prelude only on their initial load. They do not call _Prelude on reentrant loads.

To write a reentrant NLM, create a startup function that checks to see if the NLM has previously been loaded. On the initial load of the NLM, have your startup routine call _Prelude, passing _Prelude the parameters that the OS passed into your startup function. ( _Prelude calls the main function of your NLM.) On subsequent loads of the NLM, do not have your startup routine call _Prelude ; instead, have it handle the reentrant setup and then call the main function of the NLM itself.

In the following sample, the startup function is called MultipleLoadFilter. This function uses a flag called gAlreadyLoaded to indicate whether the current load of the NLM is the first load or a subsequent load.

Reentrant NLM

  typedef struct resource_list  
  {  
     struct resource_list   *next;  
     int                    screenHandle;  
  } ResourceList;  
   
  int            gAlreadyLoaded = 0;  
  int            gMainThreadGroupID;  
  ResourceList   *gResList = (ResourceList *) NULL;  
  typedef void (*PVF) ( void *);  
   
  LONG MultipleLoadFilter (  
     LoadDefStructPtr   NLMHandle,  
     ScreenStructPtr    initErrorScreenID,  
     BYTE               *cmdLineP,  
     BYTE               *loadDirPath,  
     LONG               uninitDataLen,  
     LONG               NLMFileHandle,  
     LONG               cdecl (*readFunc)())  
  {  
     int   myThreadGroupID;  
     if (!gAlreadyLoaded) /* first time through!!!!! */  
        return _Prelude(NLMHandle, initErrorScreenID, cmdLineP,  
        loadDirPath,uninitDataLen, NLMFileHandle, readFunc);  
     /* subsequent times through...*/  
     myThreadGroupID = SetThreadGroupID(gMainThreadGroupID);  
     BeginThreadGroup((PVF) main, NULL, NULL, cmdLineP);  
     SetThreadGroupID(myThreadGroupID);  
     return 0L;  
  }  
   
  void main(int argc,char *argv[])  
  {  
     int myThreadGroupID;  
     ...  
     char **argV;  
     if (!gAlreadyLoaded)  
     {  
        gMainThreadGroupID = GetThreadGroupID();  
        RenameThread(gMainThreadGroupID, "Sample-main");  
        gAlreadyLoaded = 1;  
        firstTime      = TRUE;  
        argV           = argv;  
        AtUnload(Cleanup);  
     }  
     else  
     {  
        char threadName[17+1+13];  
        sprintf(threadName, "Sample-#%d", gAlreadyLoaded);  
        myThreadGroupID = GetThreadGroupID();  
        RenameThread(myThreadGroupID, threadName);  
        gAlreadyLoaded++;  
        firstTime = FALSE;  
        argV      = args;  
     }  
     scrH = CreateScreen("Sample Reentrant NLM", 0);  
     if (!scrH)  
     {  
        ConsolePrintf("\nUnable to create screen...");  
        goto NoScreenExit;  
     }  
     LogScreenHandle(scrH);  
     SetCurrentScreen(scrH);  
     printf("\nSample Reentrant NLM: %d\n", gAlreadyLoaded);  
  ...  
  }
  

Your startup function must return zero. If it does not, the operating system displays the message "Attempt to reinitialize reentrant module FAILED" even if the NLM successfully loads.

When an NLM is loaded, its startup thread is an operating system thread, which usually doesn’t have CLIB context until _Prelude is called. In the example code above, the startup function MultipleLoadFilter calls _Prelude the first time the NLM is loaded, and _Prelude gives the thread CLIB context and creates a default thread group ID. In the example, main saves the default thread group ID in gMainThreadGroupID the first time the NLM is loaded. On subsequent loads of the NLM, MultipleLoadFilter gives the operating system thread CLIB context by setting the thread group ID using the ID stored in gMainThreadGroupID.

You specify that an NLM is reentrant when you link its object modules. In the linker definition file, use the REENTRANT option to specify that the NLM is reentrant. Use the START option to specify the function you want to use as the startup function. The following is an example of a WLINK definition file:

  form novell nlm ’Reentrant NLM’  
  name rentrant  
  option   reentrant  
  option   start = MultipleLoadFilter   
  option   case,nodefaultlibs  
  file     prelude  
  file     rentrant  
  import   @clib.imp
  

The REENTRANT option specifies that the NLM is reentrant. The START option specifies the name of the function to call when reentering the NLM. Directive files are discussed in Specifying a Linker Definition File.

For each instance that you use a reentrant NLM, you must load the NLM with the LOAD command. However, every instance of the reentrant NLM is unloaded with a single UNLOAD command. For this reason you must keep track of all of the resources that are used by all instances of the NLM and free all of them when the NLM is unloaded. (RENTRANT.C shows this by keeping a list of screens.)