実行時コンパイル
ソースコードを生成する方法と同じ要領でCodeDomを利用すれば、アプリケーション実行時に実行可能なアセンブリコードを生成することができます。これは、私たちが作成したアプリケーションからCodeDomを用いて実行可能ファイルを出力することが可能であることを意味しています。
実行可能なアセンブリコードの生成
CodeDomProviderクラスを実装しているオブジェクトを用いてCodeCompileUnitオブジェクトからソースコードを生成したように、CodeCompileUnitオブジェクトのプログラム構造に従ってプログラムをコンパイルすることができます。
public virtual CompilerResults CompileAssemblyFromDom ( CompilerParameters options, params CodeCompileUnit[] compilationUnits )
optionsは、コンパイラに与える設定情報を表すSystem.CodeDom.Compiler.CompilerParametersクラスのオブジェクトを指定します。コンパイル結果の保存方法や、DLLを生成するのかEXEを生成するのかなどを指定することができます。
System.Object System.CodeDom.Compiler.CompilerParameters
[SerializableAttribute] public class CompilerParameters
このクラスのコンストラクタは、いくつかにオーバーロードされていますが、ここでは最も単純なパラメータを受けないコンストラクタを使いましょう。
public CompilerParameters ()
実行可能ファイルを生成するのか、DLLファイルを生成するのかはGenerateExecutableプロパティで設定することができます。このプロパティがtrueの場合は実行可能ファイル、すなわちEXEファイルが生成され、falseの場合はDLLが生成されます。既定ではfalseが設定されています。EXEファイルを生成する場合、エントリーポイントが必要であることを忘れないでください。
public bool GenerateExecutable { get; set; }
出力するアセンブリの名前はOutputAssemblyプロパティで設定します。生成されるEXEやDLLは、このプロパティの名前に従います。
public string OutputAssembly { get; set; }
CompileAssemblyFromDom()メソッドの結果は、戻り値であるSystem.CodeDom.Compiler.CompilerResultsクラスのオブジェクトから取得することができます。
System.Object System.CodeDom.Compiler.CompilerResults
[SerializableAttribute] public class CompilerResults
例えば、コンパイルされた結果となるアセンブリや、コンパイラが出力した文字、発生したエラーや警告、コンパイラの戻り値などもこのクラスのオブジェクトが保有しています。ここでは、コンパイラが出力したメッセージを提供するOutputプロパティを紹介します。
public StringCollection Output { get; }
Outputプロパティから取得するStringCollectionは、メッセージの配列を管理するstring型のコレクションオブジェクトです。コンパイルエラーが発生した場合などは、エラー原因などを表すメッセージをOutputプロパティから取得することができます。コンパイル結果をユーザーに通知する手段などに利用できるでしょう。
using System; using System.CodeDom; using System.CodeDom.Compiler; using Microsoft.CSharp; class Test { public static void Main() { CodeCompileUnit compileUnit = new CodeCompileUnit(); CodeNamespace name = new CodeNamespace("Sample01"); CodeTypeDeclaration type = new CodeTypeDeclaration("Test"); CodeEntryPointMethod method = new CodeEntryPointMethod(); type.Members.Add(method); name.Types.Add(type); compileUnit.Namespaces.Add(name); CSharpCodeProvider provider = new CSharpCodeProvider(); CodeGeneratorOptions option = new CodeGeneratorOptions(); CompilerParameters param = new CompilerParameters(); CodeCompileUnit[] units = { compileUnit }; param.GenerateExecutable = true; param.OutputAssembly = "output.exe"; provider.GenerateCodeFromCompileUnit(compileUnit,Console.Out, option); CompilerResults result = provider.CompileAssemblyFromDom
(param, units); foreach (string t in result.Output) Console.WriteLine(t); } }
//------------------------------------------------------------------- // // このコードはツールによって生成されました。 // ランタイム バージョン:2.0.50727.42 // // このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 // コードが再生成されるときに損失したりします。 // //------------------------------------------------------------------- namespace Sample01 { public class Test { public static void Main() { } } }
Sample02は、エントリーポイントを保有するCodeCompileUnitオブジェクトを用意し、これをGenerateCodeFromCompileUnit()メソッドからコンパイルしています。どのようなコードがコンパイルされたのかを視覚的に確認するため、同一のCodeCompileUnitオブジェクトをC#言語のソースコードに変換して標準出力にも出力させています。特にエラーが発生しなければ、コンパイラ設定に基づく名前のアセンブリが生成されているはずです。
このプログラムで生成した実行ファイルは、出力されたソースを見て分かるように何もしません。Main()メソッドの内部のステートメントは何も定義していないので、出力した実行ファイルを起動しても何も表示せずに終了するだけです。このサンプルでは、コードが適切にコンパイルされ、実行ファイルが生成されることを確認してください。
例えば、エントリーポイントを削除すれば意図的にエラーを発生させることができます。エラーを発生させた場合は、CompilerResultsオブジェクトのOutputプロパティから次のようなエラー報告が得られます。
//-------------------------------------------------------------------- // // このコードはツールによって生成されました。 // ランタイム バージョン:2.0.50727.42 // // このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 // コードが再生成されるときに損失したりします。 // //-------------------------------------------------------------------- namespace Sample01 { public class Test { } } ...\sample02> "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe"/t:exe /utf8output /out:"output.exe" /debug- /optimize+ "...\Local
Settings\Temp\fkyrdlq3.0.cs" Microsoft(R) Visual C# 2005 Compiler version 8.00.50727.42 for Microsoft(R) Windows(R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved. error CS5001: プログラム '...\sample02\output.exe'は、エントリ ポイント
に適切な静的'Main'メソッドを含んでいません
この結果を見れば、プログラムが内部で生成したソースコードを一時ファイルに保存し、cscコンパイラを別のプロセスとして起動してコンパイルしていることが分かります。
コード自動生成ツール
本稿で解説したCodeDomを使えば、さまざまな形で.NETアプリケーション開発を支援するツールを実現することができます。例えば、あなたが.NETアプリケーションを開発する独自のプログラミング言語を作り、そのプログラミング言語のソースコードからCodeCompileUnitオブジェクトを生成するパーサを開発することで、他の.NET系言語に変換したり、コンパイルすることができるようになります。また、専用のCodeDomProviderの実装を提供することでCodeCompileUnitから独自言語への変換も可能になります。
もし、CodeDomだけで開発することができるツールを開発(つまり、ビジュアルな方法でCodeCompileUnitオブジェクトを完成させる方法を提供)すれば、そのツール1つで、C#やVisual Basic .NETなどの言語のソースを自動生成させることができるようにもなります。本稿で紹介したCodeDomの機能はまだ一部であり、ステートメントの定義などを含めるとCodeDomは非常に複雑なので、簡単な作業とは言えませんが、技術的には実現可能ではないでしょうか。
そこで、System.Windows.Formsで提供されている標準コントロールのPropertyGridクラスを用いて、シンプルなCodeDomによるビジュアルな開発ツールを作成してみました(Sample03)。このツールでは、フォーム上で名前空間やクラスを追加して、最終的にC#やVisual Basic .NETのソースコードを出力することができます。このプログラムはコード量が少々多いので、興味のある方はサンプルをダウンロードしてご覧ください。Visual Studio 2005のプロジェクトデータです。
このプログラムでは、最初に[追加]ボタンを押して任意の名前空間を追加します。その後、追加した名前空間を選択し、右側のPropertyGridコントロールでオブジェクトのプロパティを編集します。名前空間や型を追加したら、[ファイル]メニューから[C#コードを生成]または[VB.NETコードを生成]を選んでください。入力したプログラムの構造に従ってCodeCompileUnitオブジェクトがソースコードに変換され、指定したファイルに保存されます。
PropertyGridは非常に便利ですが、このプログラムでは何もカスタマイズしていないので、すぐに限界に気付くことになるでしょう。このプログラムでできることはせいぜい型の宣言までで、詳細なメンバやステートメントを構築するには不十分です。あらゆるプログラムをCodeDomで表現したいと考えるならば、やはり専用のユーザーインターフェイスを構築しなければなりません。
最後に
System.CodeDom名前空間は、一般的なアプリケーション開発やプロジェクトではめったに必要とされることはありません。しかしながら、動的なコードの生成はアプリケーションに新しい角度での柔軟性を与えます(ユーザーが自由にコードを生成して実行できる仕組みの場合、セキュリティの問題も新たに発生しますが)。
開発環境や開発支援ツールの開発に利用できることはもちろんですが、オブジェクト構造を用いてソースをモデル化するという手法は、実行時コード生成や実行時コード解析の世界で大きな役に立つことでしょう。System.Reflection名前空間によるリフレクションと組み合わせることで、さらに面白いことができるかもしれません。実行時コード生成をどのように応用するかは、まだまだ大きな可能性があると考えています。

Console.Out, option);
CompilerResults result = provider.CompileAssemblyFromDom