.NETにおける動的アセンブリ読み込みとアンロードの実装手法

.NETではAssemblyLoadContextを拡張することで、実行時にアセンブリを動的に読み込み・解放可能なプラグイン機構を構築できる。以下はその実装例である。

public class DynamicPluginContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _dependencyResolver;

    public DynamicPluginContext(string entryAssemblyPath) : base(isCollectible: true)
    {
        _dependencyResolver = new AssemblyDependencyResolver(entryAssemblyPath);
    }

    protected override Assembly? Load(AssemblyName requestedAssembly)
    {
        var resolvedPath = _dependencyResolver.ResolveAssemblyToPath(requestedAssembly);
        return resolvedPath != null ? LoadFromAssemblyPath(resolvedPath) : null;
    }

    protected override IntPtr LoadUnmanagedDll(string nativeLibraryName)
    {
        var nativePath = _dependencyResolver.ResolveUnmanagedDllToPath(nativeLibraryName);
        return nativePath != null ? LoadUnmanagedDllFromPath(nativePath) : IntPtr.Zero;
    }
}
class PluginManager
{
    private static AssemblyLoadContext? _currentContext;

    static void LoadPlugin(string pluginPath)
    {
        UnloadCurrent();

        var context = new DynamicPluginContext(pluginPath);
        context.LoadFromAssemblyPath(pluginPath);
        _currentContext = context;
    }

    static void UnloadCurrent()
    {
        _currentContext?.Unload();
    }

    static WeakReference CreatePluginInstance()
    {
        var targetType = _currentContext?.Assemblies
            .Select(asm => asm.GetType("Plugin.Entity"))
            .FirstOrDefault(t => t != null);

        var instance = Activator.CreateInstance(targetType!);
        return new WeakReference(instance);
    }

    static void Main()
    {
        LoadPlugin(@"path\to\plugin1.dll");
        var entity1 = CreatePluginInstance();

        LoadPlugin(@"path\to\plugin2.dll");
        var entity2 = CreatePluginInstance();

        Console.WriteLine(entity2.Target?.ToString());

        for (int attempt = 0; attempt < 10 && entity1.IsAlive; attempt++)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.WriteLine($"GC Round {attempt + 1}: Alive = {entity1.IsAlive}");
        }
    }
}

この仕組みにより、ゲームのホットアップデートや拡張性を重視するアプリケーションにおいて、再起動なしにモジュールを更新・交換することが可能となる。

タグ: AssemblyLoadContext .NET プラグインシステム 動的ロード メモリ管理

7月30日 07:41 投稿