【入門編】【ファイルI/O最適化】ADODB.Stream を用いたバイナリデータの高速な「部分読み込み」とシグネチャ判定 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

巨大なファイルに立ち向かう:ADODB.Streamで実現する「外科手術的」なファイル判定術

こんにちは。現場の自動化を支えるアーキテクトです。

「マクロの記録」から一歩踏み出し、自分自身の力でスクリプトを書き始めた皆さんに、今日は一つ、「プロフェッショナルとアマチュアを分ける決定的な境界線」についてお話ししましょう。

それは、「リソース(メモリと時間)をいかに大切に扱うか」という設計思想です。

例えば、あなたが「1GBある巨大な動画ファイルが、本当にJPEG画像なのか、それとも別のファイルなのか」を判定するプログラムを書こうとしたとします。初心者はどうするでしょうか? おそらく、ファイルを丸ごとメモリに読み込んで、その中身を調べようとするはずです。

しかし、それは「巨大な岩石を鑑定するために、岩石を粉砕して成分を調べる」ようなものです。非効率ですよね? プロなら、「表面のわずかな削り屑(シグネチャ)だけを見て、正体を突き止める」。これこそが、ファイルI/O(入出力)の最適化です。

今回は、VBScriptにおける最強の武器の一つである `ADODB.Stream` を使い、ファイル全体を読み込まずに「先頭の数バイト」だけを高速に抜き出す、極限のテクニックを伝授します。

1. なぜ FileSystemObject (FSO) ではダメなのか?

VBScriptでファイルを扱う際、多くの人が最初に触れるのが `Scripting.FileSystemObject` (FSO) です。FSOは非常に優秀で、ファイルのコピーや削除、テキストファイルの読み書きには最適です。

しかし、「バイナリデータ(画像、実行ファイル、圧縮ファイルなど)を精密に扱う」という目的においては、FSOは力不足です。FSOは基本的に「テキスト(文字列)」を扱うことを前提として設計されているため、バイナリの「生の値」を扱う際に、文字コードの解釈によってデータが壊れてしまうリスクがあるのです。

そこで登場するのが `ADODB.Stream` です。
これは、データを「ストリーム(流れ)」として扱います。ダムの放水のように、必要な分だけ、必要な形式(バイナリモード)で、ピンポイントにデータを流し込むことができる。これこそが、高速・正確なファイル判定の鍵となります。

2. 「マジックナンバー」という指紋を見つけ出せ

すべてのファイル形式には、実は「指紋」のようなものがあります。これを専門用語で 「マジックナンバー(シグネチャ)」 と呼びます。

例えば、以下のようなルールがあります。

  • JPEG画像: 先頭が `FF D8 FF`
  • PDFファイル: 先頭が `25 50 44 46` (`%PDF`)
  • ZIPファイル: 先頭が `50 4B 03 04` (`PK..`)

ファイルの中身を全部見る必要はありません。先頭の数バイトさえ読めれば、そのファイルの正体は一瞬で判明するのです。

3. 実装コード:高速シグネチャ判定エンジン

それでは、実際に使えるコードを見てみましょう。このコードは、指定したファイルの先頭数バイトだけを読み取り、それを16進数(Hex)の文字列として返します。

' ==========================================================================
' テーマ: ADODB.Stream を用いたバイナリ・シグネチャ抽出
' 目的: 巨大なファイルでもメモリを消費せず、先頭数バイトのみを高速に取得する
' ==========================================================================

Option Explicit

' --- 設定領域 ---
Dim targetFile
targetFile = "C:\temp\test_image.jpg" ' ← 検証したいファイルのパスを指定してください

' --- メイン処理 ---
Dim signature
signature = GetFileSignature(targetFile, 4) ' 先頭4バイトを取得

If Err.Number <> 0 Then
    WScript.Echo "エラーが発生しました: " & Err.Description
Else
    WScript.Echo "ファイルパス: " & targetFile
    WScript.Echo "抽出されたシグネチャ (Hex): " & signature
    
    ' 判定ロジックの例
    Select Case signature
        Case "FFD8FF" ' JPEGの先頭3バイト
            WScript.Echo "判定結果: これはJPEG画像です。"
        Case "25504446" ' PDFの先頭4バイト
            WScript.Echo "判定結果: これはPDFファイルです。"
        Case "504B0304" ' ZIPの先頭4バイト
            WScript.Echo "判定結果: これはZIP圧縮ファイルです。"
        Case Else
            WScript.Echo "判定結果: 未知のファイル形式です。"
    End Select
End If

