ものがたり(旧)

atsushieno.hatenablog.com に続く

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のソースを見てちょうだいということで。