VB、PBからロードできるDLLの作り方
 Visual Basic(VB)やPowerBuilder(PB)は、使いやすい統合開発環境を備えた完成度の高いRADツールですが、 処理の記述はスクリプトベースなので、 C言語のように細かい処理を書くことができなかったり、 できても非常にややこしいロジックが必要になったりします。 そこで、これらのRADツールには、Cなどで書いたダイナミックリンクライブラリ(DLL) をロードして、その中で定義されているDLL関数(PBでは外部関数と呼ばれます) を呼び出すことで機能拡張をする機構が用意されています。

 ここでは、このVBやPBからロードできる DLLをC言語直書きで作る方法についてメモっています。

●用意するもの

  • C言語のソースが書けるテキストエディタ。MSメモ帳でもOKです。
  • C言語のコンパイラ。ここではマイクロソフトのVisual C++ 4.0と、 インプライズのBorland C/C++ Compiler 5.5で紹介しています。 後者はフリーで配布されています。
  • 動作確認のためのVisualBasic処理系、またはVBA(Visual Basic for Applications) を搭載しているマイクロソフトのオフィスアプリ。ここではExcel97を例に扱います。 またPowerBuilderはバージョン5.0.03での動作を紹介しています。

●ソースプログラムを書く
 まず、テキストエディタでソースプログラムを書きます。 簡単な例でいきましょう。
 最初に、そのDLLが「公開」する関数の形を決める必要があります。 VBやPBなどのアプリから呼び出すとベンリな関数をCで書くわけですが、 ここでは下の4つの関数を作ります。

  • add2(与えられた2つの整数の和を返す)
  • sub2(与えられた2つの整数の差を返す)
  • len2(与えられた文字列の長さを返す)
  • itoa2(与えられた整数を文字列にして返す)
ぐはぁ。なんですかこれは、わざわざCで書かなくても、 というやつらばっかりですが、とりあえずの例はこれで。 また、最初にソースの名前、DLLの名前を決めておきます。 ここでは、「a.c」から「liba.dll」を作ることにします。
/*
 * 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が呼び出された瞬間、 またはアンロードされる瞬間に何か初期化・あとしまつ処理をしたい場合には、 DllMainDllEntryPoint という決まった形の関数を次のように書きます。
/* 必要があれば上のソースに追加してください */
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);
}

●モジュール定義ファイルを書く
(Borland C/C++ Compilerの場合はここは飛ばしてもOKです) VC++の場合は、モジュール定義ファイル ( .defファイル)というものを書く必要があります。 これは、公開するDLL関数を羅列したもので、 このa.cの場合は下のようになります。a.defという名前で適当に作りましょう。

LIBRARY liba
EXPORTS
    add2
    sub2
    len2
    itoa2
 普通は、ソース内部に__declspec(dllexport) というおまじないを書いておけば、モジュール定義ファイルは不要なのですが、 その場合、VC++は、__stdcallがついた関数に対して 「装飾名」の形で外部に公開してしまいます。 装飾名とは、「_add2@4」のように前と後ろにへんなものがついた名前で、 これだとVBやPBからいくら「add2」という関数を探しても見つからない、 というエラーになってしまいます。 モジュール定義ファイルを上のように定義しておけば、 add2という関数はadd2という名前で公開されるので、 VBやPBから読み出すことができます。

●コンパイルしよう、Visual C++編
 Visual C++といっても統合開発環境ではなく、 付属のコマンドラインユーティリティNMAKEを使います。 まずはメイクファイル「Makefile」を下のように作ります。


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> NMAKE
liba.dllができたらおなぐさみですが、どうでしょう。

●コンパイルしよう、Borland C/C++編
 Borland C/C++ Compilerの場合も付属のユーティリティMAKEを使います。 こちらも「Makefile」をこのように書きます。


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を配置
 DLLは、パスが通っている位置ならどこに置いても構いません。 C:\Windows\System でもいいですが、 どこかわかりやすいところを決めてそこに置くようにしたほうがよいでしょう。

●VBから呼び出してみよう
 では、このliba.dllの関数をVB(ここではVBA for Excel97)から呼び出してみます。

(1) まず、ExcelからVisual Basic Editorを起動します。
メニュー「ツール」→「マクロ」→「VisualBasic Editor」

(2) 空のコードモジュールを作ります。
メニュー「挿入」→「標準モジュール」
または、新規マクロを作って編集モードにすると自動的に標準モジュールがひとつ作られるので、それを使ってもOKです。

(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から呼び出してみよう
 今度は、PowerBuilderから呼び出してみましょう。 なにペインタでもいいので、使いたいコードを書くペインタを開き、 「宣言」→「グローバル外部関数」または「ローカル外部関数」 で関数宣言のダイアログを開き、次のように書きます。

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の引数と異なるとすぐに落ちてしまうので、 十分注意が必要です。

ActiveX&Java Top
(uploaded 2000/06/10 and not ever modified)

テレワークならECナビ Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!
無料ホームページ 無料のクレジットカード 海外格安航空券 海外旅行保険が無料! 海外ホテル