Excel VBAで「バイナリデータ」を制圧せよ! ADODB.Streamで開ける未知のファイル形式
「VBAでCSV以外のファイルを直接扱いたい…」
そんな渇望を抱き、夜な夜なVBEとにらめっこしているあなた。その熱意、素晴らしい。だが、既存の知識の断片を拾い集めるだけでは、真の効率化は遠い。我々が目指すべきは、単なる「動くコード」ではない。「壊れない」「保守できる」「進化する」プロダクションコードだ。
今回は、Excel VBAでバイナリデータを自在に操るための強力な武器、`ADODB.Stream`オブジェクトに焦点を当てる。これを使えば、テキストファイルだけでなく、画像、設定ファイル、あるいは独自フォーマットのデータまで、VBAの守備範囲を劇的に広げることができる。
なぜ「バイナリ」を理解する必要があるのか?
CSVは構造化されたテキストデータであり、VBAの標準機能(`Open`ステートメント、`Input #`、`Write #`など)で比較的容易に扱える。しかし、実務で遭遇するデータは、それだけではない。
- 画像ファイル (.jpg, .png): ピクセルデータそのものを扱う必要がある。
- 設定ファイル (.ini, .xml, .json): 特定のエンコーディングや改行コードが重要になる場合がある。
- バイナリ形式のDBダンプ: データベースのバックアップファイルなど。
- 独自フォーマットのデータ: 社内システムや外部連携で使われる特殊なファイル。
これらのファイルを、単に「文字列」として読み込もうとすると、文字化けやデータ破損といった悲劇に見舞われる。バイナリデータとは、文字コードや構造に依存しない「生のバイト列」であり、それを正しく解釈するには、専用のメカニズムが必要となるのだ。
ADODB.Stream:バイナリ操作の秘密兵器
`ADODB.Stream`は、ActiveX Data Objects (ADO) ライブラリの一部であり、ファイルやメモリ上のストリームデータを操作するためのオブジェクトだ。その真価は、以下の点にある。
1. 文字コードの指定: テキストモードで読み書きする際に、UTF-8, Shift_JISなどの文字コードを明示的に指定できる。これにより、文字化けの悩みが解消される。
2. バイナリモードでの直接操作: テキストとして解釈せず、生のバイト列としてファイルを読み書きできる。画像や実行ファイルなどのバイナリファイルを扱う際に不可欠だ。
3. メモリ上での操作: ファイルに書き出す前に、メモリ上でデータを加工・結合することも可能。
準備:参照設定の追加
`ADODB.Stream`を利用するには、VBEの「ツール」>「参照設定」から、「Microsoft ActiveX Data Objects x.x Library」にチェックを入れる必要がある。バージョンは環境によって異なるが、一般的には6.0以降が推奨される。
実践:バイナリファイルの読み込みと文字コード変換
まずは、テキストファイルを指定した文字コードで読み込む例を見てみよう。これにより、UTF-8で保存されたファイルをShift_JISとして開いたり、その逆も可能になる。
‘
‘ 関数名: ReadTextFileWithEncoding
‘ 機能: 指定されたパスのテキストファイルを、指定された文字コードで読み込む
‘ 引数:
‘ filePath (String): 読み込むファイルのフルパス
‘ encoding (MsoEncoding): 読み込む際の文字コード (例: msoEncodingUTF8, msoEncodingShiftJIS)
‘ 戻り値:
‘ String: 読み込んだファイルの内容。エラー時は空文字列を返す。
‘
Public Function ReadTextFileWithEncoding(ByVal filePath As String, ByVal encoding As MsoEncoding) As String
Dim stream As Object ‘ ADODB.Stream
Dim fileContent As String
On Error GoTo ErrorHandler
‘ ADODB.Stream オブジェクトを作成
Set stream = CreateObject(“ADODB.Stream”)
‘ ストリームのモードをテキストモードに設定
stream.Type = adTypeText
‘ 指定された文字コードを設定 (重要!)
stream.Charset = GetCharsetString(encoding) ‘ ヘルパー関数で変換
‘ ファイルを開く (Readモード)
stream.Open
‘ ファイルの内容を読み込む
fileContent = stream.ReadText
‘ ストリームを閉じる
stream.Close
‘ オブジェクトを解放
Set stream = Nothing
ReadTextFileWithEncoding = fileContent
Exit Function
ErrorHandler:
‘ エラー発生時の処理
If Not stream Is Nothing Then
If stream.State = adStateOpen Then
stream.Close
End If
Set stream = Nothing
End If
MsgBox “ファイルの読み込み中にエラーが発生しました: ” & Err.Description, vbCritical
ReadTextFileWithEncoding = “” ‘ エラー時は空文字列を返す
End Function
‘
‘ ヘルパー関数: MsoEncoding を Charset 文字列に変換する
‘
Private Function GetCharsetString(ByVal encoding As MsoEncoding) As String
Select Case encoding
Case msoEncodingUTF8: GetCharsetString = “utf-8”
Case msoEncodingShiftJIS: GetCharsetString = “shift-jis”
Case msoEncodingUnicode: GetCharsetString = “utf-16” ‘ UTF-16 (Little Endian)
‘ 必要に応じて他のエンコーディングを追加
Case Else: GetCharsetString = “utf-8” ‘ デフォルトはUTF-8
End Select
End Function
‘
‘ 呼び出し例:
‘
Sub Example_ReadTextFile()
Dim utf8FilePath As String
Dim sjfilePath As String
Dim content As String
‘ テスト用のUTF-8ファイルを作成 (手動で作成するか、WriteTextFileWithEncoding関数で作成)
‘ 例: C:\temp\utf8_test.txt に “これはUTF-8テストです。” と UTF-8 で保存
‘ UTF-8 ファイルを UTF-8 で読み込む
utf8FilePath = “C:\temp\utf8_test.txt”
If Dir(utf8FilePath) <> “” Then
content = ReadTextFileWithEncoding(utf8FilePath, msoEncodingUTF8)
Debug.Print “UTF-8 (UTF-8): ” & content
Else
MsgBox utf8FilePath & ” が見つかりません。”, vbExclamation
End If
‘ UTF-8 ファイルを Shift_JIS として読み込もうとする (意図的に文字化けさせる)
If Dir(utf8FilePath) <> “” Then
content = ReadTextFileWithEncoding(utf8FilePath, msoEncodingShiftJIS)
Debug.Print “UTF-8 (Shift_JIS): ” & content ‘ 文字化けするはず
End If
‘ Shift_JIS ファイルを Shift_JIS で読み込む
‘ 例: C:\temp\sjis_test.txt に “これはShift_JISテストです。” と Shift_JIS で保存
sjfilePath = “C:\temp\sjis_test.txt”
If Dir(sjfilePath) <> “” Then
content = ReadTextFileWithEncoding(sjfilePath, msoEncodingShiftJIS)
Debug.Print “Shift_JIS (Shift_JIS): ” & content
Else
MsgBox sjfilePath & ” が見つかりません。”, vbExclamation
End If
End Sub
解説:
- `CreateObject(“ADODB.Stream”)`: `ADODB.Stream`オブジェクトを動的に生成します。参照設定をしていれば `New ADODB.Stream` でも構いませんが、動的生成の方が依存性を減らせます。
- `stream.Type = adTypeText`: ストリームをテキストモードに設定します。バイナリモードにする場合は `adTypeTextBinary` を指定します。
- `stream.Charset = GetCharsetString(encoding)`: ここが肝です。読み込む際の文字コードを明示的に指定します。`GetCharsetString` ヘルパー関数は、Excel VBAの`MsoEncoding`定数を、ADOが理解できる文字列(”utf-8″, “shift-jis”など)に変換しています。
- `stream.Open`: ファイルを開きます。`adModeRead` (読み込み), `adModeWrite` (書き込み), `adModeReadWrite` (読み書き両方) などのモードを指定できますが、デフォルトは `adModeReadWrite` です。
- `stream.ReadText`: ファイルの内容を文字列として読み込みます。`Read`メソッドを使うとバイト配列として読み込めます。
- `stream.Close`: ストリームを閉じます。リソースの解放は重要です。
- `On Error GoTo ErrorHandler`: エラーハンドリングは堅牢なコードの基本です。ファイルが存在しない、アクセス権がないなどの場合に備えます。
実践:バイナリファイルの書き込み
次に、指定した文字コードでファイルに書き込む方法です。
‘
‘ 関数名: WriteTextFileWithEncoding
‘ 機能: 指定された内容を、指定された文字コードでファイルに書き込む
‘ 引数:
‘ filePath (String): 書き込むファイルのフルパス
‘ content (String): 書き込む内容
‘ encoding (MsoEncoding): 書き込む際の文字コード (例: msoEncodingUTF8, msoEncodingShiftJIS)
‘ overwrite (Boolean): Trueの場合、既存ファイルを上書きする。Falseの場合、存在しない場合のみ新規作成。
‘ 戻り値:
‘ Boolean: 書き込み成功時は True、失敗時は False
‘
Public Function WriteTextFileWithEncoding(ByVal filePath As String, ByVal content As String, ByVal encoding As MsoEncoding, Optional ByVal overwrite As Boolean = True) As Boolean
Dim stream As Object ‘ ADODB.Stream
Dim fileSystem As Object ‘ Scripting.FileSystemObject
On Error GoTo ErrorHandler
‘ ファイル存在チェックと上書き設定
If Dir(filePath) <> “” And Not overwrite Then
MsgBox “ファイル ‘” & filePath & “‘ は既に存在します。上書きは許可されていません。”, vbExclamation
WriteTextFileWithEncoding = False
Exit Function
End If
‘ FileSystemObject でディレクトリが存在するか確認し、なければ作成する
Set fileSystem = CreateObject(“Scripting.FileSystemObject”)
If Not fileSystem.GetParentFolderName(filePath) = “” Then
If Not fileSystem.FolderExists(fileSystem.GetParentFolderName(filePath)) Then
fileSystem.CreateFolder fileSystem.GetParentFolderName(filePath)
End If
End If
Set fileSystem = Nothing
‘ ADODB.Stream オブジェクトを作成
Set stream = CreateObject(“ADODB.Stream”)
‘ ストリームのモードをテキストモードに設定
stream.Type = adTypeText
‘ 指定された文字コードを設定
stream.Charset = GetCharsetString(encoding) ‘ 上記 ReadTextFileWithEncoding のヘルパー関数を使用
‘ ファイルを開く (Writeモード)。ByteOrderを指定するとBOMの有無も制御できる場合がある。
‘ adSaveCreateOverWrite: 既存ファイルは上書き、なければ新規作成
‘ adSaveCreateNotExist: 存在しない場合のみ新規作成 (overwrite=False と同様の挙動)
Dim openMode As Long
If overwrite Then
openMode = adSaveCreateOverWrite
Else
openMode = adSaveCreateNotExist
End If
‘ adModeReadWrite は書き込み可能にするための必須設定
stream.Open openMode, adModeReadWrite, adWriteLine ‘ adWriteLine は改行コードの扱い
‘ ファイルの内容を書き込む
stream.WriteText content
‘ ストリームを閉じる (これによりファイルに書き込まれる)
stream.Close
‘ オブジェクトを解放
Set stream = Nothing
WriteTextFileWithEncoding = True
Exit Function
ErrorHandler:
‘ エラー発生時の処理
If Not stream Is Nothing Then
If stream.State = adStateOpen Then
stream.Close
End If
Set stream = Nothing
End If
MsgBox “ファイルの書き込み中にエラーが発生しました: ” & Err.Description, vbCritical
WriteTextFileWithEncoding = False
End Function
‘
‘ 呼び出し例:
‘
Sub Example_WriteTextFile()
Dim utf8FilePath As String
Dim sjfilePath As String
Dim content1 As String
Dim content2 As String
content1 = “これはUTF-8で書き込まれるテストです。” & vbCrLf & “改行も入ります。”
content2 = “これはShift_JISで書き込まれるテストです。” & vbCrLf & “ASCII以外の文字は文字化けします。”
‘ UTF-8 でファイルに書き込む
utf8FilePath = “C:\temp\output_utf8.txt”
If WriteTextFileWithEncoding(utf8FilePath, content1, msoEncodingUTF8, True) Then
MsgBox “UTF-8ファイル ‘” & utf8FilePath & “‘ を作成しました。”, vbInformation
End If
‘ Shift_JIS でファイルに書き込む
sjfilePath = “C:\temp\output_sjis.txt”
If WriteTextFileWithEncoding(sjfilePath, content2, msoEncodingShiftJIS, True) Then
MsgBox “Shift_JISファイル ‘” & sjfilePath & “‘ を作成しました。”, vbInformation
End If
‘ 既存ファイルを上書きしない例
Dim existingFilePath As String
existingFilePath = “C:\temp\output_utf8.txt” ‘ 上記で作成したファイル
If Dir(existingFilePath) <> “” Then
MsgBox “既存ファイルを上書きしないテストを開始します。”, vbInformation
If WriteTextFileWithEncoding(existingFilePath, “この内容は書き込まれません。”, msoEncodingUTF8, False) Then
MsgBox “(このメッセージは表示されないはずです)”, vbInformation
Else
MsgBox “期待通り、既存ファイルは上書きされませんでした。”, vbInformation
End If
End If
End Sub
解説:
- `stream.Open openMode, adModeReadWrite, adWriteLine`: 書き込みモードで開きます。`adSaveCreateOverWrite` は上書き、`adSaveCreateNotExist` は新規作成のみを意味します。`adWriteLine` は、VBAの `vbCrLf` を適切な改行コードに変換するのに役立ちます。
- `stream.WriteText content`: 指定した文字コードで `content` をファイルに書き込みます。
- `stream.Close`: 重要! `Close` メソッドを呼び出すことで、バッファリングされていたデータが実際にファイルに書き込まれます。
真のバイナリ操作:`Read` と `Write` メソッド
テキストとしてではなく、生のバイト列としてファイルを扱いたい場合(例:画像ファイルのコピー、バイナリデータの加工)は、`Type` プロパティを `adTypeTextBinary` に設定し、`Read` および `Write` メソッドを使用します。
‘
‘ 関数名: CopyBinaryFile
‘ 機能: バイナリファイルを指定されたモードでコピーする
‘ 引数:
‘ sourcePath (String): コピー元のファイルパス
‘ destinationPath (String): コピー先のファイルパス
‘ bufferSize (Long): 読み書きに使うバッファサイズ (バイト単位)
‘ 戻り値:
‘ Boolean: コピー成功時は True、失敗時は False
‘
Public Function CopyBinaryFile(ByVal sourcePath As String, ByVal destinationPath As String, Optional ByVal bufferSize As Long = 4096) As Boolean
Dim sourceStream As Object ‘ ADODB.Stream
Dim destStream As Object ‘ ADODB.Stream
Dim buffer() As Byte ‘ バイト配列バッファ
Dim bytesRead As Long
On Error GoTo ErrorHandler
‘ 入力ファイルが存在しない場合はエラー
If Dir(sourcePath) = “” Then
Err.Raise vbObjectError + 1001, “CopyBinaryFile”, “コピー元ファイルが存在しません: ” & sourcePath
Exit Function
End If
‘ バッファサイズが不正な場合はデフォルト値を使用
If bufferSize <= 0 Then bufferSize = 4096
' バイト配列を宣言
ReDim buffer(0 To bufferSize - 1)
' ソースストリームの準備
Set sourceStream = CreateObject("ADODB.Stream")
sourceStream.Type = adTypeTextBinary ' バイナリモード
sourceStream.Open, adModeRead ' 読み込みモードで開く
' デスティネーションストリームの準備
Set destStream = CreateObject("ADODB.Stream")
destStream.Type = adTypeTextBinary ' バイナリモード
' 既存ファイルを上書き、または新規作成
destStream.Open, adModeWrite, adSaveCreateOverWrite
' ファイルシステムオブジェクトでディレクトリを作成
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.GetParentFolderName(destinationPath) = "" Then
If Not fso.FolderExists(fso.GetParentFolderName(destinationPath)) Then
fso.CreateFolder fso.GetParentFolderName(destinationPath)
End If
End If
Set fso = Nothing
' ファイルをチャンクごとに読み書き
Do While Not sourceStream.EOS ' End Of Stream でない間
bytesRead = sourceStream.Read(buffer) ' バッファに読み込む
' 実際に読み込めたバイト数だけ書き込む
If bytesRead > 0 Then
destStream.Write buffer(0 To bytesRead – 1) ‘ 読み込んだ範囲だけ書き込む
End If
Loop
‘ ストリームを閉じる
sourceStream.Close
destStream.Close
‘ オブジェクトを解放
Set sourceStream = Nothing
Set destStream = Nothing
CopyBinaryFile = True
Exit Function
ErrorHandler:
‘ エラー発生時のクリーンアップ
If Not sourceStream Is Nothing Then
If sourceStream.State = adStateOpen Then sourceStream.Close
Set sourceStream = Nothing
End If
If Not destStream Is Nothing Then
If destStream.State = adStateOpen Then destStream.Close
Set destStream = Nothing
End If
MsgBox “バイナリファイルのコピー中にエラーが発生しました: ” & Err.Description, vbCritical
CopyBinaryFile = False
End Function
‘
‘ 呼び出し例:
‘
Sub Example_CopyBinaryFile()
Dim sourceImage As String
Dim destinationImage As String
Dim sourceText As String
Dim destinationText As String
‘ テスト用の画像ファイルを用意 (例: C:\temp\sample.jpg)
sourceImage = “C:\temp\sample.jpg”
destinationImage = “C:\temp\copied_sample.jpg”
‘ テスト用のバイナリデータファイルを用意 (例: C:\temp\binary_data.bin)
‘ これは WriteBinaryDataToFile 関数などで作成可能
sourceText = “C:\temp\binary_data.bin”
destinationText = “C:\temp\copied_binary_data.bin”
‘ 画像ファイルのコピー
If Dir(sourceImage) <> “” Then
If CopyBinaryFile(sourceImage, destinationImage, 1024 1024) Then ‘ 1MBバッファ
MsgBox “画像ファイルをコピーしました: ” & destinationImage, vbInformation
Else
MsgBox “画像ファイルのコピーに失敗しました。”, vbCritical
End If
Else
MsgBox “コピー元画像ファイルが見つかりません: ” & sourceImage, vbExclamation
End If
‘ バイナリデータファイルのコピー
If Dir(sourceText) <> “” Then
If CopyBinaryFile(sourceText, destinationText) Then
MsgBox “バイナリデータファイルをコピーしました: ” & destinationText, vbInformation
Else
MsgBox “バイナリデータファイルのコピーに失敗しました。”, vbCritical
End If
Else
MsgBox “コピー元バイナリデータファイルが見つかりません: ” & sourceText, vbExclamation
End If
End Sub
‘
‘ ヘルパー関数: バイナリデータをファイルに書き出す (WriteTextFileWithEncoding はテキスト用)
‘
Public Function WriteBinaryDataToFile(ByVal filePath As String, ByVal data() As Byte) As Boolean
Dim stream As Object ‘ ADODB.Stream
On Error GoTo ErrorHandler
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = adTypeTextBinary ‘ バイナリモード
‘ 既存ファイルを上書き、または新規作成
stream.Open , adModeWrite, adSaveCreateOverWrite
‘ FileSystemObject でディレクトリを作成
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.GetParentFolderName(filePath) = “” Then
If Not fso.FolderExists(fso.GetParentFolderName(filePath)) Then
fso.CreateFolder fso.GetParentFolderName(filePath)
End If
End If
Set fso = Nothing
‘ バイト配列を書き込む
stream.Write data
stream.Close
Set stream = Nothing
WriteBinaryDataToFile = True
Exit Function
ErrorHandler:
If Not stream Is Nothing Then
If stream.State = adStateOpen Then stream.Close
Set stream = Nothing
End If
MsgBox “バイナリデータファイルの書き込み中にエラーが発生しました: ” & Err.Description, vbCritical
WriteBinaryDataToFile = False
End Function
解説:
- `sourceStream.Type = adTypeTextBinary`: ストリームをバイナリモードに設定します。
- `bytesRead = sourceStream.Read(buffer)`: `buffer` 配列に最大 `bufferSize` バイトを読み込みます。実際に読み込めたバイト数が返されます。
- `destStream.Write buffer(0 To bytesRead – 1)`: 実際に読み込めたバイト数 (`bytesRead`) だけを `destStream` に書き込みます。配列全体ではなく、読み込んだ範囲を指定することが重要です。
- `bufferSize`: 大きなファイルを扱う場合、一度に読み込むデータ量を調整することで、メモリ使用量とパフォーマンスのバランスを取ります。1MB程度 (`1024 1024`) が一つの目安ですが、環境やファイルの種類によって最適値は変わります。
- `FileSystemObject`: ディレクトリが存在しない場合に自動で作成するように、`FileSystemObject` を利用してディレクトリの存在確認と作成を行っています。これは、ファイル書き込み時の堅牢性を高めるための基本的な対策です。
運用上の注意点と堅牢な設計
1. エラーハンドリングの徹底: ファイルIOは予期せぬエラー(ディスク容量不足、アクセス権限、ファイルロックなど)が発生しやすい操作です。`On Error GoTo` を適切に配置し、リソース(特に `ADODB.Stream` オブジェクト)の解放を確実に行う設計は必須です。
2. ファイルパスの管理: 相対パスではなく、絶対パスを使用することを推奨します。必要であれば、VBAの `ThisWorkbook.Path` などと組み合わせて、実行ファイルからの相対パスを生成するようにします。
3. 文字コードの特定: 外部から受け取るファイルの文字コードが不明な場合は、推測するロジック(BOMの有無、特定のバイトパターンなど)を実装するか、ユーザーに指定させる必要があります。`ADODB.Stream` は文字コードを「指定」するもので、「自動判別」する機能はありません。
4. リソースの解放: `ADODB.Stream` オブジェクトは、使用後に必ず `Close` メソッドを呼び出し、`Set obj = Nothing` で解放してください。参照カウントが正しく管理されないと、メモリリークやリソースの枯渇につながる可能性があります。特にループ内で繰り返し生成・破棄する場合は注意が必要です。
5. パフォーマンス: 巨大なファイルを扱う場合、バッファサイズ(`CopyBinaryFile`の`bufferSize`)の調整がパフォーマンスに影響します。小さすぎるとIO回数が増え、大きすぎるとメモリを圧迫します。プロファイリングを行い、最適な値を見つけることが重要です。
6. VB.NETとの連携: VBAで扱いにくい複雑なファイル操作や、より高度なストリーム処理が必要な場合は、VB.NETでDLLを作成し、VBAからCOM連携で呼び出すという選択肢も視野に入れると良いでしょう。VB.NETでは `System.IO` 名前空間が強力なファイルIO機能を提供します。
まとめ:バイナリデータを制する者は、業務を制す
`ADODB.Stream`は、Excel VBAでテキストファイルだけでなく、あらゆる形式のバイナリファイルを扱うための強力なツールです。文字コードの自由な変換、生のバイト列へのアクセスは、これまでVBAでは難しかった領域への扉を開きます。
今回紹介したコード例は、そのまま実務で利用できる堅牢性と保守性を意識して設計しました。エラーハンドリング、リソース管理、そして具体的なユースケースを想定した構成となっています。
この知識を武器に、あなたの業務効率化ツールの可能性を無限に広げてください。次なる課題は、API連携によるWebデータ(JSON, XML)の取得・加工、あるいはデータベースとの高度な連携かもしれません。それらもまた、`ADODB.Stream`の応用や、さらに進んだ技術で攻略可能です。
さあ、未知なるファイル形式への挑戦を始めましょう。あなたのコードが、よりパワフルに、よりスマートになることを期待しています。
