Consuming Delphi interfaces in Dephi and C++

An object-oriented DLL written in Delphi exports functions with parameters of interface type. A code consuming such DLL can use an interface reference directly, but a better way is to write a wrapper type which encapsulates an interface reference. Let us consider as an example a simple interface IBytes designed to work with byte arrays:

unit ITypes;

interface

type
  IBytes = interface
    function GetLength: Integer; stdcall;
    procedure Append(B: Byte); stdcall;
    procedure CopyTo(var C: IBytes); stdcall;
  end;

implementation

end.

The IBytes interface is implemented by the TByteObject class:

unit ByteObjects;

interface

uses ITypes;

type
  TByteObject = class(TInterfacedObject, IBytes)
    FBytes: array of Byte;
    function GetLength: Integer; stdcall;
    procedure Append(B: Byte); stdcall;
    procedure CopyTo(var C: IBytes); stdcall;
  end;

implementation

{ TByteObject }

procedure TByteObject.Append(B: Byte);
begin
  SetLength(FBytes, Length(FBytes) + 1);
  FBytes[Length(FBytes)]:= B;
end;

procedure TByteObject.CopyTo(var C: IBytes);
var
  Instance: TByteObject;

begin
  Instance:= TByteObject.Create;
  SetLength(Instance.FBytes, Length(FBytes));
  Move(Pointer(FBytes)^, Pointer(Instance.FBytes)^, Length(FBytes));
  C:= Instance;
end;

function TByteObject.GetLength: Integer;
begin
  Result:= Length(FBytes);
end;

end.

We implement the IBytes interface in DLL:

library TestDLL;

uses
  SysUtils,
  Classes,
  ITypes in 'ITypes.pas',
  ByteObjects in 'ByteObjects.pas';

procedure GetInterface(var I: IBytes); stdcall;
begin
  I:= TByteObject.Create;
end;

exports
  GetInterface;

{$R *.res}

begin
end.

The DLL exports the single function which creates instances of TByteObject class.

To consume the IBytes interface in Delphi we use an advanced record type to avoid the unnecessary overhead of Delphi classes:

unit ByteWrappers;

interface

uses Windows, ITypes;

type
  TGetInterface = procedure(var I: IBytes); stdcall;

var
  GetInterface: TGetInterface;

function LoadDll(const Name: string): Boolean;

type
  TMyBytes = record
  private
    FBytes: IBytes;
  public
    procedure Append(B: Byte);
    procedure CopyTo(var C: TMyBytes);
    function GetLength: Integer;
    procedure Free;
  end;

implementation

{ TMyBytes }

procedure TMyBytes.Append(B: Byte);
begin
  if (FBytes = nil) then GetInterface(FBytes);
  FBytes.Append(B);
end;

procedure TMyBytes.CopyTo(var C: TMyBytes);
begin
  if (FBytes = nil) then C.FBytes:= nil
  else FBytes.CopyTo(C.FBytes);
end;

procedure TMyBytes.Free;
begin
  FBytes:= nil;
end;

function TMyBytes.GetLength: Integer;
begin
  if (FBytes = nil) then Result:= 0
  else Result:= FBytes.GetLength;
end;

function LoadDll(const Name: string): Boolean;
var
  LibHandle: THandle;

begin
  LibHandle:= LoadLibrary(PChar(Name));
  if (LibHandle <> 0) then begin
    @GetInterface:= GetProcAddress(LibHandle, 'GetInterface');
    if @GetInterface <> nil then begin
      Result:= True;
      Exit;
    end;
    FreeLibrary(LibHandle);
  end;
  Result:= False;
end;

end.

And simple test application to be sure everything works as expected:

program Test;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ITypes in 'ITypes.pas',
  ByteWrappers in 'ByteWrappers.pas';

procedure TestInterface;
var
  Bytes1, Bytes2: TMyBytes;

begin
  Bytes1.Append(0);
  Bytes1.CopyTo(Bytes2);
  Bytes1.Append(0);
  Writeln(Bytes1.GetLength, ' -- ', Bytes2.GetLength);
end;

begin
  try
    ReportMemoryLeaksOnShutdown:= True;
    if LoadDll('TestDLL.dll')
      then TestInterface
      else Writeln('Can''t load TestDLL.dll');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

So far so good; now we want to consume the above Delphi interface in a C++ structure (or a class – there is no difference between a C++ structure and a C++ class here). The solution is:

// ByteWrappers.hpp
#ifndef BYTEWRAPPERS_H_INCLUDED
#define BYTEWRAPPERS_H_INCLUDED

#include <string>

using namespace std;

typedef uint8_t Byte;
typedef int32_t Integer;

bool LoadDll(string Name);

class IBytes {
  public:
    virtual Integer __stdcall QueryInterface(void* riid, void** ppvObject) = 0;
    virtual Integer __stdcall AddRef() = 0;
    virtual Integer __stdcall Release() = 0;
    virtual Integer __stdcall GetLength() = 0;
    virtual void __stdcall Append(Byte B) = 0;
    virtual void __stdcall CopyTo(IBytes** C) = 0;
};

