Imo Soft  | TOP | Imo Soft | ドン!3 | 大学関係 | 掲示板 | リンク |
日曜プログラマのソフト置き場 >> Imo Soft >> VB-Delphi(目次) >> VB-Delphi-第二回
03/09/23更新

Visual BasicプログラマのためのDelphi研究室


第二回 コールバック機能付きのDLLを作る



何をやるかというと...

 Delphiで書いたDLLからVBで書いた関数を呼び出します。つまり、コールバック機能を持ったDLLを作ります。
 EnumWindowsのようにループなしで複数のものを処理するものを書いたり、あるいは、ジョイパッドに入力があったらそれを処理する関数を呼び出すなど… 色々な応用が考えられます。
 今回は、第一回で作成したDLLに途中経過の報告機能を付けることにします。


コールバック関数って何?

 Win32 APIというものの実体は第一回で作ったDLLと同じものです。そんなWin32 APIの中でも特に特徴的なのがEnum〜s系の関数です。EnumWindowsEnumFontsなどがありますが、それらに共通する機能がコールバック関数の呼び出し機能です。
 これらの関数を呼び出すときは、『AddressOf演算子』を利用して関数のアドレスを引数として渡します。すると、必要に従ってAPI側からVBで書いた関数を呼び出すというわけです。
 こういう関数の何が便利かというと、DLLを利用する側には、『ループ』が必要ないということです。つまり、一連の処理を記述した関数を用意すればAPIが勝手に必要な回数その関数を呼び出してくれるわけです。


制約事項

 このサンプルでは、Visual Basicの『AddressOf演算子』を使います。そのため、VB 5.0 以上でなければ、実行できません。


キーポイント

 VBから現在作成しようとしているDLLの関数に『AddressOf演算子』を使ってコールバック関数のアドレスを渡します。 『AddressOf演算子』を使うと関数への『ポインタ』が渡されます。『ポインタ』と聞くとそれだけで拒絶反応を起こしてしまう人もいるかも知れませんが、単なるLong型の整数です。関数の住所のようなものと考えて下さい。
DLL側では、そのアドレスを『手続き型変数』として受け取ります。Delphiでは、関数やプロシージャを変数に代入することができるのです。以下に例を示します。


function Add(N1,N2: Integer): Integer;
begin
  result:=N1+N2; //N1とN2を足した値を返す
end;

procedure Test();
//関数型(整数の引数を2つ持ち整数を返す)の変数fncを宣言
var fnc: function(A,B: Integer): Integer;
begin
  fnc:=Add; //fncにAddのアドレスを代入
  //変数fnc経由でAdd関数が間接的に呼び出される
  ShowMessage(IntToStr(fnc(1,2)));
  //画面に『3』と表示される
end;

ちなみに、返値がないもの(VBで言うところのサブプロシージャ)は、


var proc: procedure(N1,N2: Integer);

のように宣言します。


プロジェクトを開く

では、製作にかかりましょう。
[プロジェクトを開くのダイアログの表示] 第一回で作成した、プロジェクト『tossing_coin_dll』を開きましょう。
まず、『ファイル』『プロジェクトを開く』を選択します。
[プロジェクトを開くのダイアログ] 『プロジェクトを開く』のダイアログが表示されるので前回保存した『tossing_coin_dll.dpr』を開いて下さい。
画面に前回製作したプログラムが表示されるはずです。


Delphiでのコーディング

前回のコードを一部書き換えます。(*ここから↓*)から(*ここまで↑*)までが編集範囲です。(太字の範囲)

コード(Delphi、tossing_coin_dll.dpr)

library tossing_coin_dll;
{ DLL でのメモリ管理について:
  もしこの DLL が引数や返り値として String 型を使う関数/手続きをエクスポー
  トする場合、以下の USES 節とこの DLL を使うプロジェクトソースの USES 節
  の両方に、最初に現れるユニットとして ShareMem を指定しなければなりません。
  (プロジェクトソースはメニューから[プロジェクト|ソース表示] を選ぶこと
  で表示されます)
  これは構造体やクラスに埋め込まれている場合も含め String 型を DLL とやり
  取りする場合に必ず必要となります。
  ShareMem は共用メモリマネージャである BORLNDMM.DLL とのインターフェース
  です。あなたの DLL と一緒に配布する必要があります。BORLNDMM.DLL を使うの
  を避けるには、PChar または ShortString 型を使って文字列のやり取りをおこ
  なってください。}

