Word VBAで別プロジェクトのクラス呼び出し時に発生する「型が一致しません(実行時エラー13)」の原因と対処法

Word VBA で自作クラスを別プロジェクトから呼び出したときに「型が一致しません(実行時エラー 13)」が発生し、原因が分からず長時間ハマってしまうケースは少なくありません。とくに正規表現の Match オブジェクトやクラスモジュールを組み合わせると、VBA 特有の「括弧ルール」と「既定プロパティ」が絡み合い、見た目は正しいコードでもエラーになることがあります。この記事では、「Sub 呼び出し+括弧」で Match オブジェクトが文字列に化けてしまう典型パターンを題材に、原因の仕組みと実践的な対処法、再発防止のためのコーディングパターンを詳しく解説します。

目次

状況整理:Word VBA で正規表現 Match を保持するクラスを別プロジェクトから利用

まずは、どのような環境・コードでエラーが出るのかを整理しておきます。

項目内容
OS / OfficeWindows 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.RegExpMatch オブジェクトの場合、その既定プロパティは .Value です。つまり、

MyArray.Insert (REMatches(I - 1))

は、VBA の解釈としてはだいたい次のような動きをします。

  1. REMatches(I - 1)Match オブジェクト
  2. しかし括弧が付いているため、「式」として評価される
  3. 式評価の過程で、オブジェクトは既定プロパティに展開される
  4. Match の既定プロパティは .Value(文字列)
  5. 結果として InsertNewItem には文字列が渡される

一方、Insert の実装では、

  • NewItem.FirstIndex を参照したり
  • TypeOf NewItem Is Match のようなチェックをしたり

といった処理を行っていると、文字列型に対して存在しないプロパティを参照しようとすることになり、その時点か、あるいは引数束縛のタイミングで「型が一致しません(実行時エラー 13)」になります。

実際、「Insert に入る前で止まる」ように見えることが多いのは、引数が Sub の側に渡される前の段階で評価に失敗しているからです。

VBA の括弧ルールまとめ(Sub / Function 呼び出し)

理解を整理するために、VBA の代表的な呼び出しパターンを表にしておきます。

書き方意味括弧内の扱い
Result = MyFunc(arg1, arg2)Function 呼び出し引数はそのまま渡される(式ではない)
MySub arg1, arg2Sub 呼び出し(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 が RegExpMatchMatchCollection などの型を理解できるようになります(いわゆる「早期バインディング」)。

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 の MatchCollection0 始まりです。つまり、最初の要素は 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 メソッド内や引数束縛の時点で型不一致になる

そして具体的な解決策としては、

  1. 括弧を外して呼ぶ:MyArray.Insert REMatches(I - 1)
  2. Call を使って括弧を許可する:Call MyArray.Insert(REMatches(I - 1))
  3. 引数を Match 型にしてコンパイル時に型チェックさせる
  4. TypeOf やカスタムエラーで防御的コーディングを入れる

の 4 つを紹介しました。特に、正規表現やクラスモジュールを多用する大規模な Word VBA マクロでは、「早期バインディング+厳密な引数型+防御的コーディング」をセットで導入しておくと、後々のトラブルを大きく減らせます。

もし「Match をそのまま渡しているはずなのに、なぜか型が一致しない」といった現象に遭遇したら、まずは今回のように、Sub の呼び出し方と括弧の有無を疑ってみてください。たった 1 文字の括弧が、実行時エラー 13 の原因になっているかもしれません。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次