2019年2月17日日曜日

コンソールアプリケーションの呼び出し(Reflection)

コンソールアプリケーションをリフレクションで呼び出す場合、どう書けばいいでしょうか?

コンソールアプリケーションはこんな感じですね


namespace SimpleApp01
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}


クラスが非公開で、メイン関数が引数ありのstaticメソッドになっています。
つい、こんな風に書きたくなりませんか?


var asm = Assembly.Load("SimpleApp01");
var t = asm.GetType("SimpleApp01.Program");

var method = t.GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic);

// コンソールからの引数を設定
string[] args = new string[] { "aaa", "bbb" };
method.Invoke(null, args);

コンソールアプリケーションの引数は、string[] args なので、それを引数に渡したら動くような気がしちゃいます。
でも、実際にはこれではmethod.invokeでエラーとなり、
System.Reflection.TargetParameterCountException: 'パラメーター カウントが一致しません。'(parameter count mismatch)
が発生します。

Invokeの第2引数には、実際のメソッドの引数を指定する必要があります。
上記のargsを指定するということは

static void Main (string paramA, string paramB)

のようなメソッドの呼び出しをすることになってしまいます。
なので、実際は以下のようになります。

var asm = Assembly.Load("SimpleApp01");
var t = asm.GetType("SimpleApp01.Program");

var method = t.GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic);

// コンソールからの引数を設定
string[] args = new string[] { "aaa", "bbb" };
var p = new object[] { args };
method.Invoke(null, p);


VisualStudioの単体テストプロジェクトの場合PrivateTypeを使用することもできます。
その場合も同様にこんな感じになります。

var asm = Assembly.Load("SimpleApp01");
var t = asm.GetType("SimpleApp01.Program");

// PrivateTypeによる呼び出し
string[] args = new string[] { "aaa", "bbb" };
var p = new object[] { args };
PrivateType pt = new PrivateType(t);
pt.InvokeStatic("Main", p);