1. ホーム
  2. Web プログラミング
  3. ASP プログラミング
  4. アプリケーションのヒント

Net coreのホットプラグ機構とアンインストールに関する問題点ヘルプガイド

2022-01-16 21:40:26

I. 依存関係ファイル *.deps.json を読み込む。

依存関係ファイルの内容は、以下の通りです。通常、コンパイルディレクトリにあります

{
 "runtimeTarget": {
 "name": "NETCoreApp,Version=v3.1",
 "signature": ""
 },
 "compilationOptions": {},
 "targets": {
 "NETCoreApp,Version=v3.1": {
 "PluginSample/1.0.0": {
 "dependencies": {
 "Microsoft.Extensions.Hosting.Abstractions": "5.0.0-rc.2.20475.5"
 },
 "runtime": {
 "PluginSample.dll": {}
 }
 },
 "Microsoft.Extensions.Configuration.Abstractions/5.0.0-rc.2.20475.5": {
 "dependencies": {
 "Microsoft.Extensions.Primitives": "5.0.0-rc.2.20475.5"
 },
 "runtime": {
 "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
 "assemblyVersion": "5.0.0.0",
 "fileVersion": "5.0.20.47505"
 }
 }
 ...

DependencyContextJsonReaderを使った依存関係設定ファイルの読み込み ソースコードビュー

using (var dependencyFileStream = File.OpenRead("Sample.deps.json"))
{
 using (DependencyContextJsonReader dependencyContextJsonReader = new DependencyContextJsonReader())
 {
 // get the corresponding entity file
 var dependencyContext = 
 Read(dependencyFileStream);
 //define the runtime environment, if not, then it is platform-wide.
 string currentRuntimeIdentifier= dependencyContext;
 //The dll file needed to run
 var assemblyNames= dependencyContext;
 RuntimeLibraries; }
}

ネットコアのマルチプラットフォームRID(RuntimeIdentifier)定義。

{NETCore. Microsoft.NETCore.Platforms パッケージをインストールし、runtime.json ランタイム定義ファイルを探します。

{
 "runtimes": {
 "win-arm64": {
 "#import": [
 "win"
 ]
 },
 "win-arm64-aot": {
 "#import": [
 "win-aot",
 "win-arm64"
 ]
 },
 "win-x64": {
 "#import": [
 "win"
 ]
 },
 "win-x64-aot": {
 "#import": [
 "win-aot",
 "win-x64"
 ]
 },
}

NET Core RIDの依存関係概略図

{{コード
win7-x64 win7-x86
 \ / |
 | win7 |
 |||
win-x64 | win-x86
 \ /
 win
 | win
 any

public class PluginLoadContext : AssemblyLoadContext
{
 private AssemblyDependencyResolver _resolver;
 public PluginLoadContext(string pluginFolder, params string[] commonAssemblyFolders) : base(isCollectible: true)
 {
 this.ResolvingUnmanagedDll += PluginLoadContext_ResolvingUnmanagedDll;
 this.Resolving += PluginLoadContext_Resolving;
 // Step 1, parse the des.json file and call the Load and LoadUnmanagedDll functions
 _resolver = new AssemblyDependencyResolver(pluginFolder);
 //step 6, through steps 4 and 5, the dll that still failed to resolve will automatically try to call the assembly in the main program,
 //If it fails, it will throw an error that the assembly cannot be loaded.
 }
 private Assembly PluginLoadContext_Resolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
 {
 // Step 4, the event executed after the Load function failed to load the assembly
 }
 private IntPtr PluginLoadContext_ResolvingUnmanagedDll(Assembly assembly, string unmanagedDllName)
 {
 //Step 5, the event executed when LoadUnmanagedDll fails to load the native dll
 }
 protected override Assembly Load(AssemblyName assemblyName)
 {
 //step 2, first execute the assembly load function
 }
 protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
 {
 //step 3, the native dll load logic is executed first
 }
}

{{コード

  • class PluginLoadContext : AssemblyLoadContext { private AssemblyDependencyResolver _resolver; public PluginLoadContext(string pluginPath) { _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly Load(AssemblyName assemblyName) { string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath ! = null) { //load the assembly return LoadFromAssemblyPath(assemblyPath); } //return null, then load the main project assembly directly return null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath ! = null) { //load native dll file return LoadUnmanagedDllFromPath(libraryPath); } //return IntPtr.Zero, i.e. null pointer. will load the dll in the runtimes folder of the main project return IntPtr.Zero; } }

{{コード

  • namespace PluginSample { public class SimpleService { public void Run(string name) { Console.WriteLine($"Hello World!"); } } }

{{コード

{{コード ランタイム.jsonを見る

namespace Test { public class PluginLoader { pubilc AssemblyLoadContext assemblyLoadContext; public Assembly assembly; public Type type; public MethodInfo method; public void Load() { assemblyLoadContext = new PluginLoadContext("Plugin folder"); assembly = alc.Load(new AssemblyName("PluginSample")); type = assembly.GetType("PluginSample.SimpleService"); method = type.GetMethod() } } }

{{コード

{{コード

{{コード

{{コード
public void Load(out WeakReference weakReference)
 {
 var assemblyLoadContext = new PluginLoadContext("plugin folder");
 weakReference = new WeakReference(pluginLoadContext, true);
 assemblyLoadContext.UnLoad();
 }
 public void Check()
 {
 WeakReference weakReference=null;
 Load(out weakReference);
 // Generally the second time, IsAlive will become False, that is, AssemblyLoadContext unload failed.
 for (int i = 0; weakReference.IsAlive && (i < 10); i++)
 {
 GC.Collect();
 GC.WaitForPendingFinalizers();
 }
 }

public class SimpleService { //Synchronous execution, plug-in uninstallation success public void Run(string name) { Console.WriteLine($"Hello {name}! "); } // Asynchronous execution, unload success public Task RunAsync(string name) { Console.WriteLine($"Hello {name}! "); CompletedTask; } // Asynchronous execution, unload successful public Task RunTask(string name) { return Task.Run(() => { Console.WriteLine($"Hello {name}! "); }); } // Asynchronous execution, unload successful public Task RunWaitTask(string name) { return Task.Run( async ()=> { while (true) { while (true) if (CancellationTokenSource.IsCancellationRequested) { break; } await Task.Delay(1000); Console.WriteLine($"Hello {name}! "); } }); } // Asynchronous execution, uninstallation success public Task RunWaitTaskForCancel(string name, CancellationToken cancellation) { return Task.Run(async () => { while (true) { if (cancellation.IsCancellationRequested) { break; } await Task.Delay(1000); Console.WriteLine($"Hello {name}! "); } }); } // Asynchronous execution, unload failed public async Task RunWait(string name) { while (true) { if (CancellationTokenSource.IsCancellationRequested) { break; } await Task.Delay(1000); Console.WriteLine($"Hello {name}! "); } } // Asynchronous execution, unload failed public Task RunWaitNewTask(string name) { Return Task.Factory.StartNew(async ()=> { while (true) { if (CancellationTokenSource.IsCancellationRequested) { break; } await Task.Delay(1000); Console.WriteLine($"Hello {name}! "); } },TaskCreationOptions.DenyChildAttach); } }

{{コード
linux-x64

linux-arm

1. net core runtime.json file provided by Microsoft: View runtime.json .

{コード

3. 手動でロードする場合、deps.json ファイルで定義されたランタイムに従って、現在のプラットフォームのアンマネージド dll ファイルをロードすることができます。

これらのプラットフォームに関連するDLLファイルは、通常、配布ディレクトリのruntimesフォルダに配置されています。

IV. プラグインプロジェクトは、メインプロジェクトと同じランタイムを使用する必要があります。

  1. メインプロジェクトが.net core 3.1の場合、プラグインプロジェクトは.net core 2.0などを選択できず、.net標準ライブラリも選択できない
  2. そうでなければ、予測できない問題が発生します。
  3. net標準では、プロジェクトファイルの修正が必要です。 netstandard;netcoreapp3.1 .
  4. これは、.net core プロジェクトとして公開されます。
  5. メインプロジェクトのnugetパッケージが現在のプラットフォームに適していない場合、例外 Not Support Platform がスローされます。この場合、メインプロジェクトがwindowsであれば、プロジェクトのリリースターゲットをwin-x64に設定する必要があります。

AssemblyLoadContext.UnLoad()で例外がスローされない。

アセンブリが解放されたと思ってプラグインをアンインストールするためにAssemblyLoadContext.UnLoad()を呼ぶと、間違っている場合があります。公式ドキュメントによると、アンインストールに失敗するとInvalidOperationExceptionがスローされ、アンインストールができなくなるようです。 公式の説明書 .
しかし、実際のテストでは、アンインストールは失敗しましたが、エラーは報告されませんでした。

VI. リフレクションアセンブリに関連する変数の定義が、プラグインアセンブリのアンインストールを妨げているのはなぜですか?

プラグイン

2. runtime.json, under the runeims node, defines all the RID dictionary tables and RID tree relationships.

プラグインを読み込む

3. The RID identifier of the assembly definition in the *.deps.json dependency file is used to determine whether the dll pointed to in the dependency file can run on a particular platform.

AssemblyLoadContext、Assembly、Type、MethodInfoなどは、メインプロジェクトアプリケーション内のどのクラスにも直接定義することはできません。
そうでない場合は、プラグインのアンインストールに失敗します。このとき、アンインストールが成功するかどうかをテストするために、手動でロード、実行、アンインストールを1000回繰り返します。 {1000回アンインストールを行いましたが、アンインストールに成功しませんでした。
メモリが増え続けるとアンインストールに失敗します。


3. WeakReference を使用して AssemblyLoadContext を関連付け、アンインストールが成功したかどうかを判断します。

4. When the application is released in compatibility mode, we can use the runtime.json file to selectively load the platform dll and run it.

4. 上記の問題を解決するために 必要な変数をスタティックディクショナリに入れればよい。Unloadの前に対応するKey値を削除する。

VII. アセンブリ内の非同期関数の実行により、プラグインのアンロードが妨げられるのはなぜですか?

AssemblyLoadContext loading principle

public class PluginLoadContext : AssemblyLoadContext
{
 private AssemblyDependencyResolver _resolver;
 public PluginLoadContext(string pluginFolder, params string[] commonAssemblyFolders) : base(isCollectible: true)
 {
 this.ResolvingUnmanagedDll += PluginLoadContext_ResolvingUnmanagedDll;
 this.Resolving += PluginLoadContext_Resolving;
 // Step 1, parse the des.json file and call the Load and LoadUnmanagedDll functions
 _resolver = new AssemblyDependencyResolver(pluginFolder);
 //step 6, through steps 4 and 5, the dll that still failed to resolve will automatically try to call the assembly in the main program,
 //If it fails, it will throw an error that the assembly cannot be loaded.
 }
 private Assembly PluginLoadContext_Resolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
 {
 // Step 4, the event executed after the Load function failed to load the assembly
 }
 private IntPtr PluginLoadContext_ResolvingUnmanagedDll(Assembly assembly, string unmanagedDllName)
 {
 //Step 5, the event executed when LoadUnmanagedDll fails to load the native dll
 }
 protected override Assembly Load(AssemblyName assemblyName)
 {
 //step 2, first execute the assembly load function
 }
 protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
 {
 //step 3, the native dll load logic is executed first
 }
}
The official Microsoft sample code is as follows:
Example specifics

{{コード

  • class PluginLoadContext : AssemblyLoadContext { private AssemblyDependencyResolver _resolver; public PluginLoadContext(string pluginPath) { _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly Load(AssemblyName assemblyName) { string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath ! = null) { //load the assembly return LoadFromAssemblyPath(assemblyPath); } //return null, then load the main project assembly directly return null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath ! = null) { //load native dll file return LoadUnmanagedDllFromPath(libraryPath); } //returns the IntPtr.Zero, i.e. null pointer. will load the dll in the runtimes folder of the main project return IntPtr.Zero; } }
  • {{コード
  • {{コード
  • {{コード
  • {{コード

{{コード https://git.oschina.net/LucasDot/NFinal2/tree

{{コード