【固定長データ解析】LenB・MidB 関数と ADODB.Stream を駆使したマルチバイト文字の文字化けしないバイト単位切り出し
レガシーシステム、とりわけ金融・物流・自治体等の基幹系システムにおいて、今なお現役で稼働し続ける「固定長電文(固定長データ)」。
この世界では、文字数ではなく「バイト数」が絶対的正義である。
しかし、VBScript(およびWSH/VBAの初期実装)における文字列処理のデフォルトは、UTF-16(OLECHAR)ベースの文字数カウントである。ここにShift-JISやCP932などのマルチバイト文字が混入した瞬間、`Len` や `Mid` 関数は残酷なまでの位置ズレと文字化けを引き起こす。半角カナやJIS第2水準漢字の罠に沈んだエンジニアの数は数知れない。
本稿では、VBScriptの限界領域を突破し、`LenB`・`MidB` 関数と `ADODB.Stream` オブジェクトを極限までチューニングして、バイト単位の正確性を担保した固定長データパーサの実装知見を解説する。
—
1. レガシー関数 `LenB` / `MidB` の正体と文字コードの罠
VBScriptには、バイト単位で文字列を操作するための `LenB`、`MidB`、`LeftB`、`RightB` が用意されている。
しかし、これら「B付き関数」の挙動には、現代のOS環境において致命的な前提条件が存在する。
内部コード変換のブラックボックス
VBScriptの文字列型(BSTR)は常にUTF-16LE(Little Endian)でメモリ上に保持されている。
B付き関数は単に「文字列のメモリ上のバイト数(文字数×2)」を返しているに過ぎない。つまり、OSのコードページやロケール(ANSI環境)に依存したバイト数計算を自動で行うわけではない。
素朴な `MidB` をそのままShift-JISのストリームに適用すると、以下の破綻が生じる。
1. マルチバイト文字の途中でバイトが分断され、いわゆる「化け文字」の制御シーケンス(不正な先行バイト)が生成される。
2. Windows環境のデフォルト文字コードとVBScriptの内部表現のギャップにより、予期せぬバイナリ破壊が起きる。
これを完璧に制御するためには、「データをメモリ上で正しいバイナリ(バイト配列)として保持し、処理の境界を厳密に管理する」アーキテクチャが必要となる。
—
2. ADODB.Stream によるバイナリの支配
Shift-JISの固定長データを安全に読み込み、切り出すためには、ファイルI/Oのレイヤーで文字コード変換をコントロールしなければならない。ここで絶大な信頼性を誇るのが `ADODB.Stream` オブジェクトである。
`ADODB.Stream` をバイナリモード(`Type = adTypeBinary`)で一旦メモリ上にロードし、それを適切にテキストストリームへ変換、あるいはバイナリのままスライスすることで、文字化けを完全に根絶できる。
オブジェクトのライフサイクル管理の鉄則
VBScriptにおけるCOMコンポーネントの解放漏れは、長期間稼働するWSHバッチにおいてメモリリークの致命傷となる。
「生成したら必ず `Nothing` を代入する」「エラー発生時も確実にとりまわす」というガベージコレクションの自社防衛が、チーフアーキテクトとしての最低限の作法である。
—
3. 実装コード:文字化けゼロの固定長バイナリパーサ
以下に、Shift-JIS(CP932)固定長ファイルを読み込み、定義されたバイト長ごとに正確に切り出して処理するプロダクション品質のVBScriptを示す。
Option Explicit
‘ 定数定義
Const adTypeBinary = 1
Const adTypeText = 2
Const adSaveCreateOverWrite = 2
‘ —————————————————————–
‘ メイン処理エントリ
‘ —————————————————————–
Sub Main()
Dim targetFilePath
targetFilePath = “C:\Data\fixed_length_sample.dat”
‘ 固定長レイアウト定義 (例: 項目名, 開始バイト(1基点), バイト長)
‘ 1: 顧客ID (10バイト, 半角)
‘ 2: 顧客名 (20バイト, 全角混在可能)
‘ 3: 金額 (8バイト, 右詰めパディング)
Dim objStream
Set objStream = CreateObject(“ADODB.Stream”)
On Error Resume Next
‘ 1. バイナリモードでファイル全体をメモリにロード
objStream.Type = adTypeBinary
objStream.Open
objStream.LoadFromFile targetFilePath
If Err.Number <> 0 Then
WScript.Echo “致命的エラー: ファイルの読み込みに失敗しました – ” & Err.Description
Set objStream = Nothing
Exit Sub
End If
Dim rawData
rawData = objStream.Read() ‘ バイナリデータ(VT_UI1 | VT_ARRAY)を取得
objStream.Close
‘ ストリームの再利用(テキスト変換用)
objStream.Type = adTypeText
objStream.Charset = “shift_jis”
objStream.Open
objStream.Write rawData
objStream.Position = 0 ‘ 先頭へシーク
‘ 1行ずつ固定長データを解析(ここでは簡易的に全データをテキスト化して行単位で処理するアプローチ)
‘ ※厳密なバイナリレコード単位の切り出しには後述のMidBアプローチを併用する
Do While Not objStream.EOS
Dim lineText
lineText = objStream.ReadText(-2) ‘ adReadLine equivalent (-2: adReadLine)
If Len(lineText) > 0 Then
Call ParseFixedLengthRecord(lineText)
End If
Loop
If Err.Number <> 0 Then
WScript.Echo “処理中にエラーが発生しました: ” & Err.Description
End If
On Error GoTo 0
‘ 2. オブジェクトの明示的破棄(メモリ最適化)
objStream.Close
Set objStream = Nothing
WScript.Echo “固定長データの解析が正常終了しました。”
End Sub
‘ —————————————————————–
‘ 固定長レコード解析関数(バイト位置ベース)
‘ —————————————————————–
Sub ParseFixedLengthRecord(ByVal strRecord)
‘ VBScriptの文字列はUTF-16だが、Shift-JIS前提のバイト位置を
CBytes値に変換して安全に切り出すためのラッパー
Dim col1_ID, col2_Name, col3_Amount
‘ 顧客ID: 1バイト目から10バイト
col1_ID = MidB_SJIS(strRecord, 1, 10)
‘ 顧客名: 11バイト目から20バイト(全角文字の途中で切れないよう調整)
col2_Name = MidB_SJIS(strRecord, 11, 20)
‘ 金額: 31バイト目から8バイト
col3_Amount = MidB_SJIS(strRecord, 31, 8)
‘ デバッグ出力(実運用ではログファイル出力等に置き換え)
WScript.Echo “ID:[” & Trim(col1_ID) & “] Name:[” & Trim(col2_Name) & “] Amount:[” & Trim(col3_Amount) & “]”
End Sub
‘ —————————————————————–
‘ Shift-JISのバイト位置を考慮した安全なMidBラッパー関数
‘ —————————————————————–
Function MidB_SJIS(ByVal targetStr, ByVal startByte, ByVal lengthBytes)
Dim adoStream
Set adoStream = CreateObject(“ADODB.Stream”)
With adoStream
.Type = adTypeText
.Charset = “shift_jis”
.Open
.WriteText targetStr
‘ バイナリモードに切り替えてバイト単位でスライス
.Position = 0
.Type = adTypeBinary
‘ 文字列変換時のBOMや改行コードのオフセットを考慮しつつバイナリ抽出
‘ 注意: WriteTextは末尾に改行が付与される挙動があるため、正確な制御にはバイナリ直操作が望ましい
‘ ここでは簡易的にバイト配列を切り出すロジックを展開
Dim binData
binData = .Read()
End With
Set adoStream = Nothing
‘ バイト配列からの安全な切り出し処理へ引き渡し
MidB_SJIS = ExtractBytesAsShiftJIS(binData, startByte – 1, lengthBytes)
End Function
‘ —————————————————————–
ابات(Byte)配列から指定範囲を切り出し、Shift-JIS文字列として復元する
‘ —————————————————————–
Function ExtractBytesAsShiftJIS(ByRef binArray, ByVal offset, ByVal length)
Dim adoStream
Set adoStream = CreateObject(“ADODB.Stream”)
With adoStream
.Type = adTypeBinary
.Open
‘ 配列の一部、あるいは指定範囲のバイトを書き込み
‘ VBScript単体でのバイト配列スライスは冗長になるため、StreamのWriteで部分書き込みを行う
If UBound(binArray) >= (offset + length – 1) Then
‘ 該当部分のバイト配列を抽出するためのサブStream操作
Dim subStream
Set subStream = CreateObject(“ADODB.Stream”)
subStream.Type = adTypeBinary
subStream.Open
subStream.Write binArray
subStream.Position = offset
Dim extractedBin
extractedBin = subStream.Read(length)
subStream.Close
Set subStream = Nothing
‘ バイナリをShift-JISとしてテキスト化
.Write extractedBin
.Position = 0
.Type = adTypeText
.Charset = “shift_jis”
ExtractBytesAsShiftJIS = .ReadText()
Else
ExtractBytesAsShiftJIS = “”
End If
.Close
End With
Set adoStream = Nothing
End Function
‘ 実行トリガー
Call Main()
—
4. チーフアーキテクトが指摘する「実運用上の罠と極限チューニング」
上記のコードは美しく堅牢であるが、極限のパフォーマンスとエンタープライズ品質を求める現場では、さらに以下のチューニング要件を考慮しなければならない。
① 2バイト文字の「半端な切断」に対する防衛的プログラミング
固定長レイアウト設計の不備により、全角文字の2バイト目の真ん中で切り出し位置が指定された場合、いかに `ADODB.Stream` といえども文字化け(あるいは置換文字 `?` への化け)を防ぐことは物理的に不可能である。
実務においては、切り出し開始位置・終了位置がマルチバイト文字の境界(リードバイトかトレイルバイトか)にヒットしていないかを判定する「Shift-JISパリティチェッカー」を前段に噛ませるのが、真にプロフェッショナルなアーキテクチャである。
② 大容量ファイル(1GB超)におけるメモリ枯渇対策
上記のコードは `LoadFromFile` でファイル全体をメモリ上に展開している。もし対象の固定長電文が数ギガバイトに及ぶ場合、WSH(cscript.exe)のプロセス空間(32bit環境では最大2GB、64bitでもヒープ断片化のリスク)を容易に食い潰す。
超巨大ファイルを扱う場合は、`ADODB.Stream` の `Line` 単位読み込みや、一定サイズ(チャンク)ごとのバイナリリードを実装し、ストリーミング処理(逐次処理)モデルへ昇華させる必要がある。
—
総括
VBScriptはレガシーなスクリプト言語と侮られがちだが、裏で稼働するCOMコンポーネント群(特に `ADODB.Stream`)の本質を理解し、メモリとバイナリのライフサイクルを完全に掌握していれば、現代の複雑なシステム間連携においても一歩も引かない堅牢性を発揮する。
文字化けという「レガシーの呪い」は、曖昧なコードへの依存が生むエラーに他ならない。正確なバイト演算とリソース管理によって、システムの寿命をさらに数十年延ばすことこそ、現場のエンジニアに課された使命である。
