[VBA]広域変数を使用せずに、EnumChildWindowsの結果を取得する

Win32APIの[`EnumChildWindows`](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows "EnumChildWindows function (winuser.h) - Win32 apps | Microsoft Docs")の列挙した結果を、広域変数を使用せずに取得する方法のメモです。

# EnumChildWindows について

任意のウィンドウに対して、その子どものウィンドウを取得するための関数で、以下のようなシグネチャとなっています。

> ```cpp:ref_EnumChildWindows
/* 返り値は使用されない。 */
BOOL EnumChildWindows(
  HWND        hWndParent /* 親ウィンドウのハンドル。0 の場合は EnumWindows と同等。 */,
  WNDENUMPROC lpEnumFunc /* 各列挙で実行される関数へのポインタ。 */,
  LPARAM      lParam /* 任意の追加情報。 */
);
```
https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows
より引用(コメントは記事の作成者による)

`lpEnumFunc`で指定するコールバック関数`WNDENUMPROC`は、以下のようなシグネチャとなっています。

> ```cpp:ref_EnumChildProc
/* 列挙を続行する場合は TRUE を返す。 */
BOOL CALLBACK EnumChildProc(
  _In_ HWND   hwnd /* 見つかったウィンドウのハンドル。 */,
  _In_ LPARAM lParam /* EnumChildWindows で指定された追加情報。 */
);
```
https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/ms633493(v=vs.85)
より引用(コメントは記事の作成者による)

## よく見る使われ方と解決したい問題

上記のシグネチャからもわかるように、[`EnumChildWindows`](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows "EnumChildWindows function (winuser.h) - Win32 apps | Microsoft Docs")では、直接列挙された結果を受け取れるのではなく、コールバック関数の方に結果が渡されるという構成になっています。

そのため、結果に対して何かしら処理をするために以下のような実装がされていることが多いです。

1. `EnumChildProc`内に直接処理を書く
2. モジュール変数(広域変数)に結果を格納し、モジュール変数経由で受け取る 

1の方法では、その時々に応じて中のコードを書き直す必要があり、やや再利用がしにくいです。
2の方法では、1に比べて再利用しやすいですが、広域変数を使っているのがなんとなく不安になるところです。

上記の問題を解決する方法として、`lParam`の引数を使う、というのがこの記事の趣旨になります。

`lParam`ですがポインタを示す引数となっているため、VBA では任意の変数を`ByRef`で渡したり、オブジェクトを`ByVal`で渡してもシグネチャ的に問題ありません。

`ByRef`の場合はその変数へのポインタが関数に渡されますし、オブジェクトを`ByVal`で渡した場合はオブジェクト本体への参照アドレスが渡されるためです。

そのため、`lParam`経由で`VBA.Collection`などを渡せば、広域変数を介さず結果を取得することができます。

# 実装例

VBA7 以降を想定したコードです。
いまどき居ないとは思いますが、VBA6 以前を使用されている場合は`LongPtr`を`Long`に変更し、`PtrSafe`を消してください。

```vb
'https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows
Private Declare PtrSafe Function _
    EnumChildWindows Lib "user32.dll" ( _
        ByVal hWndParent As LongPtr, _
        ByVal lpEnumFunc As LongPtr, _
        ByVal lParam As VBA.Collection _
    ) As Long 'No use.

'https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)
Private Function EnumChildProc( _
        ByVal hWnd As LongPtr, _
        ByVal lParam As VBA.Collection _
    ) As Boolean 'If True Then Continue.
    
    lParam.Add hWnd
    Let EnumChildProc = True '列挙続行。
End Function

'inParentHwnd で指定したウィンドウの子ウィンドウのハンドルを格納した VBA.Collection を取得する。
    'inParentHwnd を省略した場合はトップレベルウィンドウのハンドルを取得する。
Public Function GetChildWindows(Optional inParentHwnd As LongPtr = 0) As VBA.Collection 'Of LongPtr
    Dim c As VBA.Collection 'Of LongPtr
    Set c = New VBA.Collection
    Set GetChildWindows = c
    
    Call EnumChildWindows(inParentHwnd, AddressOf EnumChildProc, c)
    
End Function
```

# 参考

> [EnumChildWindows function (winuser.h) - Win32 apps | Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows "EnumChildWindows function (winuser.h) - Win32 apps | Microsoft Docs")
> [EnumChildProc callback function (Windows) | Microsoft Docs](https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/ms633493(v=vs.85) "EnumChildProc callback function (Windows) | Microsoft Docs")

# 関連記事

[VBAでWindowsAPIを使うには - Qiita](https://qiita.com/nukie_53/items/6a88ce82565e238a3c71 "VBAでWindowsAPIを使うには - Qiita")