概要
Visual Basic for Application (VBA) において、手続きをパラメータとして引き渡す方法、いわゆるコールバック関数の実装方法を解説します。
クラスメソッドを作る
VBAでは手続きをポインタなどで扱う仕組みがありません。そこでまず、クラスモジュールにメソッドを定義してやり、インスタンスオブジェクトからメソッドをコールするという手順でコールバック関数を実装することにします。
Option Explicit
Sub callSub(ParamArray prms())
' メソッドの名前
Debug.Print "Callback1.callSub"
' 引数の一覧
Dim it, prmsStr As String
prmsStr = " prms = "
For Each it In prms
prmsStr = prmsStr & it & ", "
Next
Debug.Print prmsStr
End Sub
Option Explicit
Sub test()
Dim cbObj As New Callback1
executeCallback cbObj
End Sub
Sub executeCallback(callbackObj)
' Variant型から"callSub"を呼び出す
callbackObj.callSub 1, "α"
End Sub
VBEのイミディエイトウィンドウでModule1.test
を実行した結果。
Module1.test
Callback1.callSub
prms = 1, α,
CallByName命令を使う
前節の例では、executeCallback
に渡されたオブジェクトに対してメソッド名を決め打ちで呼び出すことしか出来ませんが、CallByName命令を使うと、オブジェクトとメソッド名の両方をパラメータとして渡すことができます。
Option Explicit
Sub test()
Dim cbObj As New Callback1
executeCallback Array(cbObj, "callSub")
End Sub
Sub executeCallback(callbackObj)
' 配列からオブジェクトと、メソッド名を取り出して呼び出す
CallByName callbackObj(0), callbackObj(1), VbMethod, 2, "β"
End Sub
Module2.test
Callback1.callSub
prms = 2, β,
インタフェースクラスを用いる
VBAはオブジェクト指向のインタフェースの仕組みをサポートしているため、これを用いてタイプセーフなコールバックを実装することもできます。
まず、インタフェースクラスとしてICallback
を作成し、メソッド名のみを定義します。
Option Explicit
Sub callSub(ParamArray prms())
End Sub
インタフェースクラスの継承にはImplements
ステートメントを使います。callSubの定義をCallback2
へ実装した例を下記に示します。
Option Explicit
Implements ICallback
Private Sub ICallback_callSub(ParamArray prms())
' メソッドの名前
Debug.Print "Callback2.ICallback_callSub"
' 引数の一覧
Dim it, prmsStr As String
prmsStr = " prms = "
For Each it In prms
prmsStr = prmsStr & it & ", "
Next
Debug.Print prmsStr
End Sub
インタフェースのメソッドをオーバーライドするにはImplements
したクラス名と、メソッド名をアンダーバー(_)で組み合わせて下記のように定義する必要があります。
クラス名_メソッド名
Callback2
はICallback
を継承しているため、Callback2
型のオブジェクトをICallback
型の仮引数に渡しても、型チェックに合格します。
Option Explicit
Sub test()
Dim cbObj As New Callback2
executeCallback cbObj
End Sub
Sub executeCallback(callbackObj As ICallback)
' ICallback型オブジェクトから"callSub"を呼び出す
callbackObj.callSub 3, "γ"
End Sub
Module3.test
Callback2.ICallback_callSub
prms = 3, γ,
オーバーライドしたメソッド名を隠蔽する
Callback2
の定義は公開メソッド名がICallback_callSub
となり不格好なので、これを隠蔽して、callSub
を公開メソッド名となるように改良します。
Option Explicit
Implements ICallback
' ICallback型のプライベート関数
Private Sub ICallback_callSub(ParamArray prms())
Debug.Print "Callback3.ICallback_callSub"
substance prms
End Sub
' Callback3型のグローバル関数
Public Sub callSub(ParamArray prms())
Debug.Print "Callback3.callSub"
substance prms
End Sub
' 処理の実体
Private Sub substance(ByVal prms)
' メソッドの名前
Debug.Print "Callback3.substance"
' 引数の一覧
Dim it, prmsStr As String
prmsStr = " prms = "
For Each it In prms
prmsStr = prmsStr & it & ", "
Next
Debug.Print prmsStr
End Sub
オーバーライドしたICallback_callSub
メソッドをPrivate
指定で隠蔽し、代わりにcallSub
メソッドをPublic
指定で公開しています。こうするとCallback3
型のオブジェクトからはcallSub
しか公開されず、更に同じインスタンスでもICallback
型にキャストされるとちゃんとICallback_callSub
を呼び出すことができます。
Option Explicit
Sub test()
Dim cbObj As New Callback3
executeCallback cbObj
' Callback3型から"callSub"を呼び出す
cbObj.callSub 4.2, "δ-2"
End Sub
Sub executeCallback(callbackObj As ICallback)
' ICallback型から"callSub"を呼び出す
callbackObj.callSub 4.1, "δ-1"
End Sub
Module4.test
Callback3.ICallback_callSub
Callback3.substance
prms = 4.1, δ-1,
Callback3.callSub
Callback3.substance
prms = 4.2, δ-2,