none
delegateによる動的アセンブリライブラリ(プラグイン)の実装について RRS feed

  • 質問

  • アプリケーションにいわゆるプラグインの機能を追加する方法としては、元となるアセンブリから抜き出したインターフェイスを実装したライブラリファイルを、Assembly.LoadFromで必要に応じて取得するものがあります(参考:http://dobon.net/vb/dotnet/programing/plugin.html)。しかし、この方法では、将来プラグインの仕様が拡張され、インターフェイスに変更が加えられたのであれば、既存のプラグインファイルのアセンブリ参照ができない可能性があります。

     インターネットでいろいろと調べてみたところ、拡張性に柔軟に対応するプラグイン開発をするにはデリゲートを用いればよいとの記述を見かけました。おそらくC言語のLoadLibraryとgetprocaddressに該当すると思うのですが、C#の場合はどのように実装すればよいのでしょうか。
    2008年11月14日 8:06

回答

  •  Tank2005 さんからの引用

    しかし、この方法では、将来プラグインの仕様が拡張され、インターフェイスに変更が加えられたのであれば、既存のプラグインファイルのアセンブリ参照ができない可能性があります。

    COMでもそうですが、既存のインターフェースは変更しない・削除しない・メソッド/プロパティを追加しないべきであり、新しいメソッドやプロパティを追加したい・メソッドの引数を変えたいと思ったときは新しいインターフェースを定義するべきです。

    古いバージョンのプラグインは古いインターフェースでアクセスし、新しいバージョンのプラグインでは新しいインターフェースでアクセスするようにすれば、バージョン間の問題は押さえられそうな気がします。

     

     Tank2005 さんからの引用

     インターネットでいろいろと調べてみたところ、拡張性に柔軟に対応するプラグイン開発をするにはデリゲートを用いればよいとの記述を見かけました。

    おそらくC言語のLoadLibraryとgetprocaddressに該当すると思うのですが、C#の場合はどのように実装すればよいのでしょうか。

    本当にdelegateが柔軟なのかは何とも言えません。

    GetProcAddressで取得できるような関数のエクスポートはできませんので、リフレクションを使って、特定の型を取り出してInvokeMemberとかすることになるのではないでしょうか。

     

    参考までに柔軟に対応できると説明しているページを教えて頂ければ幸いです。

    2008年11月14日 16:45
    モデレータ
  • どっちもどっちだと思いますけどねえ。
    ある程度の拡張のシナリオを考えて、両方の方式でどのように解決していくかを実際にコーディングしてみるとよいでしょうね。
     
    interface を利用すると、コーディング上で引数などの契約の確認が、通常のコード記述だけで簡単にできるのがよいところですね。
    GetProcAddress が受け取ったポインタを適切な型に変換されることを前提としているのと同様に、受け取った delegate を適切な型で運用しなければならないというのは大きな負担になります。たとえば、
     
    Code Snippet
    public interface IPlugYou
    {
      Dictionary Delegates { get; }
    }

     

    みたいな interface を1個用意しておいて、汎用的な delegate の取得ができるメソッドを公開しても、拡張したいときは勝手に要素を増やしてやればどうにでも拡張はできるでしょうけど、これが期待したように動き続けるように維持するには、使う側も使われる側も大変です。
     
    Code Snippet
    ((Action<bool>) (plug.Delegates["ConfigureDialog_NewRecord_set"])) (true);
    return ((Predicate<IWin32Window>) (plug.Delegates["ConfigureDialog_ShowModal#withOwner"])) (this)

     

     

    Code Snippet
    plug.ConfigureDialog.NewRecord = true;
    return plug.ConfigureDialog.ShowModal(this);

     

     

    やってることはいっしょですけどね。
     
    2008年11月14日 18:05
  • 多くの人に配布する場合、その型を含むアセンブリをどのように配置するのか、バージョンはどうするか、厳密名でやるのかと悩まされるかもしれません。

    同じ名前のDLL、同じ名前の型として、バージョンが異なる場合は別の型とみなされることもあり得ます。

     

    どのようなシナリオがあり得るか、ある程度検討した上で、最良の方法を選ぶことにはなると思います。

     

     Tank2005 さんからの引用

    ところで、インターフェイスによる実装では、仮にプラグイン仕様に拡張があった場合、

    public interface IPlugin{}

    public interface IPlugin_v2 : IPlugin{}

    public interface IPlugin_v3 : IPlugin_v2{}

    のように継承する形で行う方法で問題ないでしょうか。

    問題がないとは言い切れません。

    アセンブリのバージョンをどうしていくか等によると思います。

    2008年11月17日 14:57
    モデレータ

すべての返信

  •  Tank2005 さんからの引用

    しかし、この方法では、将来プラグインの仕様が拡張され、インターフェイスに変更が加えられたのであれば、既存のプラグインファイルのアセンブリ参照ができない可能性があります。

    COMでもそうですが、既存のインターフェースは変更しない・削除しない・メソッド/プロパティを追加しないべきであり、新しいメソッドやプロパティを追加したい・メソッドの引数を変えたいと思ったときは新しいインターフェースを定義するべきです。

    古いバージョンのプラグインは古いインターフェースでアクセスし、新しいバージョンのプラグインでは新しいインターフェースでアクセスするようにすれば、バージョン間の問題は押さえられそうな気がします。

     

     Tank2005 さんからの引用

     インターネットでいろいろと調べてみたところ、拡張性に柔軟に対応するプラグイン開発をするにはデリゲートを用いればよいとの記述を見かけました。

    おそらくC言語のLoadLibraryとgetprocaddressに該当すると思うのですが、C#の場合はどのように実装すればよいのでしょうか。

    本当にdelegateが柔軟なのかは何とも言えません。

    GetProcAddressで取得できるような関数のエクスポートはできませんので、リフレクションを使って、特定の型を取り出してInvokeMemberとかすることになるのではないでしょうか。

     

    参考までに柔軟に対応できると説明しているページを教えて頂ければ幸いです。

    2008年11月14日 16:45
    モデレータ
  • どっちもどっちだと思いますけどねえ。
    ある程度の拡張のシナリオを考えて、両方の方式でどのように解決していくかを実際にコーディングしてみるとよいでしょうね。
     
    interface を利用すると、コーディング上で引数などの契約の確認が、通常のコード記述だけで簡単にできるのがよいところですね。
    GetProcAddress が受け取ったポインタを適切な型に変換されることを前提としているのと同様に、受け取った delegate を適切な型で運用しなければならないというのは大きな負担になります。たとえば、
     
    Code Snippet
    public interface IPlugYou
    {
      Dictionary Delegates { get; }
    }

     

    みたいな interface を1個用意しておいて、汎用的な delegate の取得ができるメソッドを公開しても、拡張したいときは勝手に要素を増やしてやればどうにでも拡張はできるでしょうけど、これが期待したように動き続けるように維持するには、使う側も使われる側も大変です。
     
    Code Snippet
    ((Action<bool>) (plug.Delegates["ConfigureDialog_NewRecord_set"])) (true);
    return ((Predicate<IWin32Window>) (plug.Delegates["ConfigureDialog_ShowModal#withOwner"])) (this)

     

     

    Code Snippet
    plug.ConfigureDialog.NewRecord = true;
    return plug.ConfigureDialog.ShowModal(this);

     

     

    やってることはいっしょですけどね。
     
    2008年11月14日 18:05
  • デリゲートによるプラグインの実装について言及していたのは個人のブログページです(http://life-hack.jp/blog/charly/97)。

     

    ところで、インターフェイスによる実装では、仮にプラグイン仕様に拡張があった場合、

    public interface IPlugin{}

    public interface IPlugin_v2 : IPlugin{}

    public interface IPlugin_v3 : IPlugin_v2{}

    のように継承する形で行う方法で問題ないでしょうか。

    2008年11月17日 1:50
  • 多くの人に配布する場合、その型を含むアセンブリをどのように配置するのか、バージョンはどうするか、厳密名でやるのかと悩まされるかもしれません。

    同じ名前のDLL、同じ名前の型として、バージョンが異なる場合は別の型とみなされることもあり得ます。

     

    どのようなシナリオがあり得るか、ある程度検討した上で、最良の方法を選ぶことにはなると思います。

     

     Tank2005 さんからの引用

    ところで、インターフェイスによる実装では、仮にプラグイン仕様に拡張があった場合、

    public interface IPlugin{}

    public interface IPlugin_v2 : IPlugin{}

    public interface IPlugin_v3 : IPlugin_v2{}

    のように継承する形で行う方法で問題ないでしょうか。

    問題がないとは言い切れません。

    アセンブリのバージョンをどうしていくか等によると思います。

    2008年11月17日 14:57
    モデレータ
  • こんにちは。中川俊輔 です。

     

    Azuleanさん、K.Takaokaさん、回答ありがとうございます。

     

    Tank2005さん、フォーラムのご利用ありがとうございます。

    有用な情報と思われたため、Azuleanさん、K.Takaokaさんの回答へ回答済みチェックをつけさせていただきました。

     

    今後ともフォーラムをよろしくお願いします。

    それでは!

    2008年11月28日 5:26