class MyBytes {
  private:
    IBytes* FBytes;
  public:
    MyBytes() : FBytes(NULL) {};    // default constructor
    MyBytes(const MyBytes& A)       // copy constructor
    {
    	FBytes = A.FBytes;
    	if (FBytes != NULL)
    	{
    		FBytes->AddRef();
    	}
    };

    ~MyBytes()                      // destructor
    {
  	    if (FBytes != NULL)
  	    {
  		    FBytes->Release();
  		    FBytes = NULL;
  	    }
    };

    MyBytes& operator= (const MyBytes& A)   // assignment
    {
        if (A.FBytes != NULL)
            A.FBytes->AddRef();
        if (FBytes != NULL)
            FBytes->Release();
        FBytes = A.FBytes;
        return *this;
    }

    void Free()
    {
        if (FBytes != NULL)
        {
            FBytes->Release();
            FBytes = NULL;
        }
    }

    void Append(Byte B);
    void CopyTo(MyBytes& C);
    Integer GetLength();
};

#endif // BYTEWRAPPERS_H_INCLUDED
// ByteWrappers.cpp
#include <windows.h>
#include "ByteWrappers.hpp"

typedef void(__stdcall *PGetInterface)(IBytes**);
PGetInterface GetInterface = 0;

void MyBytes::Append(Byte B)
{
    if (FBytes == NULL) GetInterface(&FBytes);
    FBytes->Append(B);
}

void MyBytes::CopyTo(MyBytes& C)
{
    if (FBytes == NULL) C.Free();
    else FBytes->CopyTo(&C.FBytes);
}

Integer MyBytes::GetLength()
{
    if (FBytes == NULL) return 0;
    else return FBytes->GetLength();
}

bool LoadDll(string Name)
{
    HINSTANCE LibHandle;

    LibHandle = LoadLibrary(Name.c_str());
    if (LibHandle != 0)
    {
        GetInterface = (PGetInterface)GetProcAddress(LibHandle, "GetInterface");
        if (GetInterface != NULL) return true;
        FreeLibrary(LibHandle);
    }
    return false;
}

Test application:

#include <iostream>
#include <string>
#include "ByteWrappers.hpp"

using namespace std;

void TestInterface(){
    MyBytes Bytes1;
    MyBytes Bytes2;

    Bytes1.Append(0);
    Bytes1.CopyTo(Bytes2);
    Bytes1.Append(0);
    cout << Bytes1.GetLength() << " -- " << Bytes2.GetLength() << endl;
}

int main()
{
    if (LoadDll("TestDLL.dll"))
        TestInterface();
    else
        cout << "Can't load TestDLL.dll" << endl;
    return 0;
}

Some details worth being mentioned:

  • Delphi interfaces are always derived from IUnknown; a corresponding pure abstract C++ class should also define IUnknown methods;
  • Delphi interface type is kind of a pointer to the corresponding C++ abstract class, so sometimes we need one more level of indirection in C++ code;
  • Delphi interface variables are always initialized to nil by the compiler; in C++ we need default constructor to implement the nil-initialization;
  • Delphi interfaces are automatically released (i.e. call IUnknown._Release method) when an interface variable goes out of scope; In C++ we implement the same functionality in destructor;
  • Interface assignment in Delphi implicitly calls _Addref and _Release methods of IUnknown; in C++ we overload the assignment operator to implement the interface assignment correctly;
  • C++ supports variable initialization like this:
        MyBytes Bytes1;
        Bytes1.Append(0);
        MyBytes Bytes2 = Bytes1;
    

    to implement it correctly in our case we need a copy constructor.

Advertisements

5 thoughts on “Consuming Delphi interfaces in Dephi and C++

  1. Why you make it so complicated?
    There is not need of complicated wrapper record and the whole ByteWrappers unit itself.
    Just add the GetInterface function defintion to your test project, change the type of Byte1 and Byte2 variables an everything works well:

    uses
    SysUtils,
    ITypes in ‘ITypes.pas’;

    procedure GetInterface(var I: IBytes); stdcall; external ‘TestDLL.dll’ delayed; //delayed directive is optional, see http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages

    procedure TestInterface;
    var
    Bytes1, Bytes2: IBytes;
    begin
    GetInterface(Bytes1);
    GetInterface(Bytes2);

    Bytes1.Append(0);
    Bytes1.CopyTo(Bytes2);
    Bytes1.Append(0);
    Writeln(Bytes1.GetLength, ‘ — ‘, Bytes2.GetLength);
    end;

    begin
    try
    ReportMemoryLeaksOnShutdown:= True;
    TestInterface;
    except
    on E: Exception do
    Writeln(E.ClassName, ‘: ‘, E.Message);
    end;
    Readln;
    end.

    • IBytes is just a toy example. There are many reasons to encapsulate a Delphi interface reference in a wrapper record type, for example you want to hide or modify some interface methods, or overload operators.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s