!!!VC++のDLLを呼び出す VC++で作成した共有ライブラリ(DLL)をBCCで呼び出します。 この場合は、「VC++でのDLLはC言語の関数で定義」している必要があります。 たとえば、足し算・引き算を行う関数を持ったDLLを作ってみましょう。 !!VC++での作業(DLLの作成) 作成するDLL名を「AddTest.dll」とします。 「AddTest.cpp」「AddTest.h」「main.cpp」「AddTest.def」というソースを作成します。 [AddTest.h] #ifndef _ADDTEST_H #define _ADDTEST_H typedef struct { float x, y, z; } S_VECTOR3; #if __cplusplus extern "C" { #endif int AddTest(int a, int b); int SubTest(int a, int b); S_VECTOR3 Vec3AddTest(S_VECTOR3 v1, S_VECTOR3 v2); #if __cplusplus } #endif #endif [AddTest.cpp] #include #include "AddTest.h" int AddTest(int a, int b) { return (a + b); } int SubTest(int a, int b) { return (a - b); } S_VECTOR3 Vec3AddTest(S_VECTOR3 v1, S_VECTOR3 v2) { S_VECTOR3 ret; ret.x = v1.x + v2.x; ret.y = v1.y + v2.y; ret.z = v1.z + v2.z; return ret; } [main.cpp] #include #include #include #include extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { return TRUE; } [AddTest.def] LIBRARY AddTest DESCRIPTION "AddTest - DLLのテスト用外部関数" EXPORTS ; -- AddTest.cpp/h AddTest @00001 SubTest @00002 Vec3AddTest @00003 このうち「main.cpp」はDllMainを定義しているだけのダミーです。 実際は関数を呼び出すために使用しません。 関数定義は「AddTest.h」にて extern "C" { ... } で囲っている点に注意してください。つまり、C言語の関数として明示的に しているわけです。なんで、C++のクラスやC++特有の命令は使用できません。 (関数内では使用OKですが、入り口と出口部分はC言語で) 関数としては「AddTest」「SubTest」「Vec3AddTest」の3つです。 「Vec3AddTest」は構造体の引数を渡して構造体を返しています。 (アライメントに注意してください。VC++/BCC共にデフォルトは4バイトアライメントです) 「AddTest.def」は、外部関数として定義する関数を列挙します。 1行目の「LIBRARY AddTest」にてDLL名を指定します。作成されるDLL名と 同じ名称である必要があります。 「EXPORTS」以下でそれぞれ関数に対して一意な番号を割り振ってます。 この関数と番号の対応付けにより、DLLの内部仕様が変わった場合でも DLLさえ入れ替えれば対応できるようになります。 これをコンパイルすると「AddTest.DLL」ができます。 !!BCCでの実行ファイルの作成 WindowsAPIの「LoadLibrary」でDLLを呼び出して、個々の関数を「GetProcAddress」にてマッピングします。 DLL内の外部関数の記述がC言語書式であれば、BCCでもDelphiでもVBでも呼び出すことが できるようになります。 ここではマッピング用のソース「AddTestIn.cpp」「AddTestIn.h」と、 呼び出し側の「test.cpp」を作成しています。 [AddTestIn.h] #ifndef _ADDTESTIN_H #define _ADDTESTIN_H typedef struct { float x, y, z; } S_VECTOR3; typedef int (* PADDTEST)(int, int); typedef int (* PSUBTEST)(int, int); typedef S_VECTOR3 (* PVEC3ADDTEST)(S_VECTOR3, S_VECTOR3); extern PADDTEST g_pAddTest; extern PSUBTEST g_pSubTest; extern PVEC3ADDTEST g_pVec3AddTest; int LoadDLL(); void FreeDLL(); #endif [AddTestIn.cpp] #include #include #include #include "AddTestIn.h" static HINSTANCE g_DLLInst; PADDTEST g_pAddTest; PSUBTEST g_pSubTest; PVEC3ADDTEST g_pVec3AddTest; //----------------------------------------------// // DLLをマッピングする // //----------------------------------------------// int LoadDLL() { g_DLLInst = LoadLibrary("AddTest.dll"); if(!g_DLLInst) return 0; g_pAddTest = (PADDTEST)GetProcAddress(g_DLLInst, "AddTest"); g_pSubTest = (PSUBTEST)GetProcAddress(g_DLLInst, "SubTest"); g_pVec3AddTest = (PVEC3ADDTEST)GetProcAddress(g_DLLInst, "Vec3AddTest"); return 1; } //----------------------------------------------// // DLLを開放する // //----------------------------------------------// void FreeDLL() { FreeLibrary(g_DLLInst); } [test.cpp] #include #include #include "AddTestIn.h" //----------------------------------------------// // メイン // //----------------------------------------------// int main(int argc, char *argv[]) { int a, b; S_VECTOR3 v1, v2, v3; char szStr[256]; //DLLの呼び出し if(!LoadDLL()) { printf("AddTest.dllの読み込みに失敗しました。\n"); return -1; } printf("DLLより足し算関数を呼び出します。\n"); a = 125; b = 51; sprintf(szStr, "%d + %d = %d\n", a, b, g_pAddTest(a, b)); printf(szStr); sprintf(szStr, "%d - %d = %d\n", a, b, g_pSubTest(a, b)); printf(szStr); v1.x = 1.4f; v1.y = 2.5f; v1.z = 3.1f; v2.x = 4.2f; v2.y = 6.6f; v2.z = 9.3f; v3 = g_pVec3AddTest(v1, v2); sprintf(szStr, "(%.2f, %.2f, %.2f) + (%.2f, %.2f, %.2f) = (%.2f, %.2f, %.2f)\n", v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z); printf(szStr); //DLLの開放 FreeDLL(); return 0; } 「typedef int (* PADDTEST)(int, int);」の記述にて関数を呼び出すときの 型を宣言しています。この場合は型名が「PADDTEST」で引数は2つのint、戻り値がintです。 PADDTEST g_pAddTest; で「関数のポインタ」を実体化しています。 次に「LoadLibrary」でDLLを呼び出したときのハンドルを元に g_pAddTest = (PADDTEST)GetProcAddress(g_DLLInst, "AddTest"); として、関数「AddTest」を「AddTest.dll」より探し出して「g_pAddTest」に マッピングしています。以降は「c = g_pAddTest(a, b);」として 普通の関数のように呼び出すことができます。 なお、DLLでの関数名や引数の方はこの呼び出し側での定義と同じである 必要があります。関数が存在しない場合・引数が間違っている場合はNULLが返されます。 「AddTestIn.h」にてこれらの関数(のポインタ)を「extern」で宣言していますが、 これは他のファイルで共有して使うためです。 実体は「AddTestIn.cpp」の先頭で定義されています。 最後に、これらをコンパイルするためのmakefile「makefile.mak」を作成します。 [makefile.mak] CC = bcc32 LINKER = ilink32 INCLUDE = -I"i:\User\bcc program" LIB = -L"h:\program files\Borland\Bcc55\lib;i:\User\bcc program" #コンソールアプリケーションのためのコンパイルオプション CFLAGS = -O2 -w -tWC LFLAGS = /Tpe TARGET = test.exe OBJS = test.obj AddTestIn.obj ALL : $(TARGET) # -- objファイルの作成 AddTestIn.obj: AddTestIn.cpp AddTestIn.h $(CC) $(CFLAGS) -c AddTestIn.cpp test.obj: test.cpp $(CC) $(CFLAGS) -c test.cpp # -- exeファイルの作成 $(TARGET): $(OBJS) $(LINKER) $(LFLAGS) $(LIB) $(OBJS) c0x32.obj,$(TARGET),,cw32.lib import32.lib clean: del *.obj del *.tds del *.il* del *.map メイク処理は make -f makefile.mak で成功すると「test.exe」が生成されます。 VC++で作成した「AddTest.dll」を同じディレクトリに置いて コンソールから実行すると、以下のように出力されます。 DLLより足し算関数を呼び出します。 125 + 51 = 176 125 - 51 = 74 (1.40, 2.50, 3.10) + (4.20, 6.60, 9.30) = (5.60, 9.10, 12.40) !!プロジェクトとソース 上記サンプルの VC++(6.0)のプロジェクトとソースは以下になります。 {{ref vc_AddTest_dll_20040905.lzh}} BCCのソースとmakefileは以下になります。 {{ref bcc_AddTest_20040905.lzh}} !!DLLの外部関数を確認するには 「[Dependency Walker|http://www.dependencywalker.com/]」という DLL/EXEの内部構造を見るツールが便利です。 これを一回実行すると、DLLをエクスプローラで選択した状態の ポップアップメニューに「View Dependencies...」というのが追加されます。 これを選択するとDLLの内部構造が表示されます。 {{ref_image dw_20040905.png}} 「AddTest」「SubTest」「Vec3AddTest」がC言語の関数として定義されているのが 分かりますね。「Ordinal」の列がdefファイルでの番号になります。 また、DLLの依存関係の確認やうっかりデバッグでビルドしてしまった、とかいうのも この「Dependency Walker」で確認することができます。