反射仅在.NET核心中加载组件

发布于 2025-02-12 03:45:38 字数 4055 浏览 0 评论 0原文

我有一个.NET Framework WPF应用程序,我目前迁移到.NET6。在启动时,它检查了可执行文件夹中的某些程序集,以查找具有自定义汇编属性的任何内容。然后将其加载到当前的AppDomain中。 (请注意,其中一些组件可能已经在AppDomain中,因为它们是运行应用程序解决方案中的项目)。

这是4.x代码:

private void LoadAssemblies(string folder)
{
    AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
        (s, e) => Assembly.ReflectionOnlyLoad(e.Name);

    var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
    foreach (var assemblyFile in assemblyFiles)
    {
        var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
        if (ContainsCustomAttr(reflectionOnlyAssembly))
        {
            var assembly = Assembly.LoadFrom(assemblyFile);
            ProcessAssembly(assembly);
        }
    }
}
  

自定义汇编属性(该代码正在寻找的)具有一个字符串属性,该属性包含该汇编内XAML资源文件的路径。 processAssembly()方法将此资源文件添加到应用程序合并的字典中,类似的内容:

var resourceUri = string.Format(
    "pack://application:,,,/{0};component/{1}",
    assembly.GetName().Name,
    mimicAssemblyAttribute.DataTemplatePath);

var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });

只是重申,所有这些都可以在.NET 4.X应用程序中使用。

.net6 另一方面不支持仅反射加载,也无法创建第二个应用程序域来加载组件。我通过将所检查的组件加载到我所理解的暂时,不载的上下文中来重写上述代码:(

    private void LoadAssemblies(string folder)
    {
        var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
        using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
        {
            foreach (var assemblyFile in assemblyFiles)
            {
                var assm = ctx.LoadFromAssemblyPath(assemblyFile);
                if (ContainsCustomAttr(assm))
                {
                    var assm2 = Assembly.LoadFrom(assemblyFile);
                    ProcessAssembly(assm2);
                }
            }
        }
    }

    private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
    {
        private AssemblyDependencyResolver _resolver;

        public TempAssemblyLoadContext(string readerLocation)
            : base(isCollectible: true)
        {
            _resolver = new AssemblyDependencyResolver(readerLocation);
        }

        public void Dispose()
        {
            Unload();
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            var path = _resolver.ResolveAssemblyToPath(assemblyName);
            if (path != null)
            {
                return LoadFromAssemblyPath(path);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (path != null)
            {
                return LoadUnmanagedDllFromPath(path);
            }

            return IntPtr.Zero;
        }
    }

请注意,ProcessAsseMbly()方法不变)。

该代码在不崩溃的情况下通过动作而“起作用”。但是,在应用程序开始创建视图的稍后时间,我会得到以下例外:

组件' .modeselectorView'没有URI'/ .; component/component/views/modeselector/modeselector/modeselectorview.xaml'...tin/ P>

此特定视图位于本应用程序解决方案的项目中,因此程序集将已经在AppDomain中。汇编还包含自定义属性,因此上面的代码将尝试加载它,尽管我相信assembly.loadfrom() not not 是否再次加载相同的程序集?

以防万一,我修改了我的loadAssemblies()方法中的“ if”块,以忽略App域中的组件:

if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))

果然,断点表明所讨论的汇编(包含该视图)被忽略,而不是 /em>加载到应用程序域中。但是,我仍然得到同样的例外。 实际上,我可以评论整个“如果”块,以便没有将组件加载到应用程序域中,并且我仍然会得到例外,这表明它是由将汇编加载到该汇编载荷context中引起的。 此外,一个断点表明,从LoadAssemblies()方法中掉出“使用”块后,通过其Dispose()方法将上下文通过其Dispose()方法卸载。

edit :即使在“如果”块上注释了“如果”块,该方法末尾的断点表明,ctx.loadfromassemblypath()加载的所有组件最终都在appdomain.current中。我不了解什么?是AppDomain的上下文部分而不是单独的“区域”?如何以与我在.NET 4.X中使用的“仅反射”方法相似的方式实现这种“隔离”装载量?

I have a .Net Framework WPF application that I'm currently migrating to .Net6. At startup it examines certain assemblies in the executable folder looking for any with a custom assembly attribute. Those that have this are then loaded into the current appdomain. (Note that some of these assemblies may already be in the appdomain, as they are projects in the running application's solution).

This is the 4.x code:

private void LoadAssemblies(string folder)
{
    AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
        (s, e) => Assembly.ReflectionOnlyLoad(e.Name);

    var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
    foreach (var assemblyFile in assemblyFiles)
    {
        var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
        if (ContainsCustomAttr(reflectionOnlyAssembly))
        {
            var assembly = Assembly.LoadFrom(assemblyFile);
            ProcessAssembly(assembly);
        }
    }
}
  

