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