LoginSignup
29
35

VBAでコールバック関数を実装

Last updated at Posted at 2016-07-03

概要

Visual Basic for Application (VBA) において、手続きをパラメータとして引き渡す方法、いわゆるコールバック関数の実装方法を解説します。

クラスメソッドを作る

VBAでは手続きをポインタなどで扱う仕組みがありません。そこでまず、クラスモジュールにメソッドを定義してやり、インスタンスオブジェクトからメソッドをコールするという手順でコールバック関数を実装することにします。

Callback1(クラスモジュール)
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
Module1(標準モジュール)
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命令を使うと、オブジェクトとメソッド名の両方をパラメータとして渡すことができます。

Module2(標準モジュール)
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を作成し、メソッド名のみを定義します。

ICallback(クラスモジュール)
Option Explicit

Sub callSub(ParamArray prms())
End Sub

インタフェースクラスの継承にはImplementsステートメントを使います。callSubの定義をCallback2へ実装した例を下記に示します。

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したクラス名と、メソッド名をアンダーバー(_)で組み合わせて下記のように定義する必要があります。

クラス名_メソッド名

Callback2ICallbackを継承しているため、Callback2型のオブジェクトをICallback型の仮引数に渡しても、型チェックに合格します。

Module3(標準モジュール)
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を公開メソッド名となるように改良します。

Callback3(クラスモジュール)
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を呼び出すことができます。

Module4(標準モジュール)
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, 
29
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
35