' ==========================================================================
' 関数: GetFileSignature
' 引数: strFilePath (ファイルパス), intByteCount (読み込むバイト数)
' 戻り値: 16進数文字列
' ==========================================================================
Function GetFileSignature(strFilePath, intByteCount)
    Dim objStream, binaryData, i, hexString
    Dim byteValue
    
    On Error Resume Next
    
    ' 1. ADODB.Streamオブジェクトの生成
    Set objStream = CreateObject("ADODB.Stream")
    
    ' 2. ストリームの設定
    ' adTypeBinary = 1 (バイナリモード)
    ' adModeRead = 3 (読み取り専用)
    objStream.Type = 1 
    objStream.Open
    
    ' 3. ファイルを開く
    objStream.LoadFromFile strFilePath
    
    If Err.Number <> 0 Then
        GetFileSignature = ""
        Exit Function
    End If

    ' 4. 【ここが肝!】必要なバイト数だけを読み込む
    ' ファイル全体ではなく、intByteCount分だけをメモリに載せる
    binaryData = objStream.Read(intByteCount)
    
    ' 5. バイナリデータを16進数文字列に変換
    ' VBScriptではバイナリを直接扱いにくいため、1バイトずつループして変換する
    hexString = ""
    For i = 1 To LenB(binaryData)
        ' AscBを使ってバイナリの生の値を取得
        byteValue = AscB(MidB(binaryData, i, 1))
        
        ' 16進数への変換(2桁固定にするための工夫)
        hexString = hexString & Right("0" & Hex(byteValue), 2)
    Next
    
    GetFileSignature = hexString

    ' 6. 後片付け(リソースの解放)
    objStream.Close
    Set objStream = Nothing
    
    On Error GoTo 0
End Function

4. コードの徹底解剖:エンジニアの視点

このコードには、プロが意識している「3つの極意」が詰まっています。

① `objStream.Type = 1` の意味

`ADODB.Stream` には「テキストモード」と「バイナリモード」があります。`Type = 1` を指定することで、コンピュータがデータを「文字」として解釈しようとする余計な処理を排除し、純粋な「0と1の塊」として扱います。これが正確性の源です。

② `objStream.Read(intByteCount)` によるメモリ節約

ここが最大のポイントです。`Read()` の引数に読み込みたいバイト数を渡しています。もし `Read()` とだけ書くと、ファイル全体をメモリに読み込もうとしてしまいます。引数を指定することで、たとえ10GBのファイルであっても、指定した数バイト分(例:4バイト)のメモリしか消費しません。

③ `AscB` と `MidB` による精密な抽出

VBScriptでバイナリを扱う際、通常の `Mid` 関数を使うと、文字コード変換の影響を受けて値が変わってしまうことがあります。`MidB`(Byte版のMid)と `AscB`(Byte版のAsc)を使うことで、文字コードの魔法にかかることなく、生のバイト値を確実に取得しています。

5. 陥りやすいエラーと対策

この実装を進める上で、皆さんが躓きやすいポイントを挙げておきます。

1. ファイルが開けない(Permission Denied)
対象のファイルが他のプログラム(Excelや画像ビューアなど)で開かれていると、読み取りに失敗することがあります。実務では、事前にファイルの存在確認を行うか、エラーハンドリングを丁寧に行ってください。
2. 16進数の桁数不足
`Hex()` 関数は、値が小さいときに1桁(例:`0xA` を `A`)で返してしまいます。これでは `FFD8` と比較したいときに `FD8` になってしまい、一致判定に失敗します。必ず `Right(“0” & Hex(byteValue), 2)` のように、「2桁に揃える(ゼロパディング)」処理を入れてください。
3. リソースの解放忘れ
`objStream.Close` を忘れると、スクリプトが終了してもファイルがロックされたままになることがあります。必ず `Close` と `Set objStream = Nothing` をセットで行いましょう。

まとめ:基礎をマスターして、次のステージへ

いかがでしたか?
「ファイルを読む」という一見単純な動作も、対象が巨大になったとき、そのアプローチひとつで「動かないコード」にも「極めて高速なコード」にもなり得ます。

`FileSystemObject` で十分な場面もあれば、今回のように `ADODB.Stream` で外科手術のような精密な操作が必要な場面もあります。道具の特性を理解し、適材適所で使い分ける。これが、エンジニアとしての第一歩です。

ここをクリアすれば、VBScriptの基礎はバッチリですよ。自信を持って、次の自動化プロジェクトに挑んでくださいね!応援しています。

タイトルとURLをコピーしました