2012-04-01 7 views
6

ここでは、クライアントサイトに侵入したかなり不快なピクルスです。クライアントには約100台のワークステーションがあり、そこで私たちは製品「MyApp」のバージョン1.0.0を導入しました。バージョン間のインターフェイスの変更 - 管理方法

ここで、製品が行うことの1つは、アドインをロードすることです(「MyPlugIn」と呼びます)。中央のサーバー上で新しいバージョンがあるかどうかを最初に探します。そのファイルをローカルにコピーした後、Assembly.Loadを使用してアドインを読み込み、特定の既知のインターフェイスを呼び出します。

その後、クライアントはいくつかの製品にv1.0.1をインストールしたかったのです。 (すべてではないが)MyPlugInの新しいバージョンが付属している

しかし、MyAppとMyPlugInの両方が参照する共有DLLがあり、MyDLLと呼ばれています。 MyClass.MyMethod。 v1.0.0とv1.0.1の間で、MyClass.MyMethodの署名が変更されました(パラメータが追加されました)。そして今MyPlugInの新バージョンがクラッシュへv1.0.0デベロッパークライアントアプリケーションが発生します。見つからない

方法:MyClass.MyMethodなど(可能System.String)

クライアントがあからさまにV1を展開する必要はありません.0.1をすべてのクライアントステーションで使用すると、v1.0.1に含まれていた修正がいくつかのワークステーションでのみ必要であり、すべてのクライアントにロールアウトする必要はありません。残念ながら、私たちはClickOnceやその他の大量配布ユーティリティを使用していませんので、v1.0.1を公開することは苦労し、そうでなければ不要な練習になります。

MyPluginにコードを書き込んで、MyDLL v1.0.0またはv1.0.1のどちらを扱っているかにかかわらず、同じように機能するようにする方法はありますか?おそらく、実際にそれを呼び出す前に、リフレクションを使って、それが存在するかどうかを確認するために、予想されるインターフェイスを探るいくつかの方法がありますか?

EDIT:私も言及すべきです - 私たちはかなり厳しいQA手順を持っています。 v1.0.1はQAによって公式にリリースされているため、MyAppまたはMyDLLを変更することはできません。唯一の移動の自由は、MyPluginを変更することです。MyPluginは、この顧客専用のカスタムコードです。

+1

古いプラグインのバージョンで期待される方法をMyDllに再追加しないのはなぜですか?内部的には、このメソッドは新しいメソッドのバージョンを呼び出して、新しいメソッドのparamのデフォルト値を渡すことができます。 – Steve

+0

@Steve - MyDLLを変更することはできません –

+0

MyClass.MyMethodは静的ですか? –

答えて

3

私は以前に書いたアプリケーションからこのコードを抽出し、いくつかの部分を削除しました。
多くのものが、ここで想定されています。MYDLL.DLLの

  1. 場所は、反射情報を取得するために、現在のディレクトリ
  2. 名前空間である「MyDll.MyClass」
  3. クラスはパラメータなしのコンストラクタを持っています。
  4. あなたは、私たちがここで何戻り値
using System.Reflection; 

private void CallPluginMethod(string param) 
{ 
    // Is MyDLL.Dll in current directory ??? 
    // Probably it's better to call Assembly.GetExecutingAssembly().Location but.... 
    string libToCheck = Path.Combine(Environment.CurrentDirectory, "MyDLL.dll"); 
    Assembly a = Assembly.LoadFile(libToCheck); 
    string typeAssembly = "MyDll.MyClass"; // Is this namespace correct ??? 
    Type c = a.GetType(typeAssembly); 

    // Get all method infos for public non static methods 
    MethodInfo[] miList = c.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly); 
    // Search the one required (could be optimized with Linq?) 
    foreach(MethodInfo mi in miList) 
    { 
     if(mi.Name == "MyMethod") 
     { 
      // Create a MyClass object supposing it has an empty constructor 
      ConstructorInfo clsConstructor = c.GetConstructor(Type.EmptyTypes); 
      object myClass = clsConstructor.Invoke(new object[]{}); 

      // check how many parameters are required 
      if(mi.GetParameters().Length == 1) 
       // call the new interface 
       mi.Invoke(myClass, new object[]{param}); 
      else 
       // call the old interface or give out an exception 
       mi.Invoke(myClass, null); 
      break; 
     } 
    } 
} 

を期待していない:動的

  1. ロードライブラリとMyClassの種類を抽出します。
  2. タイプを使用して、リフレクションサブシステムにそのタイプのMethodInfoのリストを尋ねます。
  3. すべてのメソッド名を調べて、必要なものを探します。
  4. このメソッドが見つかると、その型のインスタンスを作成します。
  5. メソッドによって予想されるパラメータの数を取得します。
  6. パラメータの数に応じて、Invokeを使用して正しいバージョンを呼び出します。
+0

ありがとう、私はここでやったのとまったく同じように、反射を使ったアプローチに従いました。 –

2

実際、リリース間の契約はで、に変更すると悪い考えです。オブジェクト指向の環境では、古い契約を継承する可能性がある新しい契約を作成する必要があります。

内部的には、エンジンに新しいインターフェイスを使用させ、古いオブジェクトを新しいインターフェイスに変換するアダプタを提供します。アセンブリをロードする際に

public class V1ToV2Adapter : MyServiceV2 { 
    public V1ToV2Adapter(MyServiceV1) { ... } 
} 

、あなたはそれをスキャンして、:

  • 新しいインタフェースを実装するクラスを見つけたときに、古いインターフェイスを実装するクラスを見つけたとき、あなたは、
  • 直接それを使用あなたはそれ以上のアダプタを使用します

ハッキングを使用すると(インタフェースのテストのように)、あなたや他の誰かが契約を使ってすぐに噛むでしょう - ハックの詳細が必要ですインターフェイスに頼っている人には、オブジェクト指向の視点からはひどいと思われることが知られています。

+0

合意しました。これは決して許されてはいけません。しかし、私たちは今や事実を追及しており、この状況をどのように修正するかを、MyPluginのコードを変更するだけの柔軟性で解決しなければなりません。 –

1

MyDLL 1.0.1では、古いバージョンMyClass.MyMethod(System.String)を非難し、新しいバージョンでそれをオーバーロードします。

+0

ああ、それはいい考えです... ** **私たちは本当に厳しいQA手続きをしていない場合。 QAは正式にv1.0.1をリリースしました。私は今変更することはできません...私が自由に変更できるのは、この特定の顧客向けに書かれたカスタムコードであるMyPlugInだけです。 –

+3

これはタイトなQAではなく、厳格なQAです。タイトなQAは、問題が発生する前に見つかっていました。最良の解決策は、これを正しく修正することです。そうでなければ、あなたに戻って来るでしょう。 –

1

MyMethod(文字列)(バージョン1.0.0互換)とMyMethod(文字列、文字列)(バージョン1.0.1バージョン)を受け付けるようにMyMethodをオーバーロードできますか?

+0

いいえ、私の編集を参照してください - 私はMyDLLを変更することはできません。 –

4

事は、あなたが持って行った変更は、基本的に変更ほかにはないことということです。したがって、あなたの展開で互換性を回復したい場合(これは唯一のオプションです)、はありません変更インターフェイスに新しいメソッドを追加して、しっかりとリンクしないようにしてくださいあなたのプラグインの共有DLLを使用して動的に読み込みます。この場合

  • では、あなたは、実行時にロードするためにDLLのバージョンを選択することができます古いもの

  • を乱すことなく新しいfuncionalityを追加します。状況を考えると

+0

あなたのポイントは将来的には十分に取られていますが、私たちは今や事実を追及しており、私たちが作った混乱に対処しなければなりません。 –

+0

@Shaul:プラグインのリファレンスを動的にロードするのは難しいですか? – Tigran

+0

私はあなたの質問を理解しているか分からないのですか? –

1

、私はあなたがすることができる唯一のことは、本当にMYDLLの2つのバージョン実行している「横並び」、
を持っており、それはTigranが提案するもののようなものを意味し、動的にMYDLLをロードしていると思います - 例えば関連するものではありませんが、RedemptionLoader http://www.dimastr.com/redemption/security.htm#redemptionloaderを見てください(これはOutlookプラグインのためのものです。バックグラウンドストーリーと同じように、ヘルパーDLLの異なるバージョンを参照する際に問題が発生することがよくあります。複雑なCOMの複雑な原因ですが、ここではあまり変更されません) -
これはあなたが何かできることです。その場所、名前でDLLを動的にロードする - 内部的にその場所を指定したり、ハードコードしたり、configなどから設定することもできます(または、MyDllが正しいバージョンでないことを確認してください)。
それから、オブジェクトを 'ラップ'して、普通のものに合わせて動的にロードされたdllを呼び出すか、そのようなやり方をします(実装上で何かを 'フォーク'する必要があります)ケース。
また、 'no-nos'とあなたのQAの悲しみを追加するには、
1.0.0から1.0.1への下位互換性を壊してはいけません。これらはマイナーな変更、修正、破損ではありません変更するには、メジャーバージョン#が必要です。

3

私のチームは、複数回同じことをしています。私たちには同様のプラグインアーキテクチャがあります。長期的に私があなたに与えることができる最良のアドバイスは、できるだけ早くこのアーキテクチャを変更することです。これは保守性の悪夢です。下位互換性マトリックスは、リリースごとに非線形に成長します。厳密なコードレビューはいくらか救済できますが、問題は適切な方法でメソッドを呼び出すためにメソッドが追加または変更された時期を常に知る必要があることです。開発者とレビュー担当者の両方が、メソッドが最後に変更された時期を正確に把握していない限り、メソッドが見つからないときにランタイム例外が発生するリスクがあります。メソッドで最新のMyDLLバージョンを持たない古いクライアントで実行できるため、プラグインのMyDLLで新しいメソッドを安全に呼び出すことはできません。 MyMethodはあなたが同様の非静的なラッパーを作ることができ、静的でない場合

static class MyClassWrapper 
{ 
    internal static void MyMethodWrapper(string name) 
    { 
     try 
     { 
     MyMethodWrapperImpl(name); 
     } 
     catch (MissingMethodException) 
     { 
     // do whatever you need to to make it work without the method. 
     // this may go as far as re-implementing my method. 
     } 
    } 

    private static void MyMethodWrapperImpl(string name) 
    { 
     MyClass.MyMethod(name); 
    } 

} 

時間であることについて、あなたはMyPluginでこのような何かを行うことができます。

長期的な変更については、あなたが最後にできることの1つは、プラグインインターフェイスを介して通信することです。リリース後にインターフェイスを変更することはできませんが、プラグインの新しいバージョンが使用する新しいインターフェイスを定義することはできます。また、MyPlugInからMyDLLの静的メソッドを呼び出すことはできません。サーバーレベルで変更することができれば(これがあなたのコントロール外にあるかもしれないことに気づく)、新しいプラグインが古いクライアントでは動作しないと宣言できるように、ある種のバージョン対応を提供することもできます。古いクライアントは古いバージョンをサーバーからダウンロードし、新しいクライアントは新しいバージョンをダウンロードします。

関連する問題