Delayed dynamic DLL loading

2

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.

Advertisements

Operator Overloading in Delphi and C++

1

Let us consider the following toy problem: implement a user-defined integer type MyInt which is assignment compatible with built-in signed and unsigned integers and supports addition. We assume here that all integers are 32-bit. Assigning unsigned integers above 0x7fffffff to MyInt and assigning negative MyInt values to unsigned integers should raise exceptions.

To begin with we have a record (Delphi)

type
  MyInt = record
  private
    FValue: Integer;
  end;

or structure (C++)

struct MyInt{
private:
  int FValue;
};

By convention we use explicit conversion if a conversion can raise exception and implicit conversion otherwise. The Delphi solution is straightforward:

unit MyInts;

interface

uses SysUtils;

type
  MyInt = record
  private
    FValue: Integer;
  public
    class operator Implicit(AValue: MyInt): Integer;
    class operator Explicit(AValue: MyInt): Cardinal;
    class operator Implicit(AValue: Integer): MyInt;
    class operator Explicit(AValue: Cardinal): MyInt;
    class operator Add(A, B: MyInt): MyInt;
  end;

implementation

class operator MyInt.Explicit(AValue: MyInt): Cardinal;
begin
  if AValue.FValue < 0 then
    raise Exception.Create(IntToStr(AValue.FValue));
  Result:= AValue.FValue;
end;

class operator MyInt.Explicit(AValue: Cardinal): MyInt;
begin
  if AValue > $7FFFFFFF then
    raise Exception.Create(IntToStr(AValue));
  Result.FValue:= AValue;
end;

class operator MyInt.Implicit(AValue: MyInt): Integer;
begin
  Result:= AValue.FValue;
end;

class operator MyInt.Implicit(AValue: Integer): MyInt;
begin
  Result.FValue:= AValue;
end;

class operator MyInt.Add(A, B: MyInt): MyInt;
begin
  Result.FValue:= A.FValue + B.FValue;
end;

end.

With C++ we need more to take into account. First C++ supports copy initialization

  MyInt M = 1;

which is implemented by a constructor while a plain assignment requires either a conversion operator

  MyInt M;
  M = 1;

or an assignment operator

  MyInt M = 1;
  MyInt M1;
  M1 = M;

If we do not declare an assignment operator C++ implicitly provides the one which implements assignment by shallow coping; it is enough for our problem.

Second we have 2 possible ways to implement the addition operator – as a member function or as a plain function. The member function treats the operands of a binary operation asymmetrically – the first operand is passed as this pointer (different from Delphi which implements the operator member function as static) – and rarely used. More common is to implement a binary operation by a plain function which should be declared as friend in MyInt structure to enable access to the private field FValue.

Third the conversion operators in C++ are “one-way”. We can define the conversion operator FROM MyInt; conversion TO MyInt is implemented by a constructor.

There are more language details a C++ programmer should have in mind while implementing operator overloading; you will discover them if you start experimenting with the code. I leave them out of the article.

Putting it all together we are coming to the following code:

(MyInts.hpp)

#ifndef MYINTS_HPP_INCLUDED
#define MYINTS_HPP_INCLUDED

struct MyInt{
private:
    int FValue;
public:
    MyInt(int A = 0);
    MyInt(unsigned int A);
    MyInt(const MyInt& A);
//    MyInt operator=(MyInt A);

    operator int();
    operator unsigned int();
    friend MyInt operator+(const MyInt& A, const MyInt& B);
};

#endif // MYINTS_HPP_INCLUDED

(MyInts.cpp)

#include <iostream>
#include "MyInts.hpp"

using namespace std;

MyInt::MyInt(int A){
    cout << "hello from MyInt(int) ctor" << endl;
    FValue = A;
}

MyInt::MyInt(unsigned int A){
    cout << "hello from MyInt(unsigned int A) ctor" << endl;
    if (A > 0x7fffffff){
        cout << "throwing " << A << endl;
        throw(A);
    }
    FValue = A;
}

MyInt::MyInt(const MyInt& A){
    cout << "hello from MyInt(const MyInt&) ctor" << endl;
    FValue = A.FValue;
}

/*
MyInt MyInt::operator=(MyInt A){
    cout << "hello from assignment operator" << endl;
    FValue = A.FValue;
    return *this;
}
*/

MyInt::operator int(){
    cout << "hello from int conversion operator" << endl;
    return FValue;
}

MyInt::operator unsigned int(){
    cout << "hello from unsigned int conversion operator" << endl;
    if (FValue < 0){
        cout << "throwing " << FValue << endl;
        throw(FValue);
    }
    return FValue;
}

MyInt operator+(const MyInt& A, const MyInt& B){
    cout << "hello from addition operator" << endl;
    MyInt Result = A.FValue + B.FValue;
    return Result;
}