この記事は、Thushara Wedagedara氏のブログの抄訳です
インターネット上には、便利なC++ライブラリが豊富に存在します。過去にエンバカデロのブログでも、C++の素晴らしい使い方を紹介してきました。 C++は非常に高いパフォーマンスを発揮します。C++ライブラリのソースコードを入手できる場合は、DelphiプログラムでC++を使用可能にするパッケージを作成できます。しかし、C++ライブラリのソースコードが入手できないことがよくあります。商用向けのC++ライブラリでは、いくつかのC++ヘッダーと静的ライブラリファイル(.lib)のみが提供され、.cppソースファイルが提供されていないことがよくあります。もしこのようなケースで、DelphiのアプリケーションでそれらのC++ライブラリを使用したい場合、プロキシDLLを作成すれば、ソースコードが無くてもC++ライブラリを利用することができます。
Table of Contents
DelphiからC++ DLLを呼び出すためのプロキシDLLの作成方法
DelphiからDLLを利用するためには、呼び出し規約をcdeclではなく、stdcallで関数(API)を公開する必要があります。プロキシDLLのコンパイルは、C++Builder以外にも以下のような任意のC++コンパイラが利用できます。
簡単な例を挙げてみましょう。ここでは、DLLファイルと宣言を含むヘッダーファイルがあり、実装のコード.cppファイルは存在しないものとします。この例のソースコードは以下ののURLから入手できます。
https://github.com/PacktPublishing/Delphi-High-Performance/tree/master/Chapter%208/StaticLib1
1 2 3 4 5 6 7 8 9 10 |
#pragma once class CppClass { int data; public: CppClass(); ~CppClass(); void setData(int); int getSquare(); }; |
プロキシDLLを介してDelphiプログラムでC++ライブラリを使用する方法
まずプロキシDLLを作成する必要があります。
任意のIDEで新しいC++ DLLプロジェクトを作成してください。(下図は、Embarcadero Dev-C++を利用した場合の例)
「dllmain.cpp」ファイルが自動的に追加されます。 ただし、静的ライブラリをラップするために別のユニットが必要です。 ここでは「StaticLibWrapper.cpp」という新しいユニットを追加します。
インポートしたい静的ライブラリのヘッダファイルをプロジェクトにインクルードする
1 2 |
#include "stdafx.h" #include "CppClass.h" |
次に、静的ライブラリのヘッダーファイルをプロジェクトフォルダにコピーします。 そして静的ライブラリをプロジェクトに含める必要があります。 最後に下図のようにEmbarcadero Dev-C++を利用の場合は、静的ライブラリをコピーしたフォルダをIDEのライブラリパスに追加します。
VisualStudioの場合は、「構成のプロパティ| リンカー| 一般| 追加のライブラリディレクトリ設定」で行えます。
C++のDLL関数をエクスポート宣言する
ここで、DLL関数がエクスポートされたことを示すマクロを定義します。
1 |
#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__) |
次に、C++オブジェクトをキャッシュするためにIndexAllocatorクラスを実装します。このクラスはポインターの配列を格納します。Allocate”、”Release”、”Get “の3つの関数があり、ポインタをキャッシュに格納、キャッシュを解放、そしてインデックスでポインタを取得する役割を持っています。
1 2 3 |
bool Allocate(int& deviceIndex, void* obj) bool Release(int deviceIndex) void* Get(int deviceIndex) |
次にIndexAllocatorオブジェクトの割り当てと解放を行うために、Initialize関数とFinalize関数が必要です。
1 2 |
extern "C" int WINAPI Initialize() extern "C" int WINAPI Finalize() |
そしてCppClassクラスのインスタンスを作成し、この関数でキャッシュに格納します。
1 |
extern "C" int WINAPI CreateCppClass (int& index) |
上記のコードでは、extern指定子で”C”を宣言し、呼び出し規約としてWINAPIマクロ(__stdcall)を使用しています。DestroyCppClassも同様です。次に、主要なエクスポート関数である「CppClass_setValue」と「CppClass_getSquare」を見てみましょう。ユーザーがこれらの関数を呼び出すと、キャッシュからオブジェクトを取得し、これらの関数を呼び出して値を取得しています。
1 2 3 4 5 6 7 8 9 10 11 |
extern "C" int WINAPI CppClass_setValue(int index, int value) { #pragma EXPORT CppClass* instance = (CppClass*)GAllocator->Get(index); if (instance == NULL) return -1; else { instance->setData(value); return 0; } } |
1 2 3 4 5 6 7 8 9 10 11 |
extern "C" int WINAPI CppClass_getSquare(int index, int& value) { #pragma EXPORT CppClass* instance = (CppClass*)GAllocator->Get(index); if (instance == NULL) return -1; else { value = instance->getSquare(); return 0; } } |
上記で示す1つ目の関数では、オブジェクトのインデックスを取得して、変数の値を設定します。2つ目の関数では、オブジェクトのインデックスを取得し、オブジェクトの “getSquare “関数を呼び出して、その値をvalue変数に格納しています。
C++ プロキシDLLをDelphiアプリケーションで使用するには?
DLLをリンクさせるには、静的あるいは動的にリンクさせる方法があります。静的ロードでは、アプリケーションの起動時にDLLがロードされます。動的ロードでは、”LoadLibrary “を呼び出すまでDLLはロードされません。この例では、静的ロードを使用してみましょう。 以下のコードは、DLLにエクスポートされた関数を宣言しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const CPP_CLASS_LIB = 'DllLib1.dll'; function Initialize: integer; stdcall; external CPP_CLASS_LIB name 'Initialize' delayed; function Finalize: integer; stdcall; external CPP_CLASS_LIB name 'Finalize' delayed; function CreateCppClass(var index: integer): integer; stdcall; external CPP_CLASS_LIB name 'CreateCppClass' delayed; function DestroyCppClass(index: integer): integer; stdcall; external CPP_CLASS_LIB name 'DestroyCppClass' delayed; function CppClass_setValue(index: integer; value: integer): integer; stdcall; external CPP_CLASS_LIB name 'CppClass_setValue' delayed; function CppClass_getSquare(index: integer; var value: integer): integer; stdcall; external CPP_CLASS_LIB name 'CppClass_getSquare' delayed; |
Delphiのアプリケーションが作成されたときに “Initialize”関数を呼び出し、アプリケーションが破棄されたときに “Finalize”関数を呼び出す必要があります。そして、DelphiのコードでProxy DLLを使用する際には、最初に”CreateCppClass”を呼び出してオブジェクトを作成する必要があります。これにより、今後使用するクラスIDが設定されます。その後、DLLのすべての関数を呼び出すことができます。そして最後にクラスのインスタンスを破棄する場合には “DestroyCppClass “関数を呼び出す必要があります。コード例は、以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TfrmCppClassDemo.btnImportLibClick(Sender: TObject); var idxClass: Integer; value: Integer; begin if CreateCppClass(idxClass) <> 0 then ListBox1.Items.Add('CreateCppClass failed') else if CppClass_setValue(idxClass, SpinEdit1.Value) <> 0 then ListBox1.Items.Add('CppClass_setValue failed') else if CppClass_getSquare(idxClass, value) <> 0 then ListBox1.Items.Add('CppClass_getSquare failed') else begin ListBox1.Items.Add(Format('square(%d) = %d', [SpinEdit1.Value, value])); if DestroyCppClass(idxClass) <> 0 then ListBox1.Items.Add('DestroyCppClass failed') end; end; |
最終的に、Delphiのアプリケーションは以下のように動作します。
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition