IKVM.Reflectionを使って動的にMSIL/アセンブリを生成してみる
LL/MLほにゃららシリーズではMSILしか弄ってない者です、こんにちわ。最近仕事が忙しいので、軽めのネタとしてIKVM.Reflectionを利用して動的にMSIL/アセンブリを生成してみる例を紹介しておきます。
IKVM.Reflectionとは
まず、IKVMとは、.NETとJavaを相互運用させるためのソフトウェアです。相互運用とは言うものの、どちらかといえば.NETからJavaを使うためのツールで、「.NETで実装されたJavaVM」「.NETで実装されたJavaクラスライブラリ互換のライブラリ群」「Javaバイトコードから.NETアセンブリコードへの変換ツール群」といったコンポーネントで構成されています。IKVMを使うことでJavaで.NET開発することも可能です。
余談ですが、IKVMを利用してScalaから.NET開発をするという試みも存在しています。私も以前、Silverlight上でScalaを動かすというネタをやったことがあるのですが、実はこのプロジェクトの前身をちょっとごにょごにょしたものだったりします。
本題のIKVM.Reflectionですが、名前の通りIKVMのライブラリの一部です。IKVM.Reflection単体で独立したライブラリとして利用可能で、これを使うとMSIL/アセンブリを動的に生成することができます。
.NET標準ライブラリにもReflection.EmitというMSIL/アセンブリを動的生成するためのAPIが存在するのですが、IKVM.Reflectionの方がはるかに高機能です。IKVM.ReflectionはMono 2.10からMonoコンパイラのアセンブリ生成エンジンに利用されるようになったことからも、このライブラリが如何に強力であるかを示す一例であるかと思います。
動的にEXEを生成してみる
コンソールにHello Worldを表示するだけのEXEを動的に生成するコードの例です。グローバルメソッドを1つ定義して、それをエントリーポイントに設定しています。生成できたEXEファイルはそのまま実行可能です。
var assemblyName = "TestAssembly"; var moduleName = "TestModule"; var fileName = "test.exe"; using (var universe = new Universe()) { // アセンブリ/モジュールの定義 var assemlyBuilder = universe.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Save); var moduleBuilder = assemlyBuilder.DefineDynamicModule(moduleName, fileName); // function PrintHello() : void { System.Console.WriteLine("Hello"); } var methodBuilder = moduleBuilder.DefineGlobalMethod("PrintHello", MethodAttributes.Static, null, null); var ilgen = methodBuilder.GetILGenerator(); ilgen.EmitWriteLine("Hello"); ilgen.Emit(OpCodes.Ret); // EXEファイル出力 moduleBuilder.CreateGlobalFunctions(); assemlyBuilder.SetEntryPoint(methodBuilder); assemlyBuilder.Save(fileName); }
IKVM.Reflectionのコードサンプルはあまり情報がなく、日本語の情報は壊滅的なのですが、幸いなことにIKVM.Reflectionのインタフェースが標準のReflection.Emitに似せてあるため、Reflection.Emitを使ったことがある方であれば、特に苦もなくコードを書くことができると思います。
なお、IKVMはNuGetにもバイナリが公開されているのですが、今回はIKVM.Reflectionだけを利用したかったので、SourceForgeからダウンロードしたパッケージのうちIKVM.Reflection.dllのみを取り出して利用しています。
Windows8/WinRTとIKVM.Reflection
WinRT(.NET for Windows Store apps)では、Linq.Expressionsという式木(AST)から動的にコードを生成するAPIは公開されているのですが、それがバックエンド利用しているはずのReflection.Emitを触ることできません。また、Linq.Expressionsでは生成したアセンブリをファイルとして出力することが不可能です。
そこでWinRTでもIKVM.Reflectionが使えるかどうかを試してみたのですが、やっぱり使えませんでした。ただ、ソースをざっくり読んだ感じでは、「ReflectionとFileI/OまわりのAPIをWinRTに書き換え」「ファイル名を渡しているような部分をStreamを渡すようにインタフェース変更」といった改変を行えばWinRTでも動くようにはできそうです(実際にやってみると何か問題があるかもしれませんが…)。
IKVMのライセンスはzlibライセンスという緩いライセンスなので、暇な方はIKVM.Reflection for WinRTみたいな派生版を作ってみても良いのではないでしょうか。以前、Mono.CecilをWinRTで動くように移植した事がある身としては、本当はIKVM.ReflectionもWinRT移植してみたというネタでエントリを書きたかったのですが、仕事が忙しくてそこまで手が回りませんでした。そもそもHaxeチュートリアルの執筆も止まっている状態なので…。