SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

CodeDomによる実行時コード生成術

アプリケーション実行時に実行コードを生成する動的技術を探る

実行時コンパイル

 ソースコードを生成する方法と同じ要領でCodeDomを利用すれば、アプリケーション実行時に実行可能なアセンブリコードを生成することができます。これは、私たちが作成したアプリケーションからCodeDomを用いて実行可能ファイルを出力することが可能であることを意味しています。

実行可能なアセンブリコードの生成

 CodeDomProviderクラスを実装しているオブジェクトを用いてCodeCompileUnitオブジェクトからソースコードを生成したように、CodeCompileUnitオブジェクトのプログラム構造に従ってプログラムをコンパイルすることができます。

CodeDomProviderクラスのCompileAssemblyFromDom()メソッド
public virtual CompilerResults CompileAssemblyFromDom (
    CompilerParameters options,
    params CodeCompileUnit[] compilationUnits
)

 optionsは、コンパイラに与える設定情報を表すSystem.CodeDom.Compiler.CompilerParametersクラスのオブジェクトを指定します。コンパイル結果の保存方法や、DLLを生成するのかEXEを生成するのかなどを指定することができます。

System.CodeDom.Compiler.CompilerParametersクラス(継承階層)
System.Object
  System.CodeDom.Compiler.CompilerParameters
System.CodeDom.Compiler.CompilerParametersクラス(C#での構文)
[SerializableAttribute]
public class CompilerParameters

 このクラスのコンストラクタは、いくつかにオーバーロードされていますが、ここでは最も単純なパラメータを受けないコンストラクタを使いましょう。

CompilerParametersクラスのコンストラクタ
public CompilerParameters ()

 実行可能ファイルを生成するのか、DLLファイルを生成するのかはGenerateExecutableプロパティで設定することができます。このプロパティがtrueの場合は実行可能ファイル、すなわちEXEファイルが生成され、falseの場合はDLLが生成されます。既定ではfalseが設定されています。EXEファイルを生成する場合、エントリーポイントが必要であることを忘れないでください。

CompilerParametersクラスのGenerateExecutableプロパティ
public bool GenerateExecutable { get; set; }

 出力するアセンブリの名前はOutputAssemblyプロパティで設定します。生成されるEXEやDLLは、このプロパティの名前に従います。

CompilerParametersクラスのOutputAssemblyプロパティ
public string OutputAssembly { get; set; }

 CompileAssemblyFromDom()メソッドの結果は、戻り値であるSystem.CodeDom.Compiler.CompilerResultsクラスのオブジェクトから取得することができます。

System.CodeDom.Compiler.CompilerResultsクラス(継承階層)
System.Object
  System.CodeDom.Compiler.CompilerResults
System.CodeDom.Compiler.CompilerResultsクラス(C#での構文)
[SerializableAttribute]
public class CompilerResults

 例えば、コンパイルされた結果となるアセンブリや、コンパイラが出力した文字、発生したエラーや警告、コンパイラの戻り値などもこのクラスのオブジェクトが保有しています。ここでは、コンパイラが出力したメッセージを提供するOutputプロパティを紹介します。

CompilerResultsクラスのOutputプロパティ
public StringCollection Output { get; }

 Outputプロパティから取得するStringCollectionは、メッセージの配列を管理するstring型のコレクションオブジェクトです。コンパイルエラーが発生した場合などは、エラー原因などを表すメッセージをOutputプロパティから取得することができます。コンパイル結果をユーザーに通知する手段などに利用できるでしょう。

sample02(test.cs)
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のプロジェクトデータです。

CodeDomによるビジュアル開発ツール(Sample03)
CodeDomによるビジュアル開発ツール(Sample03)

 このプログラムでは、最初に[追加]ボタンを押して任意の名前空間を追加します。その後、追加した名前空間を選択し、右側のPropertyGridコントロールでオブジェクトのプロパティを編集します。名前空間や型を追加したら、[ファイル]メニューから[C#コードを生成]または[VB.NETコードを生成]を選んでください。入力したプログラムの構造に従ってCodeCompileUnitオブジェクトがソースコードに変換され、指定したファイルに保存されます。

 PropertyGridは非常に便利ですが、このプログラムでは何もカスタマイズしていないので、すぐに限界に気付くことになるでしょう。このプログラムでできることはせいぜい型の宣言までで、詳細なメンバやステートメントを構築するには不十分です。あらゆるプログラムをCodeDomで表現したいと考えるならば、やはり専用のユーザーインターフェイスを構築しなければなりません。

最後に

 System.CodeDom名前空間は、一般的なアプリケーション開発やプロジェクトではめったに必要とされることはありません。しかしながら、動的なコードの生成はアプリケーションに新しい角度での柔軟性を与えます(ユーザーが自由にコードを生成して実行できる仕組みの場合、セキュリティの問題も新たに発生しますが)。

 開発環境や開発支援ツールの開発に利用できることはもちろんですが、オブジェクト構造を用いてソースをモデル化するという手法は、実行時コード生成や実行時コード解析の世界で大きな役に立つことでしょう。System.Reflection名前空間によるリフレクションと組み合わせることで、さらに面白いことができるかもしれません。実行時コード生成をどのように応用するかは、まだまだ大きな可能性があると考えています。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

赤坂 玲音(アカサカ レオン)

平成13年度「全国高校生・専門学校生プログラミングコンテスト 高校生プログラミングの部」にて最優秀賞を受賞。2005 年度~ Microsoft Most Variable Professional Visual Developer - Visual C++。プログラミング入門サイト WisdomSoft の管理人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/605 2006/10/20 00:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー