LoginSignup
1
2

More than 1 year has passed since last update.

【VBScript】VBScriptでバイナリファイルを扱う

Last updated at Posted at 2022-01-17

VBScriptでバイナリデータを扱う

VBScript(VBS、WSH)でバイナリデータ・バイナリファイルの扱いについて調べてみると、なかなか上手く行うのは難しいものであるようです。ここでは、これをやれば一応可能です、ということで共有します。

要約

バイナリファイルの読み書き自体は、ADODBを使えば可能です。
しかし、これで扱うデータはBytes()という型のオブジェクト(以下、バイト列と称します)で、これはVBScriptにとって標準的な型ではなく、生成や編集ができない、配列っぽくて配列ではない扱いにくいものです。
しかし、DOMDocumentを使えば、このバイト列(例:{15,00,255})と16進数文字列(例:"0F00FF")の相互変換が可能になります。一度変換してしまえば16進数文字列は単なる文字列なので、速度を気にしなければMIDやREPLACEなどで抽出や加工が可能ですし、数値配列に変換することもできます。

ライブラリ

ADODB.Stream

前回にも出てきたADODBです。Typeを1にするとバイナリになります。Read()で全データが取れます。
全データを取っていますが、ReadTextで全件取るのとは異なりバイナリだと速いようなのでそのまま使えそうです。
やろうとすれば一部範囲のみをとることはできます。

MSXML2.DOMDocument

MSXML2.DOMDocumentは文字通り本来DOMを扱うもので、XMLの解析など出来ます。ネット越しに読み込む機能もあるようです。
DataTypeをbin.hexというものに指定すると、dom.Textが16進数文字列、NodeTypedValueがバイト列に対応するようになり片側に値を代入すると反対側に変換後の値が入っている状態になります。
見つけたサンプルはMicrosoft.XMLDOMでしたが、こちらの方が後継で同じことができました。

実装:バイナリファイルを16進数文字列で

二つのライブラリを同時に利用して、VBSの変数を経由せずに片方の戻り値をもう片方の引数に直接渡します。
DOMDocumentの処理はかなり速く、速度について意識する必要(あるいは余地)はありません。

Function loadBinaryToHexString(filename)
    loadBinaryToHexString = ""
    Dim dom : Set dom = CreateObject("MSXML2.DOMDocument").createElement("temp")
    dom.DataType = "bin.hex"
    Dim stream: Set stream = WScript.CreateObject("ADODB.Stream")
    stream.Type = 1 '  adTypeBinary
    stream.Open 
    stream.LoadFromFile filename
    dom.NodeTypedValue = stream.Read()
    loadBinaryToHexString = UCASE(dom.Text)
    stream.Close
    Set stream = Nothing
    Set dom = Nothing
End Function

Function saveHexStringToBinary(filename, hexStr)
    saveHexStringToBinary = false
    Dim dom : Set dom = CreateObject("MSXML2.DOMDocument").createElement("temp")
    dom.DataType = "bin.hex"
    Dim stream: Set stream = WScript.CreateObject("ADODB.Stream")
    stream.Type = 1 '  adTypeBinary
    stream.Open 
    dom.Text = hexStr
    stream.Write dom.NodeTypedValue
    stream.SaveToFile filename , 2  ' adSaveCreateOverWrite
    stream.Close
    Set stream = Nothing
    Set dom = Nothing
    saveHexStringToBinary = true
End Function

