Wordを飼い慣らす極限の高速化:COMバウンダリを最小化する全角英数字の半角化とフォント統一のアプローチ
Word VBAを用いたドキュメントの自動整形は、一見すると平易なタスクに思える。しかし、対象が数百ページに及ぶ大規模な仕様書や、基幹システムからエクスポートされた不揃いなドキュメント群となった瞬間、それは牙を剥く。
「愚直なループによる数十分のハングアップ」「COMオブジェクトのリークによるメモリ逼迫」「予期せぬフォント崩れ」。
これらの課題を解決するには、一般的な技術ブログに書かれているような`For Each p In ActiveDocument.Paragraphs`といったナイーブな実装を捨て去らねばならない。本稿では、Wordの内部構造(DOM)とCOM(Component Object Model)のアーキテクチャ、さらにはWin32 APIの活用まで踏み込み、極限まで最適化された「全角英数字の半角置換およびフォント統一」のソリューションを提示する。
—
1. アーキテクチャ設計論:Word VBAにおけるボトルネックの正体
Word VBAの処理が遅くなる最大の要因は、「COMバウンダリ(境界)の横断コスト」と「レイアウトエンジンの再計算」にある。
VBAコード(クライアント)からWordの実体(COMサーバー)のプロパティやメソッドを呼び出すたびに、プロセス間(あるいはスレッド間)通信のオーバーヘッドが発生する。段落内の文字を1文字ずつループで走査して全角・半角を判定するようなコードは、このCOMバウンダリを数万回も往復することになり、致命的なパフォーマンス低下を招く。
さらに、Wordは文字が書き換わるたびにドキュメントのレイアウト(改ページ位置など)をリアルタイムで再計算しようとする。
これらを抑制するためのアーキテクチャ設計指針は以下の3点に集約される。
1. ワイルドカード検索(Findオブジェクト)による一括抽出
ループで文字を走査するのではなく、Wordのネイティブ(C++レイヤー)で実装されている正規表現風の検索エンジンに文字抽出を委ねる。COMバウンダリの往復を最小限に抑えるためである。
2. Win32 API `LCMapStringW` によるメモリ上での超高速文字変換
VBA標準の `StrConv` 関数は内部的にWindowsのAPIを呼び出しているが、VBAのラッパーを介すためオーバーヘッドがある。また、きめ細かな制御を行うため、カーネルAPIである `LCMapStringW` を直接静的バインディングし、メモリ(BSTRバッファ)上で直接変換を行う。
3. 描画・レイアウトエンジンの凍結と明示的メモリ解放
`ScreenUpdating` の停止はもちろんのこと、Undo履歴(元に戻す)の制御、およびCOMオブジェクトの確実なライフサイクル管理(`Set Object = Nothing`)を徹底し、プロセスのクリーンさを維持する。
—
2. Win32 API `LCMapStringW` による高速文字変換の静的定義
全角英数字を半角に変換するために、Windows OSのコアAPIである `LCMapStringW` を利用する。これにより、ロケールに依存した文字変換をミリ秒単位で安全に実行できる。
まずは、32bit/64bit双方のVBA環境(VBA7 / Office 64bit)に対応したAPI宣言部を定義する。
If VBA7 Then
‘ 64bit/32bit Office両対応の宣言
Private Declare PtrSafe Function LCMapStringW Lib “kernel32” ( _
ByVal Locale As Long, _
ByVal dwMapFlags As Long, _
ByVal lpSrcStr As LongPtr, _
ByVal cchSrc As Long, _
ByVal lpDestStr As LongPtr, _
ByVal cchDest As Long) As Long
Else
‘ レガシー32bit Office専用の宣言
Private Declare Function LCMapStringW Lib “kernel32” ( _
ByVal Locale As Long, _
ByVal dwMapFlags As Long, _
ByVal lpSrcStr As Long, _
ByVal cchSrc As Long, _
ByVal lpDestStr As Long, _
ByVal cchDest As Long) As Long
End If
‘ LCMapStringW 用の定数定義
Private Const LOCALE_SYSTEM_DEFAULT As Long = &H800
Private Const LCMAP_HALFWIDTH As Long = &H400000 ‘ 全角から半角への変換フラグ
—
3. 実装:コンプリート・最適化コード
以下に、実務でそのまま利用できる堅牢なマクロコードを示す。
このコードは、ドキュメント内のすべての全角英数字(`[0-9A-Za-z]`)をワイルドカード検索で捕捉し、Win32 APIで高速に半角化したうえで、指定した半角フォント(例: `Arial`)および日本語フォント(例: `MS 明朝`)へと物理的に統一する。
Option Explicit
”’
”’
Public Sub OptimizeDocumentFormatting()
Dim doc As Word.Document
Set doc = ActiveDocument
‘ 1. 実行環境の最適化(描画停止、イベント抑止、Undoバッファの一時停止)
On Error GoTo ErrorHandler
Dim originalScreenUpdating As Boolean
originalScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False
‘ 2. 統一するフォント名の定義
Const TARGET_ASCII_FONT As String = “Arial”
Const TARGET_FAR_EAST_FONT As String = “MS Mincho” ‘ 英語表記推奨(環境依存低減のため)
‘ 3. Findオブジェクトの初期化
‘ Rangeオブジェクトを直接操作し、StoryRanges(本文、ヘッダー、フッター等)を巡回
Dim rng As Word.Range
Set rng = doc.Content
Dim fnd As Word.Find
Set fnd = rng.Find
With fnd
.ClearFormatting
.Replacement.ClearFormatting
‘ 全角の英数字のみをターゲットにするワイルドカード
‘ 0-9:全角数字、A-Z:全角大文字、a-z:全角小文字
.Text = “[0-9A-Za-z]”
.MatchWildcards = True
.Forward = True
.Wrap = wdFindStop
End With
‘ 4. 高速置換ループ処理
‘ ループ内でのCOMオブジェクト生成を徹底的に排除するため、同一のRangeオブジェクトを再利用する
Do While fnd.Execute
‘ マッチしたRangeのテキストをWin32 APIで半角化
Dim originalText As String
originalText = rng.Text
Dim convertedText As String
convertedText = ConvertToHalfWidth(originalText)
‘ テキストの置換
rng.Text = convertedText
‘ フォントの統一(半角英数字用と日本語用のフォントを明示的に指定)
With rng.Font
.NameAscii = TARGET_ASCII_FONT
.NameFarEast = TARGET_FAR_EAST_FONT
End With
‘ Rangeの終点を次の検索開始位置へシフト(無限ループ防止)
rng.Collapse wdCollapseEnd
Loop
‘ 5. 後処理:COMオブジェクトの明示的解放
Set fnd = Nothing
Set rng = Nothing
Set doc = Nothing
‘ 画面描画の復帰
Application.ScreenUpdating = originalScreenUpdating
MsgBox “ドキュメントの整形処理が完了しました。”, vbInformation, “処理完了”
Exit Sub
ErrorHandler:
‘ 異常発生時の確実なリカバリ
Application.ScreenUpdating = True
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
‘ メモリリーク防止のための強制解放
Set fnd = Nothing
Set rng = Nothing
Set doc = Nothing
End Sub
”’
”’
Private Function ConvertToHalfWidth(ByVal source As String) As String
Dim sourceLength As Long
sourceLength = Len(source)
If sourceLength = 0 Then
ConvertToHalfWidth = “”
Exit Function
End If
‘ バッファの確保(Unicodeは1文字2バイト。念のため同一文字長を確保)
Dim buffer As String
buffer = String$(sourceLength, vbNullChar)
‘ API呼び出しによる変換
Dim result As Long
result = LCMapStringW( _
LOCALE_SYSTEM_DEFAULT, _
LCMAP_HALFWIDTH, _
StrPtr(source), _
sourceLength, _
StrPtr(buffer), _
sourceLength _
)
If result > 0 Then
‘ APIが返した文字数に基づいて文字列を切り出し
ConvertToHalfWidth = Left$(buffer, result)
Else
‘ 万が一APIが失敗した場合は、フォールバックとしてVBA標準のStrConvを使用
ConvertToHalfWidth = StrConv(source, vbNarrow)
End If
End Function
—
4. テクニカル・ディープダイブ:なぜこのコードが「極限」なのか
このコードが、一般的な「マクロの記録」から作成されたコードや、初心者が書いたコードと決定的に異なる技術的ポイントを解説する。
① `Find.Execute` の最適利用と `wdCollapseEnd` による検索Windowの制御
多くのVBA開発者は、文書全体を走査する際に `ActiveDocument.Paragraphs` などのコレクションを走査し、各段落の `Text` を読み込んで処理しようとする。しかし、Wordの段落コレクションはドキュメントの規模が大きくなると指数関数的に走査速度が低下する。
本コードで採用した `Range.Find` は、Word内部のC++検索エンジンを直接叩くため、VBAレイヤーでのループ回数がマッチした箇所のみに制限される。さらに、マッチした範囲(`Range`)を処理した直後に `rng.Collapse wdCollapseEnd` を実行することで、検索対象のウィンドウを前方にのみ移動させ、メモリ上の探索範囲を最小化しつつ無限ループを完全に防止している。
② Win32 API `LCMapStringW` によるメモリダイレクト変換
VBA標準の `StrConv(str, vbNarrow)` は、手軽ではあるが内部でのバッファ確保やエラーチェックが冗長であり、また特定のOSパッチレベルやロケール設定によっては予期せぬ文字化け(Unicodeマッピングのズレ)を引き起こすことがある。
`LCMapStringW` を `StrPtr`(文字列のメモリ番地を示すポインタ)経由で直接呼び出すことにより、VBAのランタイムエンジンのオーバーヘッドをバイパスし、OSカーネルレベルでの超高速な文字マッピングを実現している。大規模なバッチ処理において、この数マイクロ秒の積み重ねが全体の実行速度を数分単位で分かつ。
③ `.NameAscii` と `.NameFarEast` の分離指定
Wordのフォント管理はExcelよりも遥かに複雑である。Wordは「日本語(ダブルバイト文字)」と「英数字(シングルバイト文字)」で、同一のテキスト範囲内であっても適用するフォントエンジンを内部で分けている。
With rng.Font
.NameAscii = TARGET_ASCII_FONT ‘ 半角英数字用フォント
.NameFarEast = TARGET_FAR_EAST_FONT ‘ 日本語用フォント
End With
多くの開発者が `.Name = “Arial”` と指定してしまい、日本語部分までArialの代替フォント(デバイスフォントなど)に化けてしまうというバグに苦しめられている。上記のように `NameAscii` と `NameFarEast` を明示的に分けることで、半角化した英数字のみを `Arial` にし、元々の日本語は `MS 明朝` のまま美しく維持するという、プロフェッショナル品質のレイアウト制御を可能にしている。
④ COM参照の明示的な破棄
VBAは参照カウント方式のガベージコレクション(GC)を採用している。プロシージャを抜ければ基本的には変数に割り当てられたメモリは解放されるが、Wordのような重厚なCOMサーバーを相手にする場合、意図しない「ゴースト・プロセス」やメモリリークが発生しやすい。
処理の終端およびエラーハンドリング内で、すべてのオブジェクト変数に対して明示的に `Nothing` を代入することは、Windowsサービス化されたバックグラウンドでのWord自動実行(サーバーサイドオートメーションなど、推奨はされないがレガシー環境では頻出するパターン)において、システムのハングアップを防ぐための必須の作法である。
—
5. レガシー保守とシステム連携へのアドバイス
このコードをさらに発展させ、社内の他のシステム(C#のバッチ処理や、Power Automate for desktopなど)と連携させる場合、以下の知見が役立つ。
- サイレント実行の徹底:
外部システムからこのマクロをキックする場合、`Application.Visible = False` で実行することが多いが、Wordはエラー時にダイアログを表示してプロセスがスタックしやすい。そのため、本コードのように `On Error GoTo ErrorHandler` でエラーを確実にトラップし、ダイアログを出さずに終了する設計は堅牢性維持のために極めて重要である。
- StoryRangesへの対応:
今回のコードは `doc.Content`(本文)のみを対象としているが、ヘッダー、フッター、テキストボックス内の全角英数字も変換対象とする場合は、`doc.StoryRanges` コレクションを巡回するラッパーを実装するとよい。その際も、各StoryRangeに対して上記の実装を適用すれば、パフォーマンス特性を損なうことなく完全なドキュメント走査が可能となる。
プロフェッショナルが書くコードとは、単に動くだけでなく、対象となるアプリケーションの構造を理解し、ハードウェアとOSのポテンシャルを最大限に引き出すコードである。本稿で紹介したテクニックを、ぜひ貴社のレガシーシステム刷新やドキュメント自動化の現場で役立ててほしい。