The custom assembly attribute (that this code is looking for) has a string property containing a path to a XAML resource file within that assembly. The ProcessAssembly() method adds this resource file to the application's merged dictionary, something like this:

var resourceUri = string.Format(
    "pack://application:,,,/{0};component/{1}",
    assembly.GetName().Name,
    mimicAssemblyAttribute.DataTemplatePath);

var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });

Just to reiterate, all this works as it should in the .Net 4.x application.

.Net6 on the other hand doesn't support reflection-only loading, nor can you create a second app domain in which to load the assemblies. I rewrote the above code by loading the assemblies being examined into what I understand is a temporary, unloadable context:

    private void LoadAssemblies(string folder)
    {
        var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
        using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
        {
            foreach (var assemblyFile in assemblyFiles)
            {
                var assm = ctx.LoadFromAssemblyPath(assemblyFile);
                if (ContainsCustomAttr(assm))
                {
                    var assm2 = Assembly.LoadFrom(assemblyFile);
                    ProcessAssembly(assm2);
                }
            }
        }
    }

    private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
    {
        private AssemblyDependencyResolver _resolver;

        public TempAssemblyLoadContext(string readerLocation)
            : base(isCollectible: true)
        {
            _resolver = new AssemblyDependencyResolver(readerLocation);
        }

        public void Dispose()
        {
            Unload();
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            var path = _resolver.ResolveAssemblyToPath(assemblyName);
            if (path != null)
            {
                return LoadFromAssemblyPath(path);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (path != null)
            {
                return LoadUnmanagedDllFromPath(path);
            }

            return IntPtr.Zero;
        }
    }

(Note the ProcessAssembly() method is unchanged).

This code "works" in so much as it goes through the motions without crashing. However at a later point when the application starts creating the views, I get the following exception:

The component '..ModeSelectorView' does not have a resource identified by the URI '/.;component/views/modeselector/modeselectorview.xaml'.

This particular view resides in a project of this application's solution, so the assembly will already be in the appdomain. The assembly also contains that custom attribute so the above code will be trying to load it, although I believe that Assembly.LoadFrom() should not load the same assembly again?

Just in case, I modified the "if" block in my LoadAssemblies() method to ignore assemblies already in the app domain:

if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))

Sure enough, a breakpoint shows that the assembly in question (containing that view) is ignored and not loaded into the app domain. However I still get the same exception further down the line.
In fact I can comment out the entire "if" block so no assemblies are being loaded into the app domain, and I still get the exception, suggesting that it's caused by loading the assembly into that AssemblyLoadContext.
Also, a breakpoint shows that context is being unloaded via its Dispose() method, upon dropping out of the "using" block in the LoadAssemblies() method.

Edit: even with the "if" block commented out, a breakpoint at the end of the method shows that all the assemblies being loaded by ctx.LoadFromAssemblyPath() are ending up in AppDomain.Current. What am I not understanding? Is the context part of the appdomain and not a separate "area"? How can I achieve this "isolated" loading of assemblies in a similar way to the "reflection only" approach that I was using in .Net 4.x?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

墨落成白 2025-02-19 03:45:38

好吧,我找到了答案,它可以使用 metadataloadcontext 。从本质上讲,这是用于仅反思加载的.NET核心替代物:

private void LoadAssemblies(string folder)
{
    // The load context needs access to the .Net "core" assemblies...
    var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
    // .. and the assemblies that I need to examine.
    var assembliesToExamine = Directory.GetFiles(folder, "*.Client.dll");
    allAssemblies.AddRange(assembliesToExamine);

    var resolver = new PathAssemblyResolver(allAssemblies);
    using (var mlc = new MetadataLoadContext(resolver))
    {
        foreach (var assemblyFile in assembliesToExamine)
        {
            var assm = mlc.LoadFromAssemblyPath(assemblyFile);
            if (ContainsCustomAttr(assm))
            {
                var assm2 = Assembly.LoadFrom(assemblyFile);
                AddMimicAssemblyInfo(assm2);
            }
        }
    }
}

Okay, so I found the answer, which is to use MetadataLoadContext. This is essentially the .Net Core replacement for reflection-only loading:

private void LoadAssemblies(string folder)
{
    // The load context needs access to the .Net "core" assemblies...
    var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
    // .. and the assemblies that I need to examine.
    var assembliesToExamine = Directory.GetFiles(folder, "*.Client.dll");
    allAssemblies.AddRange(assembliesToExamine);

    var resolver = new PathAssemblyResolver(allAssemblies);
    using (var mlc = new MetadataLoadContext(resolver))
    {
        foreach (var assemblyFile in assembliesToExamine)
        {
            var assm = mlc.LoadFromAssemblyPath(assemblyFile);
            if (ContainsCustomAttr(assm))
            {
                var assm2 = Assembly.LoadFrom(assemblyFile);
                AddMimicAssemblyInfo(assm2);
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文