私が開発しているプラグイン (PLUGIN と呼びましょう) は、それが作成されたメイン ソフトウェア (PARENT と呼びましょう) から 2 つのアセンブリ ファイルを使用しています。PARENT が新しいバージョンに更新されるとすぐに (そして、週に数回発生します)、再コンパイルを強制することなく、PLUGIN が依存関係の新しいバージョンを動的にロードするようにします。
PARENT はプラグインをソース コード ファイルとしてロードし、ジャスト イン タイムでコンパイルします。コードを DLL に入れたいので、Loader.cs ファイルはリフレクションを介して DLL から関数を呼び出します。
以下は、Loader.cs のコードです。
// error handling removed for better readability
public Loader()
{
assembly = Assembly.LoadFile(dllPath);
type = assembly.GetType("PLUGIN.PLUGINStarter");
instance = Activator.CreateInstance(type);
}
public override void Dispose()
{
type.InvokeMember("Dispose", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
base.Dispose();
}
public override void OnButtonPress()
{
type.InvokeMember("ShowForm", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
}
PLUGIN 名前空間の PLUGINStarter クラスは次のようになります。
class PLUGINStarter
{
private PLUGIN plugin = null;
/// <summary>
/// This is loading PARENT.exe and PARENTSomeOtherFile.dll dependencies.
/// </summary>
public PLUGINStarter()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
{
var fullAssemblyName = new AssemblyName(eventArgs.Name);
// this is not executed when PARENT version changes
MessageBox.Show(fullAssemblyName.Name, "Loading assembly...");
if (fullAssemblyName.Name.Equals("PARENT"))
{
// AppDomain.CurrentDomain.FriendlyName will handle the case where PARENT.exe is re-named
var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, AppDomain.CurrentDomain.FriendlyName));
return found;
}
else if (fullAssemblyName.Name.Equals("PARENTSomeOtherFile"))
{
var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, "PARENTSomeOtherFile.dll"));
return found;
}
else
{
return null;
}
};
Initialize();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Initialize()
{
// the PARENT's assemblies are referenced in the PLUGIN class
plugin = new PLUGIN();
}
public void ShowForm()
{
plugin.ShowForm();
}
public void Dispose()
{
plugin.Dispose();
}
}
PARENT が新しいバージョンに更新されると、イベントは発生しません。なんで?
編集#1
明確にするために:PARENTは、PARENTからのアセンブリ(および主にPARENT.exe自体)に依存するPLUGIN.dllをロードするLoader.csをロードします(ジャストインタイムでコンパイルします)。
編集#2
PARENT ソフトウェアは手動で更新されます。ユーザーは、インターネット (製品の Web サイト) からダウンロードします。次に、ユーザーは PLUGIN.dll を PARENT の「Plugins」ディレクトリにコピーします。
次に、Loader.cs で次の例外をキャッチできます。
[07:55:19.822 D] [PLUGIN] Loading DLL 'C:\PNT\PARENTNew\Plugins\PLUGIN\PLUGIN.dll'.
[07:55:19.826 D] [PLUGIN] Exception loading assembly 'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileLoadException: Could not load file or assembly 'PARENT, Version=6.2.8113.191, Culture=neutral, PublicKeyToken=21a554ab5c01ae50' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
at PLUGIN.PLUGINStarter..ctor()
--- End of inner exception stack trace ---
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at PLUGINLoader.Loader..ctor() in c:\PNT\PARENTNew\Plugins\PLUGIN\Loader.cs:line 42'.
編集#3
Is it possible to replace a reference to a strong named assembly with a "weak" reference? で説明されているように、これは可能だと思いますか? しかし、何らかの奇妙な理由で、私のアプリケーションでは失敗します。
編集#4
PLUGINStarter を削除し、アセンブリ解決コードを Loader.cs のコンストラクターに移動することで問題を解決しました。アセンブリのバージョンが間違っていても、すべてがうまく解決するようになりました。