/* semi-formal class which manages libraries.
   Child classes of this class are loaded via reqire(ChildLib), at which point
  the library is loaded only if it was already loaded.  Also, a global variable
  is added (to the Actor Dictionary) based on its name -- see the require()
  method.  [E.g. #FooLib -> #FooLibClass$Inst ]
 */!!

inherit(Library, #ProvidedLibs, #(globalNameSymbol  /* the global name of this library as a Symbol */
lastError), 2, nil)!!

setClassVars(ProvidedLibs, #($LoadedLibs
$InPcall?))!!

now(class(ProvidedLibs))!!

/* 12/13/1995 11:11 */
/* PUBLIC
   If library has not been loaded, load it.  Return nil on failure,
   else an instance of the library.
*/
Def requireWithPath1(self, pathNameString | libSymbol, libInstance)
{
/* make up the global name of the library start with child's class name */
   libSymbol := asSymbol( name(class(self)) + "$Inst" ) ;
   
   if (libInstance := at($LoadedLibs, libSymbol))
   then ^libInstance   /* Success: already exists */
   endif ;

   /* let our Child do this itself */
   libInstance := provide( self, pathNameString ) ;
   if not( libInstance )
   then ^nil  /* failure */
   endif ;
   
   /* success -- add to libs */
   setGlobalSymbol( libInstance, libSymbol ) ;
   add( $LoadedLibs, libSymbol, libInstance ) ; /* remember it */
   add( Actor, libSymbol, libInstance ) ;       /* and make it global */
   
   ^libInstance /* Success: newly created */
}
!!

/* PRIVATE (TEST) */
Def $LoadedLibs(self)
{ ^$LoadedLibs }
!!

/* DEBUG */
Def nilifyLibs(self)
{ LoadedLibs := new( OrderedCollection, 20 );
  $LoadedLibs := new( Dictionary, 20 ) ;
}!!

/* FORMAL -- Children should define */
Def provide(self, path)
{
  errorBox( "ProvidedLibs Class",
            "Child Class forgot to implement provide() method"+"!"+"!"
          ) ;
}
!!

/* PUBLIC
   Free all libraries and remove them from local dictionary.
   Do NOT destroy global refs as may want them for debugging purposes.
*/
Def removeAllLibs(self | doItAgain libList)
{
  libList := reverse(LoadedLibs);
  do( libList, {using(lib) free(lib)} );
/*@@{
  doItAgain := #true;
  loop
  while doItAgain
  begin 
    doItAgain:=nil;
    do( libList,
      {using(lib) 
        if freeWithChecks(lib)  /* if any lib still exists; run it again *
          doItAgain:=#true;
        endif;
      }  /* close all dll's *
    );
  endLoop;
  loop
  while ((doItAgain := Call GetModuleHandle(asciiz("strlib.dll"))) <> 0)
  begin
    Call FreeLibrary( doItAgain ) ;
  endLoop;
}@@*/

  LoadedLibs := new( OrderedCollection, 20 );
  $LoadedLibs := new( Dictionary, 20 ) ;
}!!

/* PUBLIC 
** Child classes for which the dll name differs from this name-generation
** `rule' should override this method.
*/
Def require(self | theName)
{
  /* Make up a `pathname'; use className + .dll 
     Truncate the classname if too long.
   */
  theName := name( class(self) ) ; /* should get child's class name */

  if (size(theName) > 8)  /* Grody DOS limit! */
  then theName := subString(theName, 0, 8) ;
  endif ;
  
  ^requireWithPath( self, theName + ".dll" )  
}
!!

