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

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

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

```vb: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
```

```vb: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`を実行した結果。

```text:イミディエイト
Module1.test
Callback1.callSub
  prms = 1, α, 
```

# CallByName命令を使う
前節の例では、`executeCallback`に渡されたオブジェクトに対してメソッド名を決め打ちで呼び出すことしか出来ませんが、[CallByName](https://msdn.microsoft.com/ja-jp/library/office/gg278760(v=office.15).aspx)命令を使うと、オブジェクトとメソッド名の両方をパラメータとして渡すことができます。

```vb: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
```

```text:イミディエイト
Module2.test
Callback1.callSub
  prms = 2, β, 
```

# インタフェースクラスを用いる
VBAはオブジェクト指向のインタフェースの仕組みをサポートしているため、これを用いてタイプセーフなコールバックを実装することもできます。
まず、インタフェースクラスとして`ICallback`を作成し、メソッド名のみを定義します。

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

Sub callSub(ParamArray prms())
End Sub
```

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

```vb: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`型の仮引数に渡しても、型チェックに合格します。

```vb: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
```
```text:イミディエイト
Module3.test
Callback2.ICallback_callSub
  prms = 3, γ, 
```

# オーバーライドしたメソッド名を隠蔽する
`Callback2`の定義は公開メソッド名が`ICallback_callSub`となり不格好なので、これを隠蔽して、`callSub`を公開メソッド名となるように改良します。

```vb: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`を呼び出すことができます。

```vb: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
```

```text:イミディエイト
Module4.test
Callback3.ICallback_callSub
Callback3.substance
  prms = 4.1, δ-1, 
Callback3.callSub
Callback3.substance
  prms = 4.2, δ-2, 
```