uses SysUtils,Classes;

(*ここから↓*)
type TcbProc=procedure(D: Double); stdcall; //ユーザ定義型を宣言
(*ここまで↑*)


{$R *.res}
function TossingCoin(N: Integer): Double; stdcall;
var I: Integer;
    Counter: Integer;
begin
    if N<=0 then //ゼロ以下の時はエラーなので-1を返す。
    begin
        result:=-1;
        exit;
    end; //if
    Counter:=0;
    for I:=0 to N do
    begin
        Counter:=Counter+Trunc( Random(2) );
    end; //for
    result:=Counter/N;
end; //function

(*ここから↓*)
function TossingCoinCallBack(N: Integer; CallBackProc: TcbProc): Double; stdcall;
var I: Integer;
    Counter: Integer;
begin
    if N<=0 then //ゼロ以下の時はエラーなので-1を返す。
    begin
        result:=-1;
        exit;
    end; //if
    Counter:=0;
    for I:=0 to N do
    begin
        Counter:=Counter+Trunc( Random(2) );
        if (I mod 100000)=99999 then
        begin
            CallBackProc(Counter/I);
        end; //if
    end; //for
   result:=Counter/N;
end; //function
exports    //ここに列挙したものが外部に公開される。
        TossingCoin,TossingCoinCallBack;
(*ここまで↑*)


begin
end
.

コンパイル

[コンパイルの仕方の説明画像]第一回と同様コンパイルします。
もし、ミスがあると、『エラー』『警告』『ヒント』が出ますので、修正して下さい。
無事コンパイルが完了すると、プロジェクトを保存したフォルダに『tossing_coin_dll.dll』というファイルができているはずです。
『tossing_coin_dll.dll』システムフォルダにコピーしておいて下さい。(通常、Win9x/MeならC:\System\、Win NT/2k/xpではC:\System32\) 
※後で作成するVisual Basicのプロジェクトと同じフォルダにコピーしてもかまいません。(ただし、VBの開発環境からの実行では、DLLが見つからないと言ってきます。EXEを作成してから試す必要あり)


Visual Basicでのフォームのデザイン

前回のプロジェクトに『ListBox』を一つ追加して下さい。 VBのフォームイメージ

VBでのコーディング

第一回で作成したプロジェクトに一部変更を加えます。APIの宣言など画面幅が足りないと強制的に改行されているので注意して下さい。
(コピペすれば問題ないですが。)

コード(Visual Basic、Form1.frm)

Option Explicit
Private Sub cmdDelphi_Click()
'(変更↓)
  lblDelphi.Caption = TossingCoinDelphiCallBack(10000000, AddressOf CallBackProc)
'(ここまで↑)

End Sub

Private Sub cmdVB_Click()
  lblVB.Caption = TossingCoinVB(10000000)
End Sub



コード(Visual Basic、mdlTossingCoin.bas)

Option Explicit
Public Declare Function TossingCoinDelphi Lib "tossing_coin_dll.dll" Alias "TossingCoin" (ByVal N As Long) As Double

'(ここから↓)
Public Declare Function TossingCoinDelphiCallBack Lib "tossing_coin_dll.dll" Alias "TossingCoinCallBack" (ByVal N As Long, ByVal CallBack As Long) As Double

Public Sub CallBackProc(ByVal D As Double)
  Form1.List1.AddItem (D)
  DoEvents
End Sub
'(ここまで↑)


Public Function TossingCoinVB(N As Long) As Double
Dim I As Long '変数の宣言
Dim Counter As Long
  If N <= 0 Then 'ゼロ以下の時はエラーなので-1を返す。
    TossingCoinVB = -1
    Exit Function
  End If
  Counter = 0 'Counterを初期化。VBでは無くても良い(自動的に初期化される)
  For I = 0 To N
   Counter = Counter + Int(Rnd() * 2)
  Next I
  TossingCoinVB = Counter / N
End Function

実行

 コードの入力が終わったら、早速実行してみましょう。『List1』に途中経過が追加されていくと思います。
 DelphiからVBのサブルーチンを呼び出すというのは、何か不思議な感じですね。結局コンパイラが吐き出すのは、どれも同じ機械語ということが実感できたかと思います。


次回はインラインアッセンブラについて書きたいと思います。

<目次へ> <第一回へ> <第三回へ>