Options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Debug);
var references = _defaultAssemblies
.Select(assembly => assembly.Location)
.Where(path => !string.IsNullOrEmpty(path) && File.Exists(path))
.Select(path => MetadataReference.CreateFromFile(path))
.ToList();
var syntaxTrees = sourceFiles
.Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p)))
.ToList();
var compilation = CSharpCompilation.Create(_pluginName)
.WithOptions(compilationOptions)
.AddReferences(references)
.AddSyntaxTrees(syntaxTrees);
var emitResult = compilation.Emit(dllPath);
if (!emitResult.Success)
{
throw new InvalidOperationException(string.Join("\r\n",
emitResult.Diagnostics.Where(d => d.WarningLevel == 0)));
}
//return _context.LoadFromAssemblyPath(Path.GetFullPath(dllPath));
using (var stream = File.OpenRead(dllPath))
{
var assembly = _context.LoadFromStream(stream);
return assembly;
}
}
private IPlugin GetInstance()
{
var instance = _instance;
if (instance != null && !_changed)
return instance;
lock (_reloadLock)
{
instance = _instance;
if (instance != null && !_changed)
return instance;
UnloadPlugin();
_context = new AssemblyLoadContext(
name: $"Plugin-{_pluginName}", isCollectible: true);
var assembly = CompilePlugin();
var pluginType = assembly.GetTypes()
.First(t => typeof(IPlugin).IsAssignableFrom(t));
instance = (IPlugin)Activator.CreateInstance(pluginType);
_instance = instance;
_changed = false;
}
return instance;
}
public string GetMessage()
{
return GetInstance().GetMessage();
}
public void Dispose()
{
UnloadPlugin();
_watcher?.Dispose();
_watcher = null;
}
}
internal class Program
{
static void Main(string[] args)
{
using (var controller = new PluginController("MyPlugin", "../guest"))
{
bool keepRunning = true;
Console.CancelKeyPress += (sender, e) => {
e.Cancel = true;
keepRunning = false;
};
while (keepRunning)
{
try
{
Console.WriteLine(controller.GetMessage());
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType()}: {ex.Message}");
}
Thread.Sleep(1000);
}
}
}
}
}
host.csproj 的内容:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" />
</ItemGroup>
</Project>
Plugin.cs 的内容:
using System;
using Common;
namespace Guest
{
public class MyPlugin : IPlugin
{
public MyPlugin()
{
Console.WriteLine("MyPlugin loaded");
}
public string GetMessage()
{
return "Hello 1";
}
public void Dispose()
{
Console.WriteLine("MyPlugin unloaded");
}
}
}
运行示例程序
进入 pluginexample/host
下运行 dotnet run
即可启动宿主程序,这时宿主程序会自动编译与加载插件,检测插件文件的变化并在变化时重新编译加载。你可以在运行后修改 pluginexample/guest/Plugin.cs
中的 Hello 1
为 Hello 2
,之后可以看到类似以下的输出:
MyPlugin loaded
Hello 1
Hello 1
Hello 1
MyPlugin unloaded
MyPlugin loaded
Hello 2
Hello 2
我们可以看到程序自动更新并执行修改以后的代码,如果你有兴趣还可以测试插件代码语法错误时会出现什么。
源代