2文字で1バイトを表すので、文字列長の半分がファイルサイズになります。
MIDでiバイト目のデータを取り出すなら、CBYTE("&H" & MID(hexStr, i*2 + 1, 2)となります。

実装:16進数文字列を数値配列で

数値配列(ここではByte型)の方が処理しやすいということで、16進数文字列と数値配列の相互変換です。

Function convertHexStringToByteArray(hexStr)
    Dim i
    Dim size : size = LEN(hexStr) / 2
    Dim byteArray() : ReDim byteArray(size - 1)
    For i= 0 To size - 1
        byteArray(i) = CBYTE("&H" & MID(hexStr,i*2 + 1,2))
    Next
    convertHexStringToByteArray = byteArray
End Function

Function convertByteArrayToHexString(byteArray)
    Dim i
    Dim size : size = UBOUND(byteArray) + 1
    Dim hexArray() : ReDim hexArray(size - 1)
    For i= 0 To size - 1
        hexArray(i) = RIGHT("00" & HEX(byteArray(i)), 2)
    Next
    convertByteArrayToHexString = JOIN(hexArray,"")
End Function

前回にも書いた通り、文字列の結合は文字列配列に入れてJOINするのが速いです。
とはいえ、ループ回数が多いので、どちらの変換もそれなりにモタつきます。

使ってみる

使ってみます。出力の所はコメント化して可読性を上げています。

    'WScript.Echo(TIME & " : load start")
    Dim hexStr : hexStr = loadBinaryToHexString("C:\_Work_\vbScript\BinaryFileUtil\input.png")
    'WScript.Echo(TIME & " : loaded")
    'WScript.Echo(TIME & " : dump:"      & Left(hexStr,128) & "...")
    'WScript.Echo(TIME & " : filesize :" & (LEN(hexStr)/2))
    Dim byteArray : byteArray = convertHexStringToByteArray(hexStr)
    'WScript.Echo(TIME & " : converted array")
    'WScript.Echo(byteArray(1) & "," & byteArray(2) & "," &byteArray(3) & vbcrlf _
    '           & CHR(byteArray(1)) & "," & CHR(byteArray(2)) & "," & CHR(byteArray(3)))
    Dim w : w  = ((byteArray(16 + 0) * 256 + byteArray(16 + 1) ) * 256 + byteArray(16 + 2) ) * 256 + byteArray(16 + 3)
    Dim h : h  = ((byteArray(20 + 0) * 256 + byteArray(20 + 1) ) * 256 + byteArray(20 + 2) ) * 256 + byteArray(20 + 3)
    'WScript.Echo(TIME & " : imagesize:" & w & "x" & h)
    saveHexStringToBinary "C:\_Work_\vbScript\BinaryFileUtil\output.png", hexStr
    'WScript.Echo(TIME & " : saved 1")
    h = FIX(h / 2)
    byteArray(20 + 3) = h mod 256 : h = FIX(h / 256)
    byteArray(20 + 2) = h mod 256 : h = FIX(h / 256)
    byteArray(20 + 1) = h mod 256 : h = FIX(h / 256)
    byteArray(20 + 0) = h mod 256 
    hexStr = convertByteArrayToHexString(byteArray)
    'WScript.Echo(TIME & " : converted hex")
    saveHexStringToBinary "C:\_Work_\vbScript\BinaryFileUtil\output2.png", hexStr
    'WScript.Echo(TIME & " : saved 2")

PNGファイルは、(0バイト目から数えて)1~3バイトにはPNGという文字列が入っています。
また、16~19バイト目に横方向のサイズ、20~23バイト目に縦方向のサイズが入っています。
それらを表示して、無変換の16進数文字列をoutput.pngで保存。
縦方向のサイズを半分にして、20~24バイト以降を更新してからoutput2.pngで保存してみます。

C:_Work_\vbScript\BinaryFileUtil>cscript //nologo SamplePng.vbs
7:25:14 : load start
7:25:14 : loaded
7:25:14 : dump:89504E470D0A1A0A0000000D49484452000014000000140008020000003957E4D
1000000097048597300000EC400000EC401952B0E1B0000200049444154789C...
7:25:14 : filesize :13342807
7:25:25 : converted array
80,78,71
P,N,G
7:25:25 : imagesize:5120x5120
7:25:25 : saved 1
7:25:37 : converted hex
7:25:37 : saved 2

vbsBinary.jpg

output.pngとして同じファイルが保存できています。
output2.pngはファイルサイズは同じでヘッダしか違わないわけですが、サムネイルで見ると分かるとおり縦が半分として扱われています。(もちろん厳密には不正なファイルなので実用はできませんけれど)

速度について

上記出力の通り、13MBのPNGファイル(縦横5120pxの正方形)を、読出と保存、バイト列と16進数文字列の相互変換は一瞬で行う事ができます。一方で、16進数文字列と数値配列に変換するのはそれぞれ10秒少しも掛かっています。(純粋にバイト数に比例するようで、1MBなら1秒くらいですみます)

扱うファイルサイズによっては、必要な部分だけ切り出して数値配列を行ったり、16進数文字列のまま処理をしたりと、環境・やりたいこと・待てるかなどに応じた最適化が必要なようです。

余談

なお、ADODBで読み込んだバイト列ですが、むりやりMIDB関数で文字列として扱いつつ1文字を抽出。
ASCBでその文字コードを取る、という2段攻撃で16進数文字列化しなくとも数値として取り出すことが出来ます。

    Dim bytes : bytes = stream.Read()
    For i= 0 To UBOUND(bytes) : WScript.Echo(ASCB(MIDB(bytes,i + 1,1))) : Next

さらに、bytesをCSTRで文字列にすると、型の解釈がシンプルになるのか目に見えて高速になります。

    Dim byteStr : byteStr = CSTR(stream.Read())
    For i= 0 To LENB(byteStr)-1 : WScript.Echo(ASCB(MIDB(byteStr,i + 1,1))) : Next

ただし、これをしたところで、
上記の16進数文字列→数値配列化と同じくらいの時間が掛かり(文字列1バイトずつ抽出して数値変換‥‥と、やってることが同じ)なので、あまり意味はありません。DOMDocumentの16進数文字列が一瞬でできるのが強い。
加えて、保存時にはこういった事が出来ないので結局16進数文字列にしなければなりません。

VBScriptは手軽に扱えるのがメリットですが、大きいデータを扱うとなるとそれなりに大変です。最適解とかを考え出すと逆に手軽さが失われるので、こだわりすぎに注意が必要ですね。

とにかく、こうすることで可能になるよ、ということで残しておきます。


独学のため正確でない可能性があります。
(っ・x・)っ きゅ

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2