動的コンパイル

初回からナンですが、今回の話の中心は CodeDOM であって、リフレクションは最後のほうに少し出るだけです。次回以降は、リフレクションを主体にする予定ですが、これも「例」のひとつとして参考にしていただけたらと思います。

.NET には、実行時に外部のソース ファイルをコンパイルし、実行可能ファイルやアセンブリを生成するためのクラスが用意されています。今回は VB .NET で書かれたソース ファイルから、アセンブリを生成する方法について説明します。まず、TestClass.vb というファイルに、次のような実験用クラスを定義します。

Imports System

Public Class TestClass
    Public Function GetMessage() As String
        Return "メッセージ"
    End Function
End Class

次に、適当なコンソール プロジェクトを作り、以下のコードを貼り付けてください。コード中の ..\TestClass.vb の部分は、実際に TestClass.vb を置いているパスに書き換えてください。このコードではプロジェクトと同じフォルダにあると仮定しています。

Imports System.Reflection
Imports System.CodeDom.Compiler

Module Module1

    Sub Main()

        Dim vbc As ICodeCompiler = (New VBCodeProvider).CreateCompiler()
        Dim param As New CompilerParameters
        Dim result As CompilerResults = vbc.CompileAssemblyFromFile( _
            param, "..\TestClass.vb")

        If result.NativeCompilerReturnValue = 0 Then
            Dim asm As [Assembly] = result.CompiledAssembly

            Dim obj As Object = asm.CreateInstance("TestClass")
            Console.WriteLine(obj.GetType().GetMethod("GetMessage"). _
                Invoke(obj, Nothing))
        Else
            Console.WriteLine("Failed to compile.")
        End If

        Console.WriteLine("Enter を押すと終了します。")
        Console.ReadLine()

    End Sub

End Module

実行結果

メッセージ
Enter を押すと終了します。

Main の上部3行の意味については、クラスやメソッドの名前からして自明なので、CompilerParameters と CompilerResults の用法について説明したいと思います。CompilerParameters の用法は、名前のとおりコンパイルのパラメータを設定するものなのですが、そのひとつに「出力先の指定」というものがあります。上記コードのように出力先を指定しない場合は、一時ファイルを保存するフォルダに、ランダムな名前の DLL が作成されます。ファイル名を限定したい場合は、OutputAssembly プロパティに名前を設定します。また、ファイルとして生成されると問題がある場合、GenerateInMemory プロパティを True にします。文字どおりメモリ上に生成されます。DLL でなく EXE を生成したい場合は、プログラムのいずれかのクラスにエントリ ポイントを追加し、GenerateExecutable プロパティを True にします。

ところで今回は String しか使いませんでしたが、自分で別途作成したアセンブリのクラスを使用したい場合はどうすればいいのでしょうか。その場合は ReferencedAssemblies プロパティに、参照させたいアセンブリの名前を追加すると使用できるようになります。これは IDE のメニューから「参照の追加」を選び、参照するアセンブリを追加するのと同じことです。また、コンパイラを呼び出すアセンブリに定義されているクラスを使用したい場合、ReferencedAssemblies に自身のパスを追加し、完全限定名で型を指定することで使用可能になります。「参照の追加」では EXE を指定できませんが、こちらでは指定できます。

次に CompilerResults について説明します。コンパイルの成否は、NativeCompilerReturnValue プロパティの値で判断します。MSDN ライブラリによれば、0 であれば成功、それ以外はすべて失敗だそうです。いずれの場合でも、Output プロパティにメッセージが出力されます。失敗した場合は Errors プロパティを参照してください。成功した場合は、CompiledAssembly プロパティにアセンブリへの参照が設定されます。

アセンブリを生成したら、コンストラクタやメソッドへの参照を取得し、呼び出します。これを詳しく扱うと今回のテーマの範囲を超えるので、簡単なコードを掲載するだけにしました。説明は省略します。ちなみにファイルとしてアセンブリを生成した場合、自動的には削除されません。一時ファイルを保存するフォルダに、アルファベットの小文字、数字、アンダー スコアをランダムに組み合わせた8文字の名前をもつ DLL があるはずです。不都合があれば削除してください。

この方法による動的コンパイルは、C# で書かれたソースに対しても同様に行なうことができます。もちろん vbc.exe や csc.exe を起動してコンパイルしてもよさそうですが(実際、CompileAssemblyFromFile メソッドはこれらのプログラムを起動しているようですが)、パラメータをプロパティとして設定できるとか、出力を文字列のコレクションとして得られるなど、色々と便利なこの方法を用いることを推奨します。