/* Library defines a Windows dynamically-linked library
module.  All procs must be imported by ordinal. */!!

inherit(Object, #Library,
#(hLib,     /* handle to library */
name,     /* name of library */
ordinals, /* table of ordinals */
procs     /* dictionary of procs */), 2, nil)!!

now(class(Library))!!

/* Return an initialized library object. */
Def new(self)
{ ^init(new(self:Behavior));
}!!

now(Library)!!

/* Add a procedure name and args descriptor.
  Example: add(lib, #doRegression, 1, #(0 1 1 0)) */
Def add(self, sym, ret, args)
{ add(procs, sym, new(Proc));
  setArgs(procs[sym], ret, args);
}!!

/* propagated 'at's to the procs collection.  This
  makes the syntax:
  "pcall(procs(Lib)[#function], ...)" into
  "pcall(Lib[#function], ...)". */
Def at(self, index)
{ ^at(procs, index);
}!!

/* Free the global memory occupied by the library. */
Def free(self)
{ Call FreeLibrary(hLib);
}!!

/* Set up the procs method dictionary. */
Def init(self)
{ procs := new(IdentityDictionary, 16);
}!!

/* Load the library and look up all proc addresses.
  NOTE: you must add arg descriptors for all procs
  that you wish to use before calling load. */
Def load(self)
{ loadOrdinals(self);
  hLib := Call LoadLibrary(asciiz(name));
  if hLib < 32
  then ^nil;
  endif;
  /* now find addresses of all procedures */
  keysDo(procs,
  {using(pr | addr) addr := Call
    GetProcAddress(hLib, ordinals[pr]);
    setAddr(procs[pr], addr);
  });
}!!

/* Build the lookup table of names and ordinals from the .EXE module.
  Both the Resident and Non-Resident Name tables are scanned. */
Def loadOrdinals(self | exe, hdr, len, pname, struc, newEXE, blk)
{ hdr := new(Struct, 64);
  struc := new(Struct, 2);
  ordinals := new(IdentityDictionary, 16);
  exe := setName(new(File), name);
  open(exe, 0);
  checkError(exe);
  readInto(hdr, exe);
  moveTo(exe, newEXE := longAt(hdr, 0x3c)); /* find NEW EXE header */
  checkError(exe);
  readInto(hdr, exe); /* read new EXE header */
  blk :=
  {using(storeBlk) /* block to load name/ordinals */
    checkError(exe);
    /* format of name table.
      Format: (len byte) MODULE NAME 00 00
              (len byte) PROC NAME
              (ordinal)... 00 00
    */
    len := asInt(readChar(exe));
    move(exe, asLong(len+2));
    /* skip the module name and nulls */
    loop
    while (len := asInt(readChar(exe))) > 0
    begin pname := read(exe, len);
      readInto(struc, exe);
      eval(storeBlk, asSymbol(pname), asInt(wordAt(struc, 0)));
    endLoop;
  };

  /* load ordinals from non-resident name table */
  moveTo(exe, longAt(hdr, 44));
  eval(blk, {using(name, ord) ordinals[name] := ord;});

  /* load names from resident name table */
  moveTo(exe, newEXE+wordAt(hdr, 38));
  eval(blk, {using(name, ord) ordinals[name] := asString(name);});

  close(exe);
}!!

/* Return the procs variable, the dictionary of Procs. */
Def procs(self)
{ ^procs;
}!!

/* Set and return the library's name variable. */
Def setName(self, nam)
{ ^name := nam;
}!!
