バイナリデータを読み込むには
VBAではファイルを扱う場合はFileSystemObjectクラスを利用することが一般的ですが、バイナリファイルに関してはFileSystemObjectクラスでは扱えません。そのため、Openステートメントでバイナリファイルを開きます。
具体的な書き方は後述のサンプルコードになりますが、概要だけを書くとOpenステートメントを実行する際のMode引数でバイナリファイルであることを指定します。
バイナリデータを扱う場合は、だいたい以下の3パターンのいずれかでの読み込み方法になります。
- 全データを1度に読み込む方法
- 1バイトずつ読み込む方法
- 一定のサイズ(例えば500バイト)ごと読み込む方法
それぞれ少し書き方が異なる点があり、バイナリファイルを扱うときに一番ハマるのが「データ取得の書き方」なので、それぞれについて説明します。
なお、Openステートメントなどの詳細については「VBAでテキストファイルの読み書きを行う」をご参照ください。
バイナリファイルデータを1度に全て読み込む場合
全データを1度に読み込む方法は一番簡単な方法です。バイナリファイルのサイズが1KB程度などのように小さいことが事前に分かっている場合は、この方法をオススメします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
Sub ReadBinaryAllData() Dim sFilePath As String '// バイナリファイルパス Dim fn '// ファイル番号 Dim byAry() As Byte '// 読み込みデータ配列 Dim byData As Byte '// バイナリデータ1バイト分 Dim iSize '// ファイルサイズ Dim i '// ループカウンタ '// バイナリファイルパス設定 sFilePath = "C:\aaa\b.bin" '// ファイル番号取得 fn = FreeFile '// ファイルオープン Open sFilePath For Binary Access Read As #fn '// ファイルサイズ取得 iSize = LOF(fn) '// 配列の領域確保 ReDim byAry(iSize - 1) '// バイナリデータ読み込み byAry = InputB(iSize, fn) '// ファイルクローズ Close #fn '// 1バイトずつ読み込んで出力 For i = 0 To iSize - 1 '// 現ループの配列値を取得 byData = byAry(i) '// 改行コードの場合 If byData = 10 Or byData = 13 Then Debug.Print "改行です" End If '// 出力 Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]" Next End Sub |
ポイントは「Byte配列 = InputB(ファイルサイズ, ファイル番号)」の部分です。ここで全データを配列にセットしています。Byte配列の領域を事前に「Redim byAry(iSize – 1)」で確保していますが、確保しなくてもInputB()で確保された状態で格納されます。ただ、プログラミングの作法として配列の領域を確保しないのは意図的なのかコーディング漏れなのかが読みにくいため、確保しておく方がよいでしょう。
Byte型配列に格納したあとは必要な処理を行います。ここではくるくる回して1バイトずつ出力しているだけですが、実際のバイナリデータを扱う場合はByte型の16進数値がどうなのでこういうことをやる、という処理になることが多いと思います。
1バイトのByte型データの判定方法の書き方として改行コードの場合のサンプルを書いてます。
バイナリファイルデータを1バイトずつ読み込む場合
1バイトずつ判定して処理を行いたい場合はGetステートメントかInputB関数を使ってファイル終端まで1バイトずつ読み込むコードを書きます。
以下のコードはGetステートメントとInputB関数の両方を使って同じバイト位置を1バイトずつ取得して出力しています。もちろん実際に使う場合はGetかInputBのどちらか一方のみで構いません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
Sub ReadBinary1Byte() Dim sFilePath As String '// バイナリファイルパス Dim fn '// ファイル番号 Dim byData As Byte '// バイナリデータ1バイト分(Get用) Dim iSize '// ファイルサイズ Dim i '// ループカウンタ Dim s As String '// 読み込み文字列(InputB用) '// バイナリファイルパス設定 sFilePath = "C:\aaa\b.bin" '// ファイル番号取得 fn = FreeFile '// ファイルオープン Open sFilePath For Binary Access Read As #fn '// ファイルサイズ取得 iSize = LOF(fn) '// 1バイトずつ読み込んで出力 i = 0 Do Until EOF(fn) '// <Getステートメントでの書き方> '// 現ループの1バイトを取得 Get #fn, i + 1, byData '// 出力 Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]" '// <InputB関数での書き方> '// 現ループの1バイトを取得 s = InputB(1, #fn) '// 出力 Debug.Print "No." & CStr(i) & " - 0x" & AscB(s) & " - Char[" & Chr(AscB(s)) & "]" i = i + 1 Loop '// ファイルクローズ Close #fn End Sub |
バイナリデータを取得する際にGetステートメントとInputB関数のどちらを使ってもいいのですが、当然違いがあるため用途に合わせて使い分けをした方がいいでしょう。
Getステートメントは指定したファイルデータの位置(Seek関数での位置)から変数の領域分だけ読み取ります。言い方を変えると、変数に入る分だけ読み取ります。
InputB関数は第一引数で指定したサイズ分をバイナリデータのまま文字列として読み込みます。バイナリデータのまま、というのは似た関数にInput関数がありますが、こちらはUnicodeに変換した文字列が変数に格納されますが、InputB関数はなにも変換せず変数に文字列として格納します。
GetステートメントとInputB関数のいずれの場合も、可変サイズの取得を行うときは、ファイル終端を超えないように考慮が必要になります。
ちなみに私はバイナリデータを1バイトずつ取得するのであればGetステートメントを使うことが多いです。理由は、バイナリデータを扱うのであればByte型を使いたいところですが、InputB関数は文字列型(String型)を返すため感覚的に嫌、というのがあります。これは好みとしか言いようがないかもしれません。
バイナリファイルデータを一定サイズごとに読み込む場合
一番コードとして複雑になるのがここで紹介する一定サイズごとに読み込む場合です。どうしてもループ終端に達するかどうかの判定や読み込みサイズの調整が必要になります。
やらなくていいならこの方法はしない方がいいですが、バイナリファイルのサイズが大きい場合やサイズが不明の場合はこの方法しかない場合があります。
ここではGetステートメントでの書き方を紹介します。もちろんInputB関数でもコーディング可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
Sub ReadBinaryXByte() Dim sFilePath As String '// バイナリファイルパス Dim fn '// ファイル番号 Dim byAry() As Byte '// 読み込みデータ配列 Dim byData As Byte '// バイナリデータ1バイト分(Get用) Dim iArySize '// 配列サイズ Dim iSize '// ファイルサイズ Dim i '// ループカウンタ Dim iReadSize '// 読み込み済みサイズ '// バイナリファイルパス設定 sFilePath = "C:\aaa\b.bin" '// ファイル番号取得 fn = FreeFile '// ファイルオープン Open sFilePath For Binary Access Read As #fn '// ファイルサイズ取得 iSize = LOF(fn) '// 指定サイズを21バイトとして配列を確保(20と書いたら21バイト分確保される) iArySize = 20 ReDim byAry(iArySize) '// 1バイトずつ読み込んで出力 i = 0 Do Until EOF(fn) '// 配列サイズ分を読み込み Get #fn, , byAry i = 0 For i = 0 To iArySize '// 配列から1バイト取得 byData = byAry(i) '// 出力 Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]" Next '// 読み込み済みのサイズを設定(配列は20 + 1なのでその+1分も加算) iReadSize = iReadSize + iArySize + 1 '// 読み込みサイズがファイルサイズより小さい場合で、次の読み込みで終端に達する場合 If (iReadSize <= iSize) And (iSize - iReadSize < iArySize + 1) Then '// 次に読み込むサイズを計算 iArySize = iSize - iReadSize '// 配列を再構築 ReDim byAry(iArySize) End If Loop '// ファイルクローズ Close #fn End Sub |
ここでは21バイト(20 + 1)を配列のサイズとしてByte型配列を用意し、それをバイナリファイルのEOFに達するまで21バイトずつ取得していくコードです。
例えばバイナリファイルのサイズが150バイトだとした場合、ループごとで21、42、63、84、105、126、147、と読み込んでいきますが、終端の直前では150バイトから読み込み済みの147バイトを引いた3バイト+終端の4バイトが読み込むサイズになります。なお、終端はNULL(0x00)が1バイト読み込まれます。
そのため、ループの最後の部分で読み込みサイズが終端の直前かどうかの判定を行い、最後に読み込む配列サイズをRedimで調整しています。
ちなみに調整しなくても21バイトずつ取得しても終端に達するとループは抜けますが、その時の配列には終端の0x00以降の要素には全て0x00が設定されます。扱っているバイナリデータに0x00がないことが分かっているのであれば終端直前のRedimは不要ですが、その後で1バイトずつ処理することをあったり、0x00がデータとして存在している可能性があるなら配列のサイズ調整はしておいた方がよいでしょう。