The problem: suppose you are writing a 3rd party library which uses an external DLL. This is the case when dynamic (explicit) DLL loading is preferable over static (implicit). Your library includes a load function which loads the DLL (by calling LoadLibrary and GetProcAddress functions) and now you have a dilemma – you can either warn the user that he should call the load function before using your library or you can call the load function yourself (in the initialization section of one of library units) so all a user should do is to place the DLL in the right location on disk. Both solutions have their pros and cons.
There is also an elegant solution that combines the best of both – you allow a user to load the DLL explicitly by calling the load function but if he did not done it the load function is called transparently when a DLL function is called first time. Here is an implementation example:
The DLL which exports 2 functions:
library DemoLib; type TLoadResult = (LOAD_ERROR = -1, LOAD_OK = 0, LOAD_ALREADYLOADED = 1); function GetValue(var Value: Integer): TLoadResult; begin Value:= 42; Result:= LOAD_OK; end; function GetIsValid(AValue: Integer; var IsValid: Boolean): TLoadResult; begin IsValid:= AValue = 42; Result:= LOAD_OK; end; exports GetValue, GetIsValid; {$R *.res} begin end.
The load function:
unit LoadLib; interface type TLoadResult = (LOAD_ERROR = -1, LOAD_OK = 0, LOAD_ALREADYLOADED = 1); type TGetValue = function(var Value: Integer): TLoadResult; TGetIsValid = function(AValue: Integer; var IsValid: Boolean): TLoadResult; var GetValue: TGetValue; GetIsValid: TGetIsValid; function LoadDemo(const Name: string = ''): TLoadResult; implementation uses Windows; const LibName = 'DemoLib.dll'; function GetValueError(var Value: Integer): TLoadResult; begin Result:= LOAD_ERROR; end; function GetIsValidError(AValue: Integer; var IsValid: Boolean): TLoadResult; begin Result:= LOAD_ERROR; end; var LoadResult: TLoadResult = LOAD_OK; function LoadDemo(const Name: string): TLoadResult; var LibHandle: THandle; begin if (LoadResult = LOAD_ALREADYLOADED) then begin Result:= LOAD_ALREADYLOADED; Exit; end; if Name = '' then LibHandle:= LoadLibrary(LibName) else LibHandle:= LoadLibrary(PChar(Name)); if (LibHandle <> 0) then begin @GetValue:= GetProcAddress(LibHandle, 'GetValue'); @GetIsValid:= GetProcAddress(LibHandle, 'GetIsValid'); if (@GetValue <> nil) and (@GetIsValid <> nil) then begin LoadResult:= LOAD_ALREADYLOADED; Result:= LOAD_OK; Exit; end; FreeLibrary(LibHandle); end; @GetValue:= @GetValueError; @GetIsValid:= @GetIsValidError; LoadResult:= LOAD_ERROR; Result:= LOAD_ERROR; end; function GetValueStub(var Value: Integer): TLoadResult; begin LoadDemo(); Result:= GetValue(Value); end; function GetIsValidStub(AValue: Integer; var IsValid: Boolean): TLoadResult; begin LoadDemo(); Result:= GetIsValid(AValue, IsValid); end; initialization @GetValue:= @GetValueStub; @GetIsValid:= @GetIsValidStub; end.
And the test application:
program TestLib; {$APPTYPE CONSOLE} uses SysUtils, LoadLib in 'LoadLib.pas'; procedure Test; var Value: Integer; begin if GetValue(Value) <> LOAD_OK then raise Exception.Create('Load Error'); Writeln(Value); end; begin try Test; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; ReadLn; end.
Initially GetValue and GetIsValid procedural variables reference GetValueStub and GetIsValidStub functions which call the DLL load function first; after the DLL was loaded these variables reference either the DLL functions if the loading was successful or the error functions GetValueError and GetIsValidError respectively if the loading failed.