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.
Pingback:ADS and Delphi 10.2 Tokyo – Joachim Dürr softwareengineering