/* PUBLIC
   If library has not been loaded, load it.  Return nil on failure,
   else an instance of the library.
*/
Def requireWithPath(self, pathNameString | libSymbol, libInstance)
{
   /* display DLL being loaded */
   if CLIULibraryClass$Inst cand pathNameString
     writeTextToLogin(CLIULibraryClass$Inst,
       "Loading " + asLowerCase(pathNameString));
   endif;

/* make up the global name of the library start with child's class name */
   libSymbol := asSymbol( name(class(self)) + "$Inst" ) ;
   
   if (libInstance := at($LoadedLibs, libSymbol))
   then ^libInstance   /* Success: already exists */
   endif ;

   /* let our Child do this itself */
   libInstance := provide( self, pathNameString ) ;
   if not( libInstance )
   then ^nil  /* failure */
   endif ;
   
   /* success -- add to libs */
   setGlobalSymbol( libInstance, libSymbol ) ;
   add( $LoadedLibs, libSymbol, libInstance ) ; /* remember it */
   add( Actor, libSymbol, libInstance ) ;       /* and make it global */
   
   ^libInstance /* Success: newly created */
}
!!

now(ProvidedLibs)!!

/* PUBLIC */
Def clearError(self | prevError)
{
  prevError := lastError ;
  lastError := 0 ;

  ^prevError
}
!!

/* PRIVATE  */
Def defineStructs(self)
{
/* child classes which add stucture defs should override this method */
}
!!

/* PUBLIC */
Def fileName(self)
{ ^name }
!!

/* PRIVATE -- free Lib until it is *REALLY* free */
Def freeWithChecks(self | myName, myHandle)
{
  myName := asciiz( name ) ;

  if ( (myHandle := Call GetModuleHandle(myName)) <> 0 )
  then 
    Call FreeLibrary( myHandle ) ;
    ^#true;
  else
    ^nil;
  endif ;
}!!

/* PUBLIC */
Def globalName(self)
{ ^asString(globalNameSymbol) }
!!

/* PUBLIC */
Def globalSymbol(self)
{ ^globalNameSymbol }
!!

/* PUBLIC */
Def handle(self)
{ ^hLib }
!!

/* PRIVATE 
   Open and initialize a server library
*/
Def initialise(self, path | tempFile)
{
   /* Test for file exist */
   tempFile := setName(new(File), path);
   if not(open(tempFile,0)) then
      /* Warn user about the potential */
      new(ErrorBox, self, 
      "DLL not found : '"+path+"'. Possible incorrect working "+CR_LF+
      "directory in the MP/SLD's Program Item Properties.",  
      "MP/SLD", MB_OK bitOr MB_ICONHAND);
      ^nil;
   endif;
   close(tempFile);
   setName( self, path );
   addImportProcs( self ) ;
   defineStructs( self ) ;
   ^load( self ) ;
}
!!

/* PRIVATE 
   Open and initialize a server library
*/
Def initialize(self, path)
{
  /* Removed duplicate code by calling its clone directly */
  ^initialise(self, path);
}
!!

/* PUBLIC */
Def lastError(self)
{ ^lastError }
!!

/* Used before ALL pcall()'s */
Def pcallLock(self)
{
  if $InPcall?
  then
    /* @@ - printNewLine("Nested PCall"); */
    ^#ok /*@@*/
  else
    $InPcall? := #true ;
/*@@    wProcOn();  /* enable wProcNotify */
    ^#ok /* locked */
  endif ;
}
!!

/* Used after ALL pcall()'s */
Def pcallUNLock(self)
{
  $InPcall? := nil ;
/*@@  wProcOff(); /* MUST be AFTER "$InPcall? := nil" */
}
!!

/* New proc prototypes points have been added; get the entry points */
Def reLoad(self)
{ 
  keysDo( procs,
          {using(pr | addr) 
           addr := Call GetProcAddress(hLib, ordinals[pr]);
           setAddr(procs[pr], addr);
          }
         );
}
!!

/* PRIVATE  -- used only by class code */
Def setGlobalSymbol(self, aSymbol)
{ ^(globalNameSymbol := aSymbol) }
!!

/* ProvidedLibs Class Initialization Code */

$LoadedLibs := new( Dictionary, 11 ) ;