Dynamic Dll Loading

just write whatever you want
Post Reply
gnif
Posts: 46
Joined: Fri Jan 05, 2007 9:12 am

Dynamic Dll Loading

Post by gnif »

I have been experimenting with a way to dynamically load a DLL and its entrypoints on the fly with error checking instead of implicit linking. I didn't like how if I was to do this, I would have to write 100's of stubs that called LoadLibrary and GetProcAddress... so I came up with this

Note: requires some assembler understanding

Code: Select all

unit uFreeglut;
interface

uses Windows, SysUtils, OpenGL;

const
        freeglut = 'freeglut.dll';

type
  TglutInit = procedure(var pargc: Integer; var argv: PChar); cdecl;
  TglutInitWindowPosition = procedure(x, y: Integer); cdecl;

var
  glutInit: TglutInit;
  glutInitWindowPosition: TglutInitWindowPosition;
  
implementation

procedure FailedToCall(Proc: PChar);
begin
  raise Exception.Create('Unable to find the procedure ''' + Proc + ''' in the dll ''' + freeglut + '''');
end;

//Jumps to the procedure in the dll after dynamically loading it
procedure DllFunc(Proc: PChar); cdecl;
var
  hDll: HINST;
  P   : Pointer;
begin
  //Get/Load the Dll
  hDll := GetModuleHandle(freeglut);
  if hDll = 0 then hDll := LoadLibrary(freeglut);
  P := GetProcAddress(hDll, Proc);      //Get the proc address
  if P = nil then FailedToCall(Proc)    //Check if the proc was found
  else
    begin
      asm
        push P                          //Push the proc address onto the stack
        add esp, 7*4                    //Restore ESP to our saved registers
        popad                           //Restore the registers
        jmp dword ptr [esp-(15*4)]      //Jump to the real function
      end;
      //We should never get here since we are jumping into the function
      raise Exception.Create('Fatal Error');
    end;
end;

{
  Lookup Table
  We use this instead of stubs because delphi likes to add asm to functions
  with params that we are forced to clean up, this way the asm is pure and
  we always know what state the stack is in.

  pushad                        //Save the registers
  push _XX                      //Push the name of the function onto the stack
  call DllFunc                  //Call the generic dll handler
  //only gets here if dll loading failed, DllFunc does not return.
  popad                         //Restore the registers and the stack
  ret                           //Return

  Each entry is exactly 14 bytes long
}
procedure CallTable();
const
  _000: PChar = 'glutInit';
  _001: PChar = 'glutInitWindowPosition';
asm
  pushad; push _000; call DllFunc; popad; ret;
  pushad; push _001; call DllFunc; popad; ret;
end;

initialization
  glutInit := Pointer(Integer(@CallTable) + (14*000));
  glutInitWindowPosition := Pointer(Integer(@CallTable) + (14*001));
end.
Yes, I am translating a freeglut header :)

The idea is to have one generic function that loads the library if it is not loaded, find the proc address, and if it doesnt exist then throw a detailed exception.

so, using this example... calling glutInit would follow this flow:

glutInit -> CallTable + 0 -> DllFunc -> Real glutInit

Each entry in CallTable saves the registers, pushes the function name onto the stack, and calls DllFunc, if DllFunc falls through because the dll could not be loaded, it restores the registers and returns. If DllFunc succeeds, then the stack is restored to the state it was in when CallTable was called, and then it jumps to the function inside the dll.

DllFunc will only return if the dll/proc wasnt found, and CallTable will only return if DllFunc returned.

Hope this all makes sence, it seems to work really well and saves heaps of duplicated code.

Share your thoughts/suggestions/ideas... I would be interested to know what others think.
gnif
Posts: 46
Joined: Fri Jan 05, 2007 9:12 am

Post by gnif »

Hrmm, just thinking about how to improve performance even more...

I could be possible to change the address of the variable "glutInit" to the real address in the dll after the first time it has been called to bypass the error checking. I suppose you could call it dynamic implicit linking.

edit:

Code re-writing of the call table should accomplish this... if the code is replaced with the following:

jmp addr
dw ????? //The entrypoint address
addr:
jmp dword ptr[eip-4] //absolute jump using the address on the last line

14 bytes should be more then enough to squeeze this into

edit edit:

Real code to use

Code: Select all

push $12345678 //Real address goes here
add esp,4
jmp dword [esp-4]
Thats exactly 12 bytes :)
gnif
Posts: 46
Joined: Fri Jan 05, 2007 9:12 am

Post by gnif »

I got it working :crazy:

Code: Select all

//Patches the call table with a jump to the supplied pointer
procedure PatchTable(Addr: Pointer);
var
  PatchRec: array[0..11] of Byte;
  Code    : Pointer;
  Old     : LongInt;
begin
  //Get the table address
  asm
    push eax
    mov  eax, esp
    add  eax, 13*4
    mov  eax, dword ptr [eax]
    sub  eax, 12
    mov  Code, eax
    pop  eax
  end;

  PatchRec[00] := $68;
  CopyMemory(@PatchRec[01], @Addr, SizeOf(Addr));
  PatchRec[05] := $83;
  PatchRec[06] := $C4;
  PatchRec[07] := $04;
  PatchRec[08] := $FF;
  PatchRec[09] := $64;
  PatchRec[10] := $24;
  PatchRec[11] := $FC;
  VirtualProtect(Code, SizeOf(PatchRec), PAGE_EXECUTE_READWRITE, @Old);
  CopyMemory(Code, @PatchRec[0], SizeOf(PatchRec));
  VirtualProtect(Code, SizeOf(PatchRec), Old, @Old);
end;

//Jumps to the procedure in the dll after dynamically loading it
procedure DllFunc(Proc: PChar); cdecl;
var
  hDll: HINST;
  P   : Pointer;
begin
  //Get/Load the Dll
  hDll := GetModuleHandle(freeglut);
  if hDll = 0 then hDll := LoadLibrary(freeglut);
  P := GetProcAddress(hDll, Proc);      //Get the proc address
  if P = nil then FailedToCall(Proc)    //Check if the proc was found
  else
    begin
      PatchTable(P);
      asm
        push P                          //Push the proc address onto the stack
        add esp, 7*4                    //Restore ESP to our saved registers
        popad                           //Restore the registers
        jmp dword ptr [esp-(15*4)]      //Jump to the real function
      end;
      //We should never get here since we are jumping into the function
      raise Exception.Create('Fatal Error');
    end;
end;
This gives dynamic linking with the speed of static linked librarys
madshi
Site Admin
Posts: 10749
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

I think this is a bit like MSVC++'s "delay loading"... :)
Post Reply