System.Resources関係のメモ
infocardなんてちっとも流行っていないものは放置して、.net 2.0の穴埋めの仕事をやっつけている。で、ここ2,3日mscorlibのSystem.Resourcesを眺めていたのだけど、1.xとはずいぶん違う物になっているみたいだし、微妙に内部動作に依存したAPI設計になっていて困ったりする。今日はそんなSystem.Resourcesについてちょこっと書いてみたい。役に立つ話ではないけど。
…役に立たない話なので続きを読むモードにしておこう。
ランタイム上存在しない型のデータ処理
ResourceWriter.AddResourceData(string name, string typeName, byte data) は、型の指定まで型名で指定してデータを追加する。ResourceReader.GetResourceData(string name, out string typeName, out byte data)はこの逆だ。
ResourceReader.GetEnumerator()で列挙されるオブジェクトの値を取得しようとすると、リソースに含まれるデータのうち、ランタイム型として存在しない(例えば、シリアライズした側にあって、こちら側にない)場合にはエラーになるがこれは誤りのようだ。nullになる、GetResourceData()を使えば、デシリアライズできなくてもリソースを読み込む処理が、出来なくもない。そしてそのようにして読み込んだデータを、AddResourceData()で書き戻すこともできる。
安全なデータ列挙を求めるのであれば、GetEnumerator()で取得したIDictionaryEnumeratorから、Valueを取得しようとしてはいけない。ただしどのリソースがどの型になるかは、事前に判別のしようがないので、Valueがnullだった場合にGetResourceData()をtry-catchで括るようなダメなやり方で処理するしかなさそうだ。
ResourceManager.GetStream()
ResourceManager.GetStream(string name)は、GetObject(string)やGetString(string)と同じで、指定されたリソースアイテムの内容をStreamの状態で返す。といっても(2.0で追加された)Streamオブジェクトとしてシリアライズされたオブジェクト(というかMemoryStreamしか思いつかないけど)のみが、これで取得できる。
で、その内容はUnmanagedMemoryStreamになっていて、僕が実装する時はMemoryStreamにでもして返したいのだけど(何しろresourcesファイルはStreamを開いているだけなので)、MemoryStreamはUnmanagedMemoryStreamではないので、UnmanagedMemoryStream「にして」返さないといけない。めんどくさい。というか効率が悪い。実装に依存した悪いAPIだ。
データフォーマット
v1とv2では違う。見た感じ↓のようになっているようだ。
- Magic Number (CE CA EF BE, in BE)
- Header Version Number (01 for both .net 1.x and 2.0)
- length for header type infos (int32BE)
- ResourceReader AssemblyQualifiedName (int32BE length, string AQN)
- ResourceSet type name (int32BE length, string)
- v1 : AssemblyQualifiedName
- v2 : FullName
- Resource Version, byte
- v1 : 01
- v2 : 02
- Resource count, int32BE
- Custom type count, int32BE
- type name (int32BE length, string) * type-count
- "PAD" bytes to align the next data to 8n bytes, using '', 'P', 'PA', ... to 'PADPADP'
- name section: items are sorted by hash
- name string hash (int23LE) * item-count (name-count); hash looks like DJB-ish
- name offsets (int32LE) * item-count (name-count)
- data offset (int32LE)
- name (7bitnum, string) * item-count (name-count)
- data section
BEはbig endian, LEはlittle endian。ちなみにBinaryWriter/BinaryReaderのフォーマットを利用している部分がいくつかあるといえる。文字列ハッシュはDJBっぽいが(既存のソースの定数でぐぐった)、演算には+ではなく^が使用されている部分がある。
data sectionやtypesの内容も違う。typesも、2.0ではstringやintなどの基本的な型の情報は入っていない。data sectionは型情報も含むようになった。
…これ以上の説明は不毛なのでそのうちコミットするResourceReader/ResourceWriterのソースを見てちょうだいということで。