Roslyn(三)运行脚本并引用DLL在不同上下文(AssemblyLoadContext - ALC)中的访问问题

DLL在Roslyn中的使用

在上一篇Roslyn的文章中 - Roslyn动态编译DLL和缺少Private.CoreLib的问题 我们成功的动态编译了DLL文件,那么我们今天来继续研究在Roslyn中使用。

本文研究的是如何从开始简单的调用DLL,到最后在不同的ALC中加载运行。

简单调用DLL

加载DLL,为了后面的卸载功能。这里统一使用AssemblyLoadContext,后面统一叫ALC。更简单的可以直接使用Assembly.Load,不是本文重点,请自行研究。

准备工作

我们先复习下怎么制作DLL,见 Roslyn动态编译DLL和缺少Private.CoreLib的问题 ,我们编译这个code0.cs文件,距离步骤这里不写了。

using System;
namespace RoslynCompileSample
{
    public class Funs
    {
        public static string GetName()
        {
            return ""Get Funs.Name ()"";
        }
    }
}

放到c:\Temp\code0.dll。

首先加载

我们把dll加载到一个名为mydll的ALC中

    string path = @"c:\Temp\code0.dll";
    string contextname = "mydll";
    
    AssemblyLoadContext alc_dll = new AssemblyLoadContext(contextname, true);
    Assembly ass = alc_dll.LoadFromAssemblyPath(path);
    

创建动态脚本调用

全部脚本:

public void LoadDll()
        {
            //读取程序集MyLib.dll
            string mydllPath = @"c:\Temp\code0.dll";
            
            string contextname = "code0";

            AssemblyLoadContext alc_dll = new AssemblyLoadContext(contextname, true);
            Assembly ass = alc_dll.LoadFromAssemblyPath(mydllPath);

            //创建动态脚本调用
            string sourceCode = File.ReadAllText(@"c:\Temp\my.cs");
            SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(SourceText.From(sourceCode));

            List < MetadataReference > list = new List<MetadataReference>();
            
            string dllpath = @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.9\ref\net7.0\";

            list.Add(MetadataReference.CreateFromFile(dllpath + "mscorlib.dll"));
            list.Add(MetadataReference.CreateFromFile(dllpath + "System.Runtime.dll"));
            list.Add(MetadataReference.CreateFromFile(dllpath + "System.Console.dll"));

            //添加我的mylib.dll的引用
            list.Add(MetadataReference.CreateFromFile(mydllPath));

            // 创建编译对象
            CSharpCompilation compilation = CSharpCompilation.Create(
                "my",
                new[] { syntaxTree },
                list,
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                )
                ;
            using (var ms = new MemoryStream())
            {
                // 将编译好的IL代码放入内存流
                EmitResult result = compilation.Emit(ms);

                // 编译失败,提示
                if (!result.Success)
                {
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                                diagnostic.IsWarningAsError ||
                                diagnostic.Severity == DiagnosticSeverity.Error);
                    foreach (Diagnostic diagnostic in failures)
                    {
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }
                }
                else
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    // 编译成功,从内存中加载编译好的程序集

                    Assembly assembly = alc_dll.LoadFromStream(ms);
                    if (assembly != null)
                    {
                        Type type = null;
                        object obj = null;
                        type = assembly.GetType("RoslynCompileSample.Writer");
                        obj = Activator.CreateInstance(type);

                        // 通过反射方式调用类中的方法。(Hello World 便是我们传入方法的参数)
                        Console.Write("InvokeMember ...\n");
                        type.InvokeMember("Write",
                            BindingFlags.Default | BindingFlags.InvokeMethod,
                            null,
                            obj,
                            null);
                    }
                }
            }
        }

my.cs的内容为

using System;
            
            namespace RoslynCompileSample
            {
                public class Writer
                {
                    public void Write()
                    {
                        Console.WriteLine(""---"");
                        Console.WriteLine(MyLib.GetGreeting());
                    }
                }
            }

运行后直接得到结果。

Roslyn(三)运行脚本并引用DLL在不同上下文(AssemblyLoadContext - ALC)中的访问问题_第1张图片
这里就简单的运行了,这里注意几个点,我们的dll和mycs在同一个ALC中,如果要卸载会导致需要两个都要卸载。下一步我们把他们分别放在不同的ALC中。

不同的ALC的问题

我们把最后一段代码改为

AssemblyLoadContext mycs = new AssemblyLoadContext("mycs", true);
Assembly assembly = mycs.LoadFromStream(ms);
if (assembly != null)
{
    Type type = null;
    object obj = null;
    type = assembly.GetType("RoslynCompileSample.Writer");
    obj = Activator.CreateInstance(type);

    // 通过反射方式调用类中的方法。(Hello World 便是我们传入方法的参数)
    Console.Write("InvokeMember ...\n");
    type.InvokeMember("Write",
        BindingFlags.Default | BindingFlags.InvokeMethod,
        null,
        obj,
        null);
}

这里我们创建了一个新的名为mycs的ALC。我们运行一下看看
Roslyn(三)运行脚本并引用DLL在不同上下文(AssemblyLoadContext - ALC)中的访问问题_第2张图片

这里直接抛出了错误。无法找到code0,因为我们的两个执行文件在不同的ALC,这里他只会先找同一个ALC,如果没有会在ALC.Default中查找。那么怎么办呢?

如果把这两行代码换成了Default就可以运行了。

//AssemblyLoadContext alc_dll = new AssemblyLoadContext(contextname, true);
//Assembly ass = alc_dll.LoadFromAssemblyPath(mydllPath);
Assembly ass = AssemblyLoadContext.Default.LoadFromAssemblyPath(mydllPath);

但是这个Default是无法卸载的。

解决办法

我们可以利用ALC的Resolving来处理。我们只需要增加下面的代码,让他返回正确的Assembly就可以了。
Resolving : 当尝试加载到此程序集加载上下文时程序集解析失败时发生。

AssemblyLoadContext mycs = new AssemblyLoadContext("mycs", true);
mycs.Resolving += (loadContext, assemblyName) =>
{
	Console.WriteLine("--Resolving---" + loadContext.Name + "," + assemblyName);
	return ass;
};

运行后,获取到了。
Roslyn(三)运行脚本并引用DLL在不同上下文(AssemblyLoadContext - ALC)中的访问问题_第3张图片
这样就可以实现不同的ALC中的上下文问题。

因为学习中,可能存在错误,还请留言。

引用和参考

https://zhuanlan.zhihu.com/p/636044493

https://learn.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability

你可能感兴趣的:(Roslyn,C#,roslyn,c#)