C# でプラグインとやり取りしたいと思ってテストコードを作っていたのだが、思わぬ所で躓いた。

やりたいことはごく普通で、
  • プラグインのインターフェースを定義
  • インターフェースを実装したプラグイン DLL を作成
  • ホスト(EXE)側から、プラグイン DLL を動的に呼びだし
というもの。

プラグインのインターフェースは以下のようなシンプルなもの(PluginInterface.cs)。
namespace TestPlugin
{
    public interface ITestPlugin
    {
        Int32 Diff(Int32 oNum1, Int32 oNum2);
    }
}

このインターフェースを実装した TestDLL.dll を作成しておき、ホスト(EXE)側で以下のコードで呼びだした。
private void button12_Click(object sender, EventArgs e)
{
    ITestPlugin aPI = null;
    try
    {
        Assembly aAsm = Assembly.LoadFrom(@"C:\TestDLL.dll");
        foreach (Type aType in aAsm.GetTypes())
        {
            if (aType.IsInterface)
            {
                continue;
            }

            Object aObj = Activator.CreateInstance(aType);
            aPI = aObj as ITestPlugin;
            if (aPI != null)
            {
                MessageBox.Show("プラグインの計算結果:" + aPI.Diff(5, 3).ToString());
                break;
            }
        }
    }
    catch (Exception oExcep)
    {
        MessageBox.Show(oExcep.Message);
    }
    if (aPI == null)
    {
        MessageBox.Show("プラグインをロードできませんでした。");
    }
}

実行してみると、Activator.CreateInstance() でオブジェクトが生成され、aObj に値が入っているのが(デバッガで)確認できた。また、別途コードを追加して aType.GetInterfaces() でインターフェース名を取得すると、ITestPlugin を有していることも確認できた。

しかし!

次の行のキャストは失敗し、aPI は null のままであった。

いろいろ悩んだが、結果的には、「プラグインインターフェースを定義している PluginInterface.cs を、DLL とホストの双方で直接プロジェクトに追加していた」のが原因だった。

解決策としては、
  1. PluginInterface.cs を直接追加したプロジェクトで予め Interface.dll を作っておき、プラグイン DLL とホストの双方から、参照設定で Interface.dll を参照する。
  2. ホスト側のプロジェクトに PluginInterface.cs を直接追加してビルドし、プラグイン DLL ではホストのバイナリを参照設定で参照する。
のいずれかを行えば良い。解決策 1 だとバイナリファイルが 3 つになるので、ファイルの数で言えば、解決策 2 の方がすっきりする。

しかし、双方で直接プロジェクトに追加するとなぜうまく動かないのかは不明。GUID の問題なのかと思って ITestPlugin に Guid 属性を付けてみたりもしたが、改善しなかった。