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: