はじめに
Excelのタイプライブラリでは、Range
やCells
といったセルへの参照用のプロパティがグローバルで公開されています。
これらを使うと簡単にセルにアクセス出来るため、初心者にとってはとっつきやすく、慣れてからでもちょっとした処理をしたい場合に便利です。
しかし「ちょっとした処理」では済まない処理、例えば複数のシートにまたがった処理をする場合には、これらのグローバルなプロパティへの参照はバグの温床となります。
この記事は、そのグローバルなプロパティを参照している場所を簡単に見つけるアイディアのメモです。
サンプル:Range系プロパティ
まずは具体的なサンプルを確認します。
あらかじめチェックしたいプロジェクトでコンパイルを行っておき、コンパイルエラーが起きていないことを確認しておきます。
その上で、以下のコードを標準モジュールに追加し、再度コンパイルを行います。
この時エラーになる箇所がグローバルなプロパティ(以下のコードだとCells
・Columns
・Range
・Rows
)を参照している場所となります。
'標準モジュール
Option Explicit
Option Private Module
'Property override
Public Sub Cells(): End Sub
Public Sub Columns(): End Sub
Public Sub Range(): End Sub
Public Sub Rows(): End Sub
チェックが終わったら上記コードは消しておきます。
手順まとめ
- コンパイルしてエラーを無くす
- 標準モジュールに上記コード追加
- 再度コンパイルしてエラーとなる場所を直す
- コンパイルしてエラーが出なくなったら、追加したコードを削除
やっていること
上記のコードで行っていることは、グローバルなメンバーの上書き、となります。
VBAでは対象を明示しない場合、以下の順に識別子の解決が行われます(経験則です)。
- プロシージャ内の識別子(変数・定数など)
- 同じモジュール内の識別子(変数・定数・プロシージャ…)
- 同じプロジェクト内の識別子(同上)
- 他のライブラリのグローバルなメンバー(参照設定の上から順)
Excel上のVBAの場合、Range
は4の「他のライブラリ(Excel)のグローバルなメンバー」を示しています。
ここで、標準モジュールにPublic Sub Range(): End Sub
のプロシージャを定義すると、「同じプロジェクト内の識別子」となり、ExcelのRange
より優先順位が高くなります。
この時Excel本来のRange
と矛盾する定義にしておくと、コンパイルエラーとして検知される、ということになります。
以下のポイントを抑えると、矛盾する定義にしやすいです。
- プロパティはSubプロシージャにする
- 引数の数を変える
- 返り値の無いメソッドはProperty Getプロシージャにする
注意点
この方法で検知できるのは、「グローバルに公開されているメンバー」かつ「対象を明示しない書き方をしている場合」となります。
グローバルに公開されていないメンバーで問題になりそうな例としては、シートモジュール内のRange
などが挙げられます。
シートモジュール内でRange("A1")
とした場合、それは「2. 同じモジュール内の識別子」となり、常にそのシートのA1セルを示します。
「対象を明示した書き方」というのは、以下のように識別子の前にライブラリ名を付けた書き方です(処理自体に意味は無いです)。この場合も検知できません。
Excel.Union(Excel.Range("A1"), Excel.Cells).Activate
おまけ
外部からExcelを使用する場合
外部のVBAホストからExcelを操作する場合、Excelのグローバルなメンバー全てへの参照を避けたいです。
その場合には、以下のコードで大体は潰せるはずです。
Application
など他のVBAホストでも定義されているものに関しては適宜コメントアウトしてください。
imihito_Sandbox/DisableExcelGlobal.bas at master · imihito/imihito_Sandbox · GitHub
Option Explicit
Option Private Module
'Method override
Public Sub Calculate(a): End Sub
Public Sub DDEExecute(): End Sub
Public Sub DDEInitiate(): End Sub
Public Sub DDEPoke(): End Sub
Public Sub DDERequest(): End Sub
Public Sub DDETerminate(): End Sub
Public Sub Evaluate(): End Sub
Public Sub ExecuteExcel4Macro(): End Sub
Public Sub Intersect(): End Sub
Public Sub Run(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z): End Sub
'Public Sub SendKeys(): End Sub 'VBA has SendKeys method.
Public Sub Union(): End Sub
'Property override
Public Sub Application(): End Sub
Public Sub Creator(): End Sub
Public Sub Parent(): End Sub
Public Sub ActiveCell(): End Sub
Public Sub ActiveChart(): End Sub
Public Sub ActiveDialog(): End Sub
Public Sub ActiveMenuBar(): End Sub
Public Sub ActivePrinter(): End Sub
Public Sub ActiveSheet(): End Sub
Public Sub ActiveWindow(): End Sub
Public Sub ActiveWorkbook(): End Sub
Public Sub AddIns(): End Sub
Public Sub Assistant(): End Sub
Public Sub Cells(): End Sub
Public Sub Charts(): End Sub
Public Sub Columns(): End Sub
Public Sub CommandBars(): End Sub
Public Sub DDEAppReturnCode(): End Sub
Public Sub DialogSheets(): End Sub
Public Sub MenuBars(): End Sub
Public Sub Modules(): End Sub
Public Sub Names(): End Sub
Public Sub Range(): End Sub
Public Sub Rows(): End Sub
Public Sub Selection(): End Sub
Public Sub Sheets(): End Sub
Public Sub ShortcutMenus(): End Sub
Public Sub ThisWorkbook(): End Sub
Public Sub Toolbars(): End Sub
Public Sub Windows(): End Sub
Public Sub Workbooks(): End Sub
Public Sub WorksheetFunction(): End Sub
Public Sub Worksheets(): End Sub
Public Sub Excel4IntlMacroSheets(): End Sub
Public Sub Excel4MacroSheets(): End Sub
生成用スクリプト
上記のコードは、.NET FrameworkのPIAの定義から生成しました。
参考までに、生成に使用したスクリプト(PowerShell)も記載しておきます。
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
[type]$global = [Microsoft.Office.Interop.Excel.GlobalClass]
[Reflection.BindingFlags]$flag = [Reflection.BindingFlags]::Public -bor [Reflection.BindingFlags]::Instance
[Reflection.MemberInfo[]]$members = $global.GetMembers($flag)
[string[]]$procDefs = $members |
? { # remove `ToString()` and others.
$_.DeclaringType -eq $global -and
# remove `get_Application`...,`_Evaluate`,`.ctor`
$_.Name -cnotmatch '^[gs]et_|^[_.]'
} |
% { 'Public Sub {0}():End Sub' -f $_.Name }
# クリップボードにコピー
Add-Type -AssemblyName PresentationFramework
[Windows.ClipBoard]::SetText( $procDefs -join "`r`n" )