空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

レイトバインディング

自前のC/C++ベースのWindowsソフトをJScriptで拡張可能にしたい,という要請に対して,COMベースのJScriptエンジンを利用する方法がある。それについては「C++で作ったアプリケーションとJScriptの連携」という記事に簡単な説明があるけれども,これを読んで,COMの鬱陶しさに萎えたのは自分だけではないはず。

どのみちWindows依存になってしまうのであれば,JScriptじゃなくて,JScript.NETを使ったらどうだろう・・・と思って調べてみた。.NET Framework を利用すると,驚くほどこれが簡単に実現できる。

やりたいことを,もう少し技術用語で言うと

  1. .NET のアセンブリを動的にビルド
  2. 動的にビルドしたアセンブリをレイトバインディング
  3. レイトバインディングしたクラス,メソッドを呼び出す

ということになる。

コードを動的にビルドするには,System.CodeDom.Compiler および Microsoft.JScript.JScriptCodeProvider の実装を使えばよい。JScript.NET って,実のところよく知らないので,今回は,単純に Microsoft.CSharp.CSharpCodeProvider を使ってみた。

レイトバインディングを行うには System.Reflection 以下のクラスを使い,System.Activator 使って,動的に読み込んだクラスのオブジェクト作成する。

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

namespace csharp_sandbox
{
  class Program
  {
    static void Main(string[] args)
    {
      string[] input = { @"C:\Temp\hoge.cs" };
      string output = @"C:\Temp\hoge.dll";
      CompilerParameters parameters = new CompilerParameters();
      parameters.GenerateExecutable = false;
      parameters.OutputAssembly = output;
      CompilerResults results = (new CSharpCodeProvider()).CompileAssemblyFromFile(parameters, input);

      if (results.Errors.Count > 0)
      {
        foreach (CompilerError error in results.Errors)
        {
          Console.WriteLine(error.ErrorText + " at line " + error.Line);
        }
      }
      else
      {
        Assembly a = Assembly.LoadFile(output);
        Type myType = a.GetType("Hoge");
        object hoge = Activator.CreateInstance(myType);
        Console.WriteLine("Created {0}!!", hoge);
        MethodInfo method = myType.GetMethod("Invoke");
        object[] fargs = { "HoiHoi" };
        Console.WriteLine(fargs[0]);
        method.Invoke(hoge, fargs);
        Console.WriteLine(fargs[0]);
      }
    }
  }
}

C:\Temp\hoge.cs は以下のような感じ

class Hoge
{
  public Hoge()
  {}
    
  public void Invoke(ref string importantData)
  {
    System.Console.WriteLine("I processed {0}", importantData);
    importantData = "HoeHoe";
  }
}

出力結果は以下のようになる。

Created Hoge!!
HoiHoi
I processed HoiHoi
HoeHoe

これだけだとありがたみがないけれど,自前のアプリケーションで HoiHoi だったデータが,動的に読み込んだプログラムファイル(スクリプトだと思いねぇ)を実行して HoeHoe に書き換えられていることに注目。たぶん CodeDom を色々いじると,グローバルオブジェクトの追加とか,もう少し fancy な書き方ができるようになるだろう。

今あるC++のアプリケーションをC++/CLIで.NET化して,この手法を用いてスクリプト的に拡張可能にする計画。お客さんの運用方法によって微調整が必要な処理の部分とか,よしんばバグフィックスとか,開発環境がないところでも,即座にパッチ書いて適用できたらかっこいい(かも)。