VB、PBからロードできるDLLの作り方 Visual Basic(VB)やPowerBuilder(PB)は、使いやすい統合開発環境を備えた完成度の高いRADツールですが、 処理の記述はスクリプトベースなので、 C言語のように細かい処理を書くことができなかったり、 できても非常にややこしいロジックが必要になったりします。 そこで、これらのRADツールには、Cなどで書いたダイナミックリンクライブラリ(DLL) をロードして、その中で定義されているDLL関数(PBでは外部関数と呼ばれます) を呼び出すことで機能拡張をする機構が用意されています。 ここでは、このVBやPBからロードできる DLLをC言語直書きで作る方法についてメモっています。
●用意するもの
●ソースプログラムを書く
/* * a.c */ #include <stdio.h> #include <string.h> #include <windows.h> #define DLLEXPORT __declspec(dllexport) DLLEXPORT int __stdcall add2(int a, int b){ return a + b; } DLLEXPORT int __stdcall sub2(int a, int b){ return a - b; } DLLEXPORT int __stdcall len2(char* p){ return strlen(p); } DLLEXPORT int __stdcall itoa2(int n, char* result){ sprintf(result, "%d", n); return 0; } /* end. */VBやPBから呼び出す関数をDLLで定義するためには、 __stdcallは必須です。 あとは普通のCのように書けばOKです。 また、 __declspec(dllexport)は、 関数をDLLから他のソースにエクスポートすることをソース内部で宣言するための 「おまじない」で、 次の「モジュール定義ファイル」があればほんとは不要なんですが、 ソースを見たときに「これは公開されているよーん」 とゆーのがすぐわかるという隠れた効果があるため、一応つけておきます。 ちなみに、普通DLLにはmain関数がありませんが、 DLLが呼び出された瞬間、 またはアンロードされる瞬間に何か初期化・あとしまつ処理をしたい場合には、 DllMainやDllEntryPoint という決まった形の関数を次のように書きます。 /* 必要があれば上のソースに追加してください */ BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved){ switch (reason) { case DLL_PROCESS_ATTACH: /* 初期化処理が必要ならここに書きます */ break; case DLL_PROCESS_DETACH: /* あとしまつ処理が必要ならここに書きます。*/ break; } return TRUE; /* 処理に成功したらTRUEを返します */ } BOOL APIENTRY DllEntryPoint(HINSTANCE hInst, DWORD reason, LPVOID reserved){ return DllMain(hInst, reason, reserved); }
●モジュール定義ファイルを書く LIBRARY liba EXPORTS add2 sub2 len2 itoa2普通は、ソース内部に__declspec(dllexport) というおまじないを書いておけば、モジュール定義ファイルは不要なのですが、 その場合、VC++は、__stdcallがついた関数に対して 「装飾名」の形で外部に公開してしまいます。 装飾名とは、「_add2@4」のように前と後ろにへんなものがついた名前で、 これだとVBやPBからいくら「add2」という関数を探しても見つからない、 というエラーになってしまいます。 モジュール定義ファイルを上のように定義しておけば、 add2という関数はadd2という名前で公開されるので、 VBやPBから読み出すことができます。
●コンパイルしよう、Visual C++編 CC = cl SHLD = cl /LD CFLAGS = /nologo /O2 /GR- /GX- LDFLAGS = /nologo LIBS = .c.obj: $(CC) $(CFLAGS) -c $*.c liba.dll: a.obj $(SHLD) /o $@ $(LDFLAGS) a.obj a.def $(LIBS) VC++のコマンドラインコンパイラ(CL.EXE)にはパスを通しておきます。 また、環境変数LIBとINCLUDEにはそれぞれVC++の標準ライブラリとヘッダファイルのパスをセットしておきます。 準備が出来たら、NMAKEでビルドします。 dos> SET INCLUDE=C:\MSDEV\INCLUDE dos> SET LIB=C:\MSDEV\LIB dos> NMAKEliba.dllができたらおなぐさみですが、どうでしょう。
●コンパイルしよう、Borland C/C++編 CC = c:\Borland\bcc55\bin\bcc32 SHLD = c:\Borland\bcc55\bin\ilink32 -c -Tpd -x CFLAGS = -O2 STARTUP = c0d32.obj LIBS = SYSLIBS = cw32mt.lib import32.lib SHLIBS = $(LIBS) $(SYSLIBS) .c.obj: $(CC) $(CFLAGS) -c $*.c liba.dll: a.obj $(SHLD) $(STARTUP) a.obj, $@, , $(SHLIBS), , あとはMAKEでビルドできます。 dos> MAKE
●DLLを配置
●VBから呼び出してみよう
(1) まず、ExcelからVisual Basic Editorを起動します。
(2) 空のコードモジュールを作ります。 (3) コードモジュールの(General)-(Declarations)に、 Declareステートメントを使ってDLL関数の宣言を書きます。 これは普通コードモジュールで行います。 フォームモジュールで行う場合には、必ず宣言をPrivateで行わないといけません (Private Declare ...とします)。 Declare Function add2 Lib "liba.dll" _ (ByVal a As Long, ByVal b As Long) As Long Declare Function sub2 Lib "liba.dll" _ (ByVal a As Long, ByVal b As Long) As Long Declare Function len2 Lib "liba.dll" _ (ByVal str As String) As Long Declare Function itoa2 Lib "liba.dll" _ (ByVal a As Long, ByVal result As String) As Long注意点は、Win32環境ではC言語のintはVBではlongになる ことと、引数には必ずByValキーワードをつける ことです。Cでの処理結果をVBの変数に格納する場合にもStringはByValで渡します。 これは間違えないようにしてください。
|
|
(4) 次にコードを書くわけですが、 可変長Stringは必ず空文字で必要な文字数だけ埋めておく必要があるのに大注意猛注意。 これをしないと異常終了してしまいます。 result = String(256, vbNullChar) a = itoa2(17, result) (5) では、Subプロシージャ(一般にマクロです)を実行してみます。 VB Editorのメニュー「実行」→「Sub/ユーザーフォームの実行」 かExcelのメニュー「ツール」→「マクロ」→「マクロ…」 でこのコードを走らせます。
●PBから呼び出してみよう FUNCTION long add2 (long a, long b) LIBRARY "liba.dll" FUNCTION long sub2 (long a, long b) LIBRARY "liba.dll" FUNCTION long len2 (string a) LIBRARY "liba.dll" FUNCTION long itoa2 (long a, REF string result) LIBRARY "liba.dll"
|
|
PowerBuilderも、 C言語でのintはlongになることは同じですが、 今度は結果をPB側で受け取るときの変数にはREFキーワードをつける点が違います。 ちなみにPBではこの宣言を 「.pbf」という拡張子をつけたテキストファイルに書いておけば、 上のダイアログに見える「ファイル検索…」というボタンを押せば取りこむことができるため、少々複雑な宣言を要するDLLでも扱いはVBより楽です。
|
|
そして、実際のスクリプトのコーディングですが、 PBの場合はPBの標準関数と同じように使うことができ、 コーディング上特に配慮が必要なことはないのですが、 宣言が実際のDLLの引数と異なるとすぐに落ちてしまうので、 十分注意が必要です。 |