Rubyでバイナリデータを読み書きする
Rubyでバイナリデータを読み書きするのには、pack, unpack使うじゃないですか。
僕もねぇ、Ruby使い始めてもう何年も経つんですけど、pack, unpackっていつまでたっても覚えられないし、
使うとなんか汚い煩雑なコードになっていくんですね。そんなことってないですか?
なんかライブラリないかなーとググりまして、こちらで紹介されているライブラリを一通り見てみました。
http://d.hatena.ne.jp/kenhys/20070522/1179848262
・binaryparser
・calibre-binaryreader
・bindata
・BitStructEx
しかし、なんかどれも複雑というか、いまいち直感的じゃないという気がします。
自分の理想の使い方の形は次のような感じです。
include(BinaryData) # float3要素のベクトル構造体定義 Vector = struct{ # Vectorの最初の要素はxで、float 32 bitの型という意味 var x: F32 # Vectorの二つ目の要素はyで、float 32 bitの型という意味 var y: F32 # まぁ!みて!このz要素の定義を!まるでJavaScriptの定義みたいだわ! var z: F32 } # 3x3のマトリックス構造体定義 Matrix = struct{ # Matrixの最初の要素はrowsで、ユーザーが定義したVector構造体の三つの要素を持つ配列という意味 var rows: Vector[3] # ユーザーが定義した構造体をネストして、しかも配列として使えるということね! } # Model = struct{ # 頂点の数をinteger 32 bit型として持つ var vertex_count: I32 # 上のvertex_countを長さとして使えるのね! var vertices: Vector[:vertex_count] var matrix: Matrix } # モデル構造体のインスタンス生成 model = Model.new # 値を格納するのも、直感的な記法なのね! model.vertex_count = 2; model.vertices[0].x = 1 model.vertices[0].y = 2 model.vertices[0].z = 3 model.vertices[1].x = 4 model.vertices[1].y = 5 model.vertices[1].z = 6 model.matrix.rows[0].x = 10 model.matrix.rows[1].y = 20 model.matrix.rows[2].z = 30 sio = StringIO.new # バイナリとして書き込む Model.write_to_stream(sio, model) #これでsioにはC言語で次のような感じで書いて出力したバイナリと同じフォーマットで入ってる # struct Vector{ float x, y, z; }; # struct Matrix{ Vector rows[3]; }; # struct Model{ int vertex_count; Vector vertices[vertex_count]; Matrix matrix; }; # Model model = {...}; # fwrite(fp, &model, 1, sizeof(model)); # ストリームを一番最初に戻す sio.pos = 0 # バイナリから読み込んで復元する model_deserialized = Model.read_from_stream(sio) # 文字列化して出力 print model_deserialized文字列化されたプリント結果
{:vertex_count=>2, :vertices=>[{:x=>1.0, :y=>2.0, :z=>3.0}, {:x=>4.0, :y=>5.0, :z=>6.0}], :matrix=> {:rows=> [{:x=>10.0, :y=>0.0, :z=>0.0}, {:x=>0.0, :y=>20.0, :z=>0.0}, {:x=>0.0, :y=>0.0, :z=>30.0}]}}で、そんな理想なライブラリの下地を作ってみました。
定義がいろいろと足りないんですけど、上のサンプルは動かせます。
Ruby1.9.1以上で動きます
require "stringio" require "pp" module BinaryData class StructType def [](key) return StructArrayType.new(self, key) end end class StructArrayData def initialize(type, data) @type = type @data = data end def data() @data end def fillup(i) while i>=@data.length @data.push(@type.new) end end def [](i) fillup(i) return @data[i] end def []=(i, v) fillup(i) @data[i] = v end end class StructArrayType < StructType def initialize(type, length) @type = type @length = length end def write_to_stream(stream, data, parent = nil) length = @length if(length.is_a?(Symbol)) length = parent[length]; end length.times{ |i| @type.write_to_stream(stream, data[i], parent) } end def read_from_stream(stream, parent = nil) length = @length; if(length.is_a?(Symbol)) length = parent[length]; end data = Array.new(length) length.times{ |i| data[i] = @type.read_from_stream(stream, parent) } return StructArrayData.new(@type, data); end def new return StructArrayData.new(@type, []) end end class PrimaryType < StructType def initialize(packformat, bytenum, defaultvalue) @packformat = packformat @bytenum = bytenum @defaultvalue = defaultvalue end def write_to_stream(stream, value, parent = nil) stream.write([value].pack(@packformat)) end def read_from_stream(stream, parent = nil) return stream.read(@bytenum).unpack(@packformat)[0] end def new() return @defaultvalue end end class StructMapData def initialize(type, data) @type = type @data = data end def data() return @data end def [](i) return @data[i] end def []=(i, v) @data[i] = v end def method_missing(name, *args) if name.to_s =~ /(\w+)=/ @data[$1.to_sym] = args[0] else return @data[name] end end end class StructMapType < StructType def initialize() @members = {} end def define(key, val) @members[key] = val end def write_to_stream(stream, data, parent = nil) @members.each_pair{ |key, type| type.write_to_stream(stream, data[key], data) } end def read_from_stream(stream, parent = nil) data = StructMapData.new(self, {}) @members.each_pair{ |key, type| data[key] = type.read_from_stream(stream, data) } return data end def new() data = StructMapData.new(self, {}) @members.each_pair{ |key, type| data[key] = type.new } return data; end end def extract(value) if(value.is_a?(StructMapData)) ret = {} value.data.each_pair{ |key, val| ret[key] = extract(val) } return ret end if(value.is_a?(StructArrayData)) return value.data.map{|val| extract(val) } end return value; end def struct(&block) ret = StructMapType.new(); def ret.var(arg) arg.each_pair{ |key, type| define(key, type) break } end ret.instance_eval(&block) return ret; end def print(v) pp extract(v) end I32 = PrimaryType.new("N", 4, 0) I8 = PrimaryType.new("c", 1, 0) F32 = PrimaryType.new("g", 4, 0.0) end>