CodeDomによるHello World

MSIL/ILによるHello Worldの次はCodeDomのHello Worldの実現。
以下のようなソースコードをCodeDomにより生成、コンパイル、実行してみる。

生成するソースコード

namespace CodeDomTest {
    using System;
    public class HelloWorld 
    {
        [STAThread()]
        public static void Main()
        {
            System.Console.WriteLine("Hello World");
        }
    }
}

HelloWorldクラスのソースコードを生成するCodeDomによるアプリケーション

using System;
using System.Text;
using System.IO;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

namespace CodeDomTest
{    
    class Application 
    {        
        [STAThread]
        static void Main(string args)
        {            
            //コードの入れ物、コンパイルの単位となるコンパイルユニットを生成
            CodeCompileUnit compileUnit = new System.CodeDom.CodeCompileUnit();

            //ネームスペースを追加
            CodeNamespace helloWorldNamespace = new CodeNamespace("CodeDomTest");
            compileUnit.Namespaces.Add(helloWorldNamespace);

            //インポートを追加
            helloWorldNamespace.Imports.Add(new CodeNamespaceImport("System"));
            //Applicationクラスを定義        
            CodeTypeDeclaration applicationClass = new CodeTypeDeclaration("HelloWorld");
            applicationClass.IsClass = true;
            helloWorldNamespace.Types.Add(applicationClass);

            //staticなMainメソッドを生成
            CodeMemberMethod mainMethod = new CodeMemberMethod();
            mainMethod.Name = "Main";
            mainMethod.Attributes = MemberAttributes.Static | MemberAttributes.Public;
            mainMethod.CustomAttributes.Add(new CodeAttributeDeclaration("STAThread"));
            applicationClass.Members.Add(mainMethod);

            //"Hello World" 出力
            CodeMethodInvokeExpression printHelloWorld = 
                new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(Console))
                , "WriteLine", new CodeExpression {new CodePrimitiveExpression("Hello World") });
            mainMethod.Statements.Add(printHelloWorld);

            //ソースコード生成
            TextWriter writer = new StreamWriter(new FileStream("HelloWorld.cs", FileMode.Create));
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            try
            {
                ICodeGenerator codeGenerator = codeProvider.CreateGenerator();
                codeGenerator.GenerateCodeFromCompileUnit(compileUnit, writer, null);
            }
            finally
            {
                writer.Close();
            }

            //生成したコードをコンパイル
            ICodeCompiler codeCompiler = codeProvider.CreateCompiler();
            CompilerParameters cparams = new CompilerParameters();
            cparams.ReferencedAssemblies.Add("system.dll"); 
            cparams.GenerateExecutable = true; 
            CompilerResults result = codeCompiler.CompileAssemblyFromFile(cparams, "HelloWorld.cs");

            //コンパイル結果からアセンブリを生成
            Assembly assembly = result.CompiledAssembly;
            object compiled = assembly.CreateInstance("CodeDomTest.HelloWorld");

            //アセンブリを実行
            MethodInfo methodInfo = compiled.GetType().GetMethod("Main");
            methodInfo.Invoke(compiled, null);
        }
    }
}

例によってコンパイル時のエラー処理等ははしょっているので試したい方は注意。生成されたソースコード"HelloWorld.cs"は実行ディレクトリに直接出力されるので内容を確認できます。

う〜ん..CodeDomはソースコードの殆ど全てをオブジェクトとして抽象化して扱うのでそれぞれの意味は解り易いんだけれどHello Worldを作るのにこれだけのコード量が必要なのは引きますね。やはり既存の型情報をベースにして別な型を作り出す時のテンプレート等を作るツールとして使うほうが生産的かも。

前回のILのEmitや今回のCodeDomのようにプログラムが他のプログラムを生成するのを専門的にはgenerative programming (生成的プログラミング)といいます。GenericsやAOP等でも使われている重要な技術ですね。

MSIL/CIL Reflection EmitによるHello World

MSIL/CILによるHello Worldの実現。

"Hello World"を標準出力に表示するプログラムを直接実行可能なILとして出力するアプリケーション

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;


public class EmitHelloWorld
{
    static void Main(string[] args)
    {
        // 動的にアセンブリを生成
        AssemblyName assemblyName = new AssemblyName(); 
        assemblyName.Name = "HelloWorld"; 
        AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        // モジュールも生成
        ModuleBuilder module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe"); 
      
        // クラスを定義 (.typeディレクティブに対応)
        TypeBuilder typeBuilder = module.DefineType("HelloWorld", TypeAttributes.Public | TypeAttributes.Class);
            
        // "Main" メソッドを定義 (.methodディレクティブに対応)
        MethodBuilder methodbuilder = typeBuilder.DefineMethod("Main"
                                                   , MethodAttributes.HideBySig | 
                                                        MethodAttributes.Static | 
                                                        MethodAttributes.Public
                                                   , typeof(void), new Type[] { typeof(string[]) });
            
        // メソッドの内容をILで生成、出力
        ILGenerator ilGenerator = methodbuilder.GetILGenerator();
        ilGenerator.EmitWriteLine("hello world");
        ilGenerator.Emit(OpCodes.Ret);
 
        // 型を生成
        Type helloWorldType = typeBuilder.CreateType();
 
        // 実行
        helloWorldType.GetMethod("Main").Invoke(null, new string[] {null});
 
        // .exeとして実行可能な形式で保存
        assemblyBuilder.SetEntryPoint(methodbuilder, PEFileKinds.ConsoleApplication);
        assemblyBuilder.Save("HelloWorld.exe");
    }
}

生成したILを実行するだけではつまらないのでPE形式として実行可能なアセンブリをカレントディレクトリに保存しています。
保存されたアセンブリをILDASM等で調べるとこのコードと出力されたILの内容の関係がよく解ると思います。