TL;DR
如何让运行时在.NET Core 5中为在运行时编译的涉及.NET 4.7.2代码的C#插件选择正确的程序集?
上下文
我有一个.NET 4.7.2应用程序,基于一些可配置的插件,某些模块在该应用程序上的行为有所不同。我在.NET 4.7.2程序集中有以下代码,该程序集在运行时编译C#插件。
public OperationResult<Assembly> CompileClass(string code, string[] references, string fileName, bool generateInMemory = true, bool includeDebugInformation = true)
{
OperationResult<Assembly> result = new OperationResult<Assembly> { Success = true };
try
{
string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
bool keepSoureceFilesAfterCompiling = false;
#if (DEBUG)
keepSoureceFilesAfterCompiling = true;
#endif
if (!Directory.Exists(pluginsFolder))
{
Directory.CreateDirectory(pluginsFolder);
}
using (CSharpCodeProvider compiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } }))
{
CompilerParameters parameters = new CompilerParameters()
{
GenerateInMemory = generateInMemory,
IncludeDebugInformation = includeDebugInformation,
OutputAssembly = Path.Combine(pluginsFolder, fileName) + ".dll",
CompilerOptions = "/debug:full",
TempFiles = new TempFileCollection { KeepFiles = keepSoureceFilesAfterCompiling }
};
parameters.ReferencedAssemblies.AddRange(references);
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);
var errors = new StringBuilder();
foreach (CompilerError error in compiledCode.Errors)
{
errors.AppendLine($"Error in line {error.Line}, Column {error.Column}: {error.ErrorText}");
}
if (!string.IsNullOrEmpty(errors.ToString()))
{
result.HandleFailure(errors.ToString());
}
result.ResultObject = compiledCode.CompiledAssembly;
}
}
catch (Exception ex)
{
LogService.Current.LogError(ex);
}
return result;
}
我现在正试图(慢慢地(将代码升级到.NET 5.0,并从UnitTests(其他项目都没有引用的项目之一(开始。我已经写了以下代码
public OperationResult<Assembly> CompileClassWithRoslyn(string code, List<string> referenceAssemblies, string assemblyName)
{
OperationResult<Assembly> result = new OperationResult<Assembly>();
try
{
//Set file name, location and referenced assemblies
string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);
if (!referenceAssemblies.Any(a => a.Contains("mscorlib")))
{
referenceAssemblies.Add("mscorlib.dll");
}
var references = trustedAssembliesPaths.Where(p => referenceAssemblies.Contains(Path.GetFileName(p)))
.Select(p => MetadataReference.CreateFromFile(p))
.ToList();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
IEnumerable<Diagnostic> failures = emitResult.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
result.HandleFailure(failures.Select(f => f.GetMessage()));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
}
}
}
catch (Exception ex)
{
return result.HandleFailure(ex);
}
return result;
}
在旧代码中,在
parameters.ReferencedAssemblies.AddRange(references);
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);
程序集由运行时按名称自动选择。在新代码中,mscorlib没有正确解析,因为我得到了一个错误:
错误CS0518:预定义类型"System.Object"未定义或导入
当使用Roslyn针对.net5进行编译时,挑战与针对遗留.net框架进行编译大不相同,因为您必须引用引用程序集,而不是实现程序集。许多提示会让您参考System.Private.CoreLib.dll,这是一个实现程序集,从而使您走向错误的方向。例如MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
下面的代码引用了.net 5 的所有引用程序集(VB除外(
foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.NETCore.App.Ref5.0.0refnet5.0", "*.dll"))
{
if (!dll.Contains("VisualBasic"))
references.Add(MetadataReference.CreateFromFile(dll));
}
如果使用Windows窗体兼容包(net5.0-windows
(,请添加以下程序集:
foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.WindowsDesktop.App.Ref5.0.0refnet5.0", "*.dll"))
{
if (!dll.Contains("VisualBasic") && !dll.Contains("PresentationFramework") && !dll.Contains("ReachFramework"))
references.Add(MetadataReference.CreateFromFile(dll));
}
有了这些参考
- 编译过程中没有出现错误
- 生成的程序集可以在其他项目中使用,而不会抱怨缺少引用(例如System.Private.CoreLib.dll(
框架的所有程序集?当窥探生成的代码时,您将看到只有所需的程序集被引用。
如果编译必须在不存在上述目录的机器上运行,一个可能的解决方案是:
- 将所有这些引用程序集嵌入为嵌入资源
- 使用
Assembly.GetExecutingAssembly().GetManifestResourceStream
将这些嵌入资源读取为stream
- 用这些流填充
byte[]
- 使用
references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll)));
添加引用