スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

バージョン違いは深刻です。

 ビルドされたアセンブリ(バイナリ)内のクラス及びメソッドを使うなら、普通にプロジェクトへ参照を追加して、名前空間をusingすれば良い。しかし、それではどうにもうまくいかない時がある。それは、利用したいアセンブリ(dll)が、こちらのプロジェクトと同じ3rdパーティ製のアセンブリを参照し、且つ異なるそのバージョンをつかている時。
 自分の仕事では、log4netのバージョンが異なってしまった。

自分のプロジェクトが使うlog4netのバージョン:1.2.12
別のプロジェクトが使うlog4netのバージョン:1.2.11

 しかも、Client Profileではない方を参照してやがる。こういう時、使いたいアセンブリをプロジェクトの参照に追加してしまったが最後、何をどうやってもビルドが通らなくなる。唯一の方法は、自分が使う(log4netの)アセンブリバージョンを下げることだ。下げられるならそれで良い。しかし、下げられない、いや下げたくない時はどうするのか?まあ、下げられたとしても、一度向こうのバージョンに合わせてしまうと、今後ずっとバージョンを合わせる必要がある。しかも、バージョンを上げたくても上げられない状況になる。向こうのバージョンアップに気づかないなんてのもザラにあるかもしれない。
 じゃあ、どうしよう?ってんで、参照に追加するのではなく、System.Reflection.Assemblyクラスを使ってロードしてみようと思った。
 結果は同じ。
using System.Reflection
...

//エラー! log4netのバージョンが違うぞ的な内容で。
Assembly.Load(@"c:\work\...\Acchi.dll");

やっていることが、参照するかしないかで、結局Loadする時にバージョンチェックは発生するらしい。
 色々調べた結果、アプリケーションドメインを新たに作ることで解決させた。
 アプリケーション ドメイン
 http://msdn.microsoft.com/ja-jp/library/2bh4z9hs(v=vs.110).aspx
詳しいことは、上記リンクに書いてある。大きいのは、同一プロセス内で、複数のアプリケーションを持てること。
 仕事では、別プロジェクトで使うアセンブリ一式を別アプリケーションと見立てて、別のアプリケーションドメインにロードさせ、メソッドを呼び出した(実行した)。インスタンスの生成、メソッドの呼び出しは、リフレクションを使う。

○dllをロードして、実行する為のクラス
using System;
using System.Reflection;

namespace TawamureDays {

/// <summary>
/// 別プロジェクトのアセンブリ用Proxyクラス
/// </summary>
[Serializable]
public class AcchiProjectProxy : MarshalByRefObject {

/// <summary>別プロジェクト用アセンブリ</summary>
private Assembly acchiAssembly_;

/// <summary>
/// コンストラクタ
/// </summary>
public AcchiProjectProxy() {
return;
}

/// <summary>
/// アセンブリをロードします。
/// </summary>
/// <param name="assemblyPath">アセンブリまでのパス</param>
public void LoadAssembly(string assemblyPath) {
//このdllが必要とする(依存する)アセンブリもロードされます。
this.acchiAssembly_ = System.Reflection.Assembly.Load(assemblyPath);
}

/// <summary>
/// メソッドを呼び出します。<br/>
/// 引数が必要な場合は、GetMethodで指定する必要があります。<br/>
/// オーバーロードされているメソッド情報を取得する時は、必須になります。<br/>
/// </summary>
public void CallMethod() {
//クラス名(FQN)から、アセンブリ内のクラスタイプを取得
var classType = this.acchiAssembly_.GetType("ClassFQN");
//クラスタイプからインスタンスを生成する。
var instance = System.Activator.CreateInstance(classType);
//メソッド情報を取得します。
var methodInfo = classType.GetMethod("MethodName");

//実行(引数無,戻り値無)
methodInfo.Invoke(instance);
return;
}
}
}


MarshalByRefObject
http://msdn.microsoft.com/ja-jp/library/system.marshalbyrefobject(v=vs.110).aspx
アプリケーションドメイン間の橋渡しをするので、MarshalByRefObjectクラスを継承しておかないと、エラーになる。

 Assembly.Loadを使うのは変わらないが、それを行うのは、別のアプリケーションドメイン内で実行する。
○アプリケーションドメインを作り、その中でProxyを生成する為のクラス
using System;
using System.Reflection;

namespace TawamureDays {

/// <summary>
/// AcchiProjectProxyクラスインスタンスを作る為のFactoryクラス
/// </summary>
public sealed class AcchiProjectProxyFactory : IDisposable {

#region コンストラクタ/デストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public AcchiProjectProxyFactory() {
//現在の アプリケーションドメインを生成します。
this.acchiNoDomain_ =
AppDomain.CreateDomain(
"AcchiProjDomain",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
return;
}

/// <summary>
/// デストラクタ
/// </summary>
~AcchiProjectProxyFactory() {
return;
}

#endregion

#region

/// <summary>
/// Proxyインスタンスを生成して取得します。<br/>
/// Proxyインスタンスは、当クラスが持つAppDomain内で生成されます。
/// </summary>
/// <returns>Proxyインスタンス</returns>
public AcchiProjectProxy GetProxy() {
var proxy = (AcchiProjectProxy)this.acchiNoDomain_.
CreateInstanceAndUnwrap(
typeof(AcchiProjectProxy).Assembly.FullName,
typeof(AcchiProjectProxy).FullName);
proxy.LoadAssembly(@"c:\work\...\acchi.dll");
return proxy;
}

#endregion

#region

/// <summary>
/// 内部リソースを解放(破棄)します。
/// </summary>
public void Dispose() {
this.OnDispose(true);
return;
}

/// <summary>
/// 内部リソースを解放(破棄)します。
/// </summary>
/// <param name="disposing">false:アンマネージドリソースのみ解放します。</param>
private void OnDispose(bool disposing) {

if (this.IsDisposed) {
return;
}

try {

if (disposing) {
//ドメインをアンロード(終了)させます。
if (this.acchiNoDomain_ != null) {
AppDomain.Unload(this.acchiNoDomain_);
this.acchiNoDomain_ = null;
}
}

} catch (Exception) {
;//

} finally {
GC.SuppressFinalize(this);
}

return;
}

/// <summary>
/// 内部リソースが破棄されたかどうかを取得します。
/// </summary>
public bool IsDisposed {
get; private set;
}

#endregion

#region

/// <summary>別プロジェクト用アプリケーションドメイン</summary>
private AppDomain acchiNoDomain_;

#endregion
}
}

○使い方
            using (var factory = new AcchiProjectProxyFactory()) {
var proxy = factory.GetProxy();

//メソッドを呼び出しましょう
proxy.CallMethod();
}

usingブロック内のみ、アプリケーションドメインがもう一つでき、その中にアセンブリがロードされる。

難点と言えば難点なのは、他プロジェクト内のクラスをProxyから直接取得できないこと。
Proxyクラスに

public object AcchiData {
get; set
}


と実装して、CallMethod内で

var classType = this.acchiAssembly_.GetType("AcchiDataClass");
this.AcchiData = var instance = System.Activator.CreateInstance(classType);

として、呼び出し側で

var data = proxy.GetacchiClass;

としても、この時点でエラーになる。そもそもアセンブリがロードされていないので、object型で受け取っても、認識できなければ、使用できない。
これは、Proxyと同じレベルで中継ぎ用のデータクラス(横文字で言えばDTO?)を作るしかない。
後はパフォーマンスかな。リフレクションだから遅いし。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。