Word VBA で自作クラスを別プロジェクトから呼び出したときに「型が一致しません(実行時エラー 13)」が発生し、原因が分からず長時間ハマってしまうケースは少なくありません。とくに正規表現の Match オブジェクトやクラスモジュールを組み合わせると、VBA 特有の「括弧ルール」と「既定プロパティ」が絡み合い、見た目は正しいコードでもエラーになることがあります。この記事では、「Sub 呼び出し+括弧」で Match オブジェクトが文字列に化けてしまう典型パターンを題材に、原因の仕組みと実践的な対処法、再発防止のためのコーディングパターンを詳しく解説します。
状況整理:Word VBA で正規表現 Match を保持するクラスを別プロジェクトから利用
まずは、どのような環境・コードでエラーが出るのかを整理しておきます。
| 項目 | 内容 |
|---|---|
| OS / Office | Windows 11 Pro / Office 2019(Word VBA) |
| 目的 | 正規表現の Match を「文書中の出現位置順」に保持するクラスを作成 |
| クラス内部 | Object 配列に Match を格納し、FirstIndex を基準に挿入位置を決定 |
| クラスの公開方法 | Instancing = PublicNotCreatable で別プロジェクトから利用可能に設定 |
| インスタンス生成 | Application.Run("GlobalSupportFunctions.NewMatchArray") でクラスインスタンスを取得 |
| エラーが出る行 | MyArray.Insert (REMatches(I - 1)) を実行すると、実行時エラー 13「型が一致しません」 |
いかにも「Match 型を Object に入れているから?」と疑いたくなりますが、実はもっと根本的な原因があります。それが、VBA の引数と括弧のルールです。
エラーの正体:Sub を Call なしで括弧付き呼び出し → 既定プロパティに暗黙変換
問題のコードをもう一度見てみましょう。
MyArray.Insert (REMatches(I - 1))
Insert はクラスのメソッドで、定義はおおよそ次のようになっているとします。
Public Sub Insert(ByRef NewItem As Object)
' NewItem.FirstIndex を見て挿入位置を決める
End Sub
いかにも「Match を Object で受けているので何でも渡せる」ように見えますが、ここに落とし穴があります。VBA では、
- Sub を Call なしで呼ぶときに、引数全体を括弧で囲むと
- 括弧内は「式」として評価されてから渡される
- オブジェクトが式評価されると、既定プロパティ(Default Property)に暗黙的に置き換えられることがある
という仕様があります。VBScript.RegExp の Match オブジェクトの場合、その既定プロパティは .Value です。つまり、
MyArray.Insert (REMatches(I - 1))
は、VBA の解釈としてはだいたい次のような動きをします。
REMatches(I - 1)はMatchオブジェクト- しかし括弧が付いているため、「式」として評価される
- 式評価の過程で、オブジェクトは既定プロパティに展開される
Matchの既定プロパティは.Value(文字列)- 結果として
InsertのNewItemには文字列が渡される
一方、Insert の実装では、
NewItem.FirstIndexを参照したりTypeOf NewItem Is Matchのようなチェックをしたり
といった処理を行っていると、文字列型に対して存在しないプロパティを参照しようとすることになり、その時点か、あるいは引数束縛のタイミングで「型が一致しません(実行時エラー 13)」になります。
実際、「Insert に入る前で止まる」ように見えることが多いのは、引数が Sub の側に渡される前の段階で評価に失敗しているからです。
VBA の括弧ルールまとめ(Sub / Function 呼び出し)
理解を整理するために、VBA の代表的な呼び出しパターンを表にしておきます。
| 書き方 | 意味 | 括弧内の扱い |
|---|---|---|
Result = MyFunc(arg1, arg2) | Function 呼び出し | 引数はそのまま渡される(式ではない) |
MySub arg1, arg2 | Sub 呼び出し(Call なし) | 引数はそのまま渡される(括弧を付けないのが正しい書き方) |
MySub (arg1) | Sub 呼び出し(Call なし・単一引数に括弧) | 括弧内は式として評価される。オブジェクトなら既定プロパティが評価されることがある |
Call MySub(arg1, arg2) | Sub 呼び出し(Call あり) | 括弧を付けるのが正しい。引数はそのまま渡される |
今回のケースは、「Sub 呼び出し+括弧付き」の典型的な落とし穴にそのまま引っかかっている形になります。
解決策 1:括弧を外してそのまま渡す(最小修正)
もっともシンプルな対処は、単に引数の括弧を外すことです。
' NG(既定プロパティ .Value が渡ってしまう)
MyArray.Insert (REMatches(I - 1))
' OK(Match オブジェクトそのものが渡る)
MyArray.Insert REMatches(I - 1)
括弧を外すと、VBA は「Sub 呼び出しの引数」としてそのまま Match オブジェクトを NewItem に渡します。これだけで、エラーは解消されます。
この「括弧を付けない書き方」は、見た目こそ少し古風に感じるかもしれませんが、VBA の正しい文法です。Sub を呼ぶときは、基本的に次のように覚えておくと安全です。
- Sub を Call なしで呼ぶときは、引数リスト全体を括弧で囲まない
- 括弧を付けたい場合は
Callを併用する
解決策 2:Call を使って括弧を許可する
どうしても括弧付きで統一したい場合は、素直に Call を使うのが安全です。
' OK:Call を使えば括弧付きでそのまま渡せる
Call MyArray.Insert(REMatches(I - 1))
Call を付けると、「これは Sub 呼び出しだ」とコンパイラがはっきり認識し、括弧内は「式」ではなく「引数リスト」として扱われます。そのため、REMatches(I - 1) で得られた Match オブジェクトが、そのまま NewItem に渡されます。
一部では「Call は古い書き方だから使わないほうがいい」という意見もありますが、実際には今でも現役で使われていますし、括弧の挙動を安定させるという意味ではむしろ実用的です。チームでコーディングスタイルを揃える場合は、
- 「Sub 呼び出しは
Callを使わないが、括弧も付けない」 - 「Sub 呼び出しは必ず
Callを使い、括弧で囲む」
のどちらかに統一することをおすすめします。
解決策 3:Match 型で引数を受けるようにしてコンパイル時に型チェック
今回のようなトラブルの根本的な原因は、
- 引数の型が
Objectのため、文字列が渡ってきてもコンパイルエラーにならない - 既定プロパティを経由して型がすり替わっても、コンパイラが検出できない
という点にあります。これを防ぐために、クラス側の引数をより厳密な型にしておくのが理想です。
VBScript 正規表現に参照設定を追加する
まず、Word VBA の [ツール] → [参照設定] から、
- Microsoft VBScript Regular Expressions 5.5
にチェックを入れてください。これで VBA が RegExp、Match、MatchCollection などの型を理解できるようになります(いわゆる「早期バインディング」)。
Insert メソッドのシグネチャを Match 型に変更
参照設定を追加したら、クラス側を次のように修正します。
Option Explicit
' 参照設定済みなので、Match 型が使える
Public Sub Insert(ByRef NewItem As Match)
' NewItem.FirstIndex などに安全にアクセスできる
End Sub
これで、Insert に文字列を渡そうとするとコンパイル時点でエラーになります。また、MyArray.Insert (REMatches(I - 1)) のようなコードを書いた場合も、
- 括弧によって既定プロパティ
.Valueが評価され、文字列型になる - しかし
Insertの引数はMatch型のため、コンパイルエラーが発生
という流れになり、実行時エラーではなくビルド時に問題に気づけるようになります。
早期バインディングのメリット
正規表現に限らず、参照設定を入れて早期バインディングすることで、次のようなメリットがあります。
| 項目 | 早期バインディング | 遅延バインディング(Variant / Object) |
|---|---|---|
| 型チェック | コンパイル時に型エラーが検出される | 実行時にならないとエラーが分からない |
| IntelliSense(補完) | プロパティ・メソッドが候補に出る | 出ない(記憶頼み) |
| 実行速度 | 一般に速い | 呼び出し解決の分だけオーバーヘッド |
| 移植性 | バージョン差の影響を受けることがある | バージョン差に比較的強い |
今回のように「既定プロパティに化けて型がすり替わる」パターンは、早期バインディング+厳密な引数型定義でかなり防止できます。
解決策 4:防御的コーディングで誤用を即座に検知する
既存コードとの兼ね合いなどで、どうしても引数を Object のままにしておきたい場合は、防御的コーディングを追加することで誤用を早期に検出できます。
Public Sub Insert(ByRef NewItem As Object)
' 型チェック(Match 以外は受け付けない)
If Not TypeOf NewItem Is Match Then
Err.Raise vbObjectError + 1, _
"MatchArray.Insert", _
"Insert には Match オブジェクトを渡してください。TypeName=" & TypeName(NewItem)
End If
Debug.Print "受け取った型: "; TypeName(NewItem)
' ここから本来の挿入処理
' ...
End Sub
こうしておくと、誤って文字列や別の型が渡された場合でも、
- 自前のわかりやすいメッセージで止めることができる
- イミディエイトウィンドウに渡された型を表示し、原因を素早く特定できる
というメリットがあります。特に、複数のプロジェクトや複数人でコードを共有している場合は、外から見て「どう呼べばいいか」がはっきり分かるエラーメッセージを用意しておくと、後から参加した人にも優しい実装になります。
MatchCollection とインデックスの扱い:0 始まりに注意
質問のコードにも出てきましたが、VBScript の MatchCollection は 0 始まりです。つまり、最初の要素は Matches(0) になります。
ループ変数を 1 始まりで回したい場合は、次のような書き方になります。
Dim I As Long
Dim REMatches As MatchCollection
For I = 1 To REMatches.Count
' I は 1 〜 Count
' MatchCollection は 0 始まりなので I - 1
MyArray.Insert REMatches(I - 1)
Next I
この I - 1 というインデックス調整自体は正しいので、今回のエラーはあくまで 括弧の付け方による型変換が原因だとわかります。
もし 0 始まりで問題なければ、単純に次のように書くこともできます。
Dim I As Long
For I = 0 To REMatches.Count - 1
MyArray.Insert REMatches(I)
Next I
あるいは、MatchCollection は For Each に対応しているので、位置順に処理するだけであれば、次のようにシンプルに書くのもおすすめです。
Dim M As Match
For Each M In REMatches
MyArray.Insert M
Next M
このように インデックス計算を減らすことも、バグの混入を防ぐ有効な手段です。
クラス設計のイメージ:Match を出現位置順に保持するコレクション
今回のクラスは「Match オブジェクトを FirstIndex 順に保持したい」という要件でした。典型的な実装イメージを少しだけ整えておきます。
Option Explicit
Private Matches() As Match
Private Count As Long
Public Sub Initialize()
ReDim Matches(0 To 0)
Count = 0
End Sub
Public Sub Insert(ByRef NewItem As Match)
Dim Pos As Long
Dim I As Long
' 挿入位置を探す(先頭から順に FirstIndex を比較)
Pos = Count
For I = 0 To Count - 1
If NewItem.FirstIndex < Matches(I).FirstIndex Then
Pos = I
Exit For
End If
Next I
' 配列を拡張
ReDim Preserve Matches(0 To Count)
' 挿入位置以降を後ろにずらす
For I = Count - 1 To Pos Step -1
Set Matches(I + 1) = Matches(I)
Next I
' 挿入
Set Matches(Pos) = NewItem
Count = Count + 1
End Sub
Public Function Item(ByVal Index As Long) As Match
Set Item = Matches(Index)
End Function
Public Property Get Length() As Long
Length = Count
End Property
このようにしておけば、呼び出し側では次のように自然なコードで扱えます。
Dim MyArray As GlobalSupport.MatchArray ' 参照設定前提
Dim I As Long
Set MyArray = Application.Run("GlobalSupportFunctions.NewMatchArray")
For I = 0 To REMatches.Count - 1
MyArray.Insert REMatches(I)
Next I
' 挿入済みの Match を確認
For I = 0 To MyArray.Length - 1
Debug.Print MyArray.Item(I).FirstIndex, MyArray.Item(I).Value
Next I
ここでも、Insert の引数を Match 型にしておくことが、バグ防止に大いに貢献します。
Application.Run と Instancing = PublicNotCreatable のポイント
今回のケースでは、クラスモジュールの Instancing プロパティを PublicNotCreatable に設定し、別プロジェクトから Application.Run を使ってインスタンス生成しています。このときのポイントも押さえておきましょう。
PublicNotCreatable クラスを返す関数の例
公開側プロジェクトには、次のような関数を用意しているイメージです。
' GlobalSupportFunctions モジュール(公開側プロジェクト)
Option Explicit
Public Function NewMatchArray() As GlobalSupport.MatchArray
Dim Obj As GlobalSupport.MatchArray
Set Obj = New GlobalSupport.MatchArray
Set NewMatchArray = Obj
End Function
この関数を別プロジェクトから呼び出すときに、
Dim MyArray As Object
Set MyArray = Application.Run("GlobalSupportFunctions.NewMatchArray")
のように Object で受け取ることもできますが、できれば参照設定を行って型を明示することをおすすめします。
' 参照設定済みなら具体的な型で受け取れる
Dim MyArray As GlobalSupport.MatchArray
Set MyArray = Application.Run("GlobalSupportFunctions.NewMatchArray")
こうしておけば、MyArray のメソッド・プロパティに IntelliSense が効き、呼び出し側のミスも減らせます。
Application.Run を多用しない設計も検討する
また、可能であれば Application.Run を減らし、
- VBA プロジェクトに参照設定を追加して直接クラスを利用する
- 共通ライブラリ的なプロジェクトを 1 つ作り、そこに正規表現クラスなどをまとめる
といった構成を取ると、
- 型安全性が高まり、今回のような「遅延バインディング+Object」が絡むバグを減らせる
- デバッグの際に、定義元へ簡単にジャンプできる
というメリットがあります。どうしても Application.Run が必要なときだけに限定し、それ以外は早期バインディングを基本とするのが、長期的に保守しやすい方針です。
VBA の括弧ルールをもう少し深掘りする
せっかくなので、今回のようなエラーを二度と出さないために、VBA の括弧ルールを少しだけ深掘りしておきます。
Sub 呼び出しの代表的な落とし穴
次のコードを見比べてください。
Sub SampleSub(ByVal X As Long)
Debug.Print X
End Sub
Sub Test()
Dim N As Long
N = 10
SampleSub N ' 10 が渡る
SampleSub (N) ' 10 が渡る(この場合は見た目の違いだけ)
End Sub
この程度の単純な型だと動作に違いはありませんが、引数が ByRef だったり、オブジェクトだったりすると挙動が変わります。
Sub SampleSub2(ByRef X As Long)
X = X + 1
End Sub
Sub Test2()
Dim N As Long
N = 10
SampleSub2 N ' N は 11 になる
SampleSub2 (N) ' N は 11 のまま(式としての N+0 が渡されるイメージ)
End Sub
オブジェクトでも同様です。
Sub ShowLength(ByVal S As String)
Debug.Print Len(S)
End Sub
Sub Test3()
Dim R As Object
Set R = CreateObject("VBScript.RegExp")
R.Pattern = "\w+"
' R は RegExp オブジェクト
ShowLength R ' ここで既定プロパティ(.Pattern など)が評価される可能性がある
ShowLength (R) ' さらに「式」として扱われ、暗黙変換がかかる余地が増える
End Sub
このような細かな挙動の違いが、今回のような「Match を渡したつもりが文字列に化ける」トラブルにつながります。結論としては、
- Sub を Call なしで呼ぶときは、引数の括弧は付けない
- 関数を代入文や
Debug.Printなどの式で呼び出すときは、引数の括弧を付けてよい(というか普通に付ける) - Sub を括弧付きで呼びたいなら、
Callを使う
というルールを守れば、ほとんどのトラブルを回避できるはずです。
実務で役立つチェックリスト
最後に、今回のようなエラーに遭遇したときに確認すべきポイントをチェックリスト形式でまとめておきます。
| チェック項目 | 確認内容 |
|---|---|
| Sub 呼び出しの括弧 | Call なしの Sub 呼び出しで、引数全体を括弧で囲んでいないか? |
| 引数の型 | Object で受けていないか? 可能なら具体的な型(Match など)で受けているか? |
| 既定プロパティ | 渡しているオブジェクトに既定プロパティがあり、それが暗黙に評価されていないか? |
| 防御的コーディング | TypeOf ... Is ... による型チェックや、自前のエラーメッセージを入れているか? |
| 参照設定 | 利用している COM ライブラリ(VBScript Regular Expressions 5.5 など)に参照設定を入れているか? |
| インデックス | MatchCollection のようなコレクションで、0 始まりと 1 始まりを取り違えていないか? |
まとめ:VBA の括弧ルールを味方につけて実行時エラー 13 を防ぐ
この記事で扱った「別プロジェクトの VBA クラス呼び出しで『型が一致しません(実行時エラー 13)』」という問題は、突き詰めると次のポイントに集約されます。
- Sub を Call なしで呼ぶときに引数を括弧で囲むと、括弧内は「式」として評価される
- オブジェクトが式評価されると、既定プロパティ(
Matchなら.Value)に暗黙変換されることがある - その結果、本来の
Matchオブジェクトではなく文字列が渡され、Insert メソッド内や引数束縛の時点で型不一致になる
そして具体的な解決策としては、
- 括弧を外して呼ぶ:
MyArray.Insert REMatches(I - 1) Callを使って括弧を許可する:Call MyArray.Insert(REMatches(I - 1))- 引数を
Match型にしてコンパイル時に型チェックさせる TypeOfやカスタムエラーで防御的コーディングを入れる
の 4 つを紹介しました。特に、正規表現やクラスモジュールを多用する大規模な Word VBA マクロでは、「早期バインディング+厳密な引数型+防御的コーディング」をセットで導入しておくと、後々のトラブルを大きく減らせます。
もし「Match をそのまま渡しているはずなのに、なぜか型が一致しない」といった現象に遭遇したら、まずは今回のように、Sub の呼び出し方と括弧の有無を疑ってみてください。たった 1 文字の括弧が、実行時エラー 13 の原因になっているかもしれません。

コメント