While working on the Advantage Client Library (ace.pas) to make it compatible with the latest Delphi version I found out that following does not work for Linux:

function AdsConnect101; external ACE_LIB name 'AdsConnect101' {$IFDEF ADSDELPHI2010_OR_NEWER} delayed {$ENDIF};

„delayed“ is an unknwon keyword for the Linux compiler and even when commenting it out, the linker reports an „undefined reference to AdsConnect101“ error. So I had to come up with a different solution.
The dynamic loading works in all areas, but to rewrite every function of the ACE API to a construct like

facelib := LoadLibrary(ACE_LIB);
@fAdsConnect101:= GetProcAddress(facelib, PChar('AdsConnect101'));
Result := fAdsConnect101(pucConnectString, phConnectionOptions, phConnect);

is really no fun. So I decided to write a wrapper around, keeping loaded libraries and known procedure entry points in memory. The base for that approach is a new unit with only one single published function to get the procedure entry point.

unit libinterfaceu;
interface
  function jdGetProcAddress(ALibName: string; AProcName: string): Pointer;
implementation
end.

A list of loaded libraries is kept in a Generic TDictionary.

implementation
uses System.Generics.Collections, System.SysUtils {$IFNDEF LINUX},windows{$ENDIF};
type
  TjdLib = class
    //...
  end;
  TjdLibMgr  = TDictionary<String, TjdLib>;
var
  fLibMgr : TjdLibMgr;

function jdGetLibrary(ALibName: string): TjdLib;
var
  jdLib:TjdLib;
begin
  if not (fLibMgr.TryGetValue(ALibName, jdLib)) then begin
    jdLib := TjdLib.Create(ALibName);
    fLibMgr.Add(ALibName, jdLib);
  end;
  result := jdLib;
end;

initialization
  fLibMgr := TjdLibMgr.Create;
finalization
  FreeAndNil(fLibMgr);
end.

When a library is requested, the function first looks it up in the dictionary. If it’s not there, a new instance of TjdLib is being created and stored in the dictionary for later use. The library will be loaded in the constructor and unloaded in the destructor.

type
  TjdLib = class
    protected
      fHandle: HMODULE;
      fLibName: string;
    public
      constructor Create(ALibName: string); reintroduce;
      destructor Destroy(); override;
      property Handle: HMODULE read fHandle;
      property LibName: string read fLibName;
  end;

constructor TjdLib.Create(ALibName: string);
begin
  inherited Create();
  //...
  fLibName := ALibName;
  fHandle := LoadLibrary(PChar(ALibName));
  if fHandle=0 then raise Exception.Create('Unable to load Library '+ALibName);
end;

destructor TjdLib.Destroy;
begin
  //...
  if fHandle <> 0 then
    begin
      FreeLibrary(fHandle);
      fHandle := 0;
    end;
  inherited;
end;

The library wrapper will also get a dictionary of known procedure entry points and a function to return the requested pointer. Of course – fjdFunction will be set to a new instance of TjdFunction in the constructor and released in the destructor 😉

type
  TjdFunction = TDictionary<String, Pointer>;
  TjdLib = class
    protected
	  //...
      fjdFunction: TjdFunction;
    public
	  //...
      function jdGetProcAddress(AProcName: string): Pointer;
  end;
//...
function TjdLib.jdGetProcAddress(AProcName: string): Pointer;
begin
  if not (fjdFunction.TryGetValue(AProcName, Result)) then begin
    Result := GetProcAddress(fHandle, PChar(AProcName));
    fjdFunction.Add(AProcName, Result);
  end;
  if Result=nil then raise Exception.Create('Unable to load Function '+AProcName);
end;

So we come back to the implementation of the only published function contained in the unit.

interface
  function jdGetProcAddress(ALibName: string; AProcName: string): Pointer;
implementation
function jdGetProcAddress(ALibName: string; AProcName: string): Pointer;
var
  jdLib:TjdLib;
begin
  jdLib := jdGetLibrary(ALibName);
  Result := jdLib.jdGetProcAddress(AProcName);
end;
//...
end.

The usage is quite simple: Create a variale to hold the returning pointer, store the procedure entry point into that variable and call it afterwards.

var
  AdsConnect101 : function( pucConnectString: PAceChar;
                            phConnectionOptions: pADSHANDLE;
                            phConnect: pADSHANDLE ):UNSIGNED32; {$IFDEF WIN32}stdcall;{$ENDIF}{$IFDEF LINUX}cdecl;{$ENDIF}
begin
  //...
  AdsConnect101 := jdGetProcAddress(ACE_LIB, 'AdsConnect101');
  rval := AdsConnect101(PAceChar(AceString(sConnect)), nil, @hconn);

To complete the post, here’s the source code of the unit:

unit libinterfaceu;
interface
  function jdGetProcAddress(ALibName: string; AProcName: string): Pointer;
implementation
uses System.Generics.Collections, System.SysUtils {$IFNDEF LINUX},windows{$ENDIF};
type
  TjdFunction = TDictionary<String, Pointer>;
  TjdLib = class
    protected
      fHandle: HMODULE;
      fLibName: string;
      fjdFunction: TjdFunction;
    public
      constructor Create(ALibName: string); reintroduce;
      destructor Destroy(); override;
      function jdGetProcAddress(AProcName: string): Pointer;
      property Handle: HMODULE read fHandle;
      property LibName: string read fLibName;
  end;
  TjdLibMgr  = TDictionary<String, TjdLib>;
var
  fLibMgr : TjdLibMgr;

function jdGetLibrary(ALibName: string): TjdLib;
var
  jdLib:TjdLib;
begin
  if not (fLibMgr.TryGetValue(ALibName, jdLib)) then begin
    //load library and add it to the Mgr
    jdLib := TjdLib.Create(ALibName);
    fLibMgr.Add(ALibName, jdLib);
  end;
  result := jdLib;
end;

function jdGetProcAddress(ALibName: string; AProcName: string): Pointer;
var
  jdLib:TjdLib;
begin
  jdLib := jdGetLibrary(ALibName);
  Result := jdLib.jdGetProcAddress(AProcName);
end;

{ TjdLib }

constructor TjdLib.Create(ALibName: string);
begin
  inherited Create();
  fjdFunction := TjdFunction.Create;
  fLibName := ALibName;
  fHandle := LoadLibrary(PChar(ALibName));
  if fHandle=0 then raise Exception.Create('Unable to load Library '+ALibName);
end;

destructor TjdLib.Destroy;
begin
  FreeAndNil(fjdFunction);
  if fHandle <> 0 then
    begin
      FreeLibrary(fHandle);
      fHandle := 0;
    end;

  inherited;
end;

function TjdLib.jdGetProcAddress(AProcName: string): Pointer;
begin
  if not (fjdFunction.TryGetValue(AProcName, Result)) then begin
    Result := GetProcAddress(fHandle, PChar(AProcName));
    fjdFunction.Add(AProcName, Result);
  end;
  if Result=nil then raise Exception.Create('Unable to load Function '+AProcName);
end;

initialization
  fLibMgr := TjdLibMgr.Create;
finalization
  FreeAndNil(fLibMgr);
end.
Dynamically loading Functions in Delphi
Markiert in:        

Ein Gedanke zu „Dynamically loading Functions in Delphi

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.