2018年9月10日月曜日

System.Reflection.Metadataを使用する

みなさん、こんにちは、旧HAZAMAです。他の場所では既にハンドルネームを昔のものに戻しているんですが、こちらでも戻すことにしました。改めましてtrain12です。

こちらのブログの投稿としては4ヶ月ぶりになってしまいました。今回は表題の通り、System.Reflection.Metadataというライブラリについての投稿です。
.NETが変化し始めてもう数年でしょうか、.NET Coreが2.1になり、だいぶ成熟していますが、これを使う上で問題になることが一つあります。そう、標準では式木などで生成したコードをアセンブリファイルに保存できないのです。.NET Frameworkでは、アセンブリを生成する際にオプションを指定することで保存も可能になるのですが、.NET Coreにはそんなオプションは用意されていません。そこで使用することになるのが表題のライブラリです。System名前空間にあることからわかる通り、将来的にはCoreに添付されるようになるのかもしれません。現状は、NuGet経由でインストールすることになります。
さて、肝心のこのライブラリの使用方法ですが、驚くことにドキュメントがありません。オープンソースなのでソースコードを見に行くとそこにはXMLコメントがあるのですが、その程度。基本的な使い方ですら、いろいろ試行錯誤して捻り出さなければならないという始末になっています。MSが開発しているようなのですが、ちょっとここらへんはお粗末としか言いようがありません。
さて、単独でアセンブリを生成する方法はまだこれから試行錯誤しなければならない段階にあるのですが、この記事では既にSystem.Reflection名前空間で吐いたアセンブリを読み込んで編集してファイルに保存し直す方法をお見せします。ただ、コードを全部載せると長くなりすぎてしまう(具体的には、System.Reflection.Metadataで動く状態のインスタンスを作るコードだけで360行ありました)ので、ここでは概要だけを示すに留めます。単純に既にあるアセンブリをいじって保存したいだけなのにできない人たちの手助けになればいいでしょう。


  1. まずは何はともあれ、PEHeaderBuilderを作成します。基本的に元の値をそのまま入れれば大丈夫なはずです。
  2. ILをコピーします。ここがちょっと曲者で、なしでも動くかもしれませんが私は、Roslynのコードを参考に多少変形をかけています。この時にMethodBodyStreamEncoder.MethodBodyからOffsetを拾ってくるのを忘れずに。これを記録しておかないと、RVA(RelationalVirtualAddressの略です、多分)が算出できなくなります。
  3. MetadataBuilderを作ります。これはMetadataBuilderのインスタンスを生成後、各テーブルの情報を押し込むところまで含めています。この部分だけで200行近く行くはずです。
  4. MetadataBuilderからMetadataRootBuilderを作ります。
  5. あとはこれらをManagedPEBuilderに放り込み、BlobBuilderにSerializeして、最後にWriteContentToでStreamに書き込めば完成のはずです。
こんな概要を見るよりも、私の自作言語のコンパイラの該当箇所を読んだ方が早いかもしれないので、該当箇所のURLを貼っておきます。

これがMSの中の人に聞きつつ、捻り出したSystem.Reflection.Metadataの使い方です。ドキュメンテーションのないライブラリを利用するのは辛いですね。今回ので身に沁みました。
中の人によると、全然ドキュメンテーションが書けてないようなので、しばらくはこれが活用されるかもしれませんね。