Last active
January 4, 2023 10:32
-
-
Save createdbyx/7ba3ebc4d510aaaa388b2babe932acf7 to your computer and use it in GitHub Desktop.
Simple bare bones example of how to execute C# code in your app under a separate context for plugins and dispose when done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) | |
{ | |
// setup the wrapper code template. | |
var generatedCode = | |
"using LiveCodeExecutionExampleDemo;" + Environment.NewLine + | |
"public class Plugin" + Environment.NewLine + | |
"{" + Environment.NewLine + | |
" public void RunCode(AppModel app)" + Environment.NewLine + | |
" {" + Environment.NewLine + | |
$"{this.CodeEditor.Text}" + | |
" }" + Environment.NewLine + | |
"}" + Environment.NewLine; | |
// parse the generated C# code | |
var syntaxTree = CSharpSyntaxTree.ParseText(generatedCode); | |
// Create a collection of references to the assemblies that the code depends on. We just give it everything this app domain has loaded. | |
var domainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); | |
var assembliesWithUncLocation = domainAssemblies.Where(x => !string.IsNullOrWhiteSpace(x.Location)); | |
var references = assembliesWithUncLocation.Select(x => MetadataReference.CreateFromFile(x.Location)); | |
// Create a C# compilation using the syntax tree and the list of references | |
var compilation = CSharpCompilation.Create( | |
"MyAssembly", | |
syntaxTrees: new[] { syntaxTree }, | |
references: references, | |
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); | |
// Create a memory stream to hold the compiled assembly. Memory stream will be disposed after emitting the assembly as it is no longer needed. | |
using (var stream = new MemoryStream()) | |
{ | |
// Compile the assembly and write it to the stream | |
var result = compilation.Emit(stream); | |
// Check for compilation errors | |
if (result.Success) | |
{ | |
// The assembly was successfully compiled, so you can use it in your application. But remember to seek ot the beginning of the stream first | |
stream.Seek(0, SeekOrigin.Begin); | |
this.RunPlugin(stream); | |
} | |
else | |
{ | |
// There were compilation errors, so you will need to fix them before you can use the assembly | |
foreach (var diagnostic in result.Diagnostics) | |
{ | |
this.OutputLog.AppendText(diagnostic.ToString() + Environment.NewLine); | |
} | |
} | |
} | |
} | |
private void RunPlugin(Stream stream) | |
{ | |
// Load the plugin assembly into a separate load context to isolate it, that way we cal unload the context and loaded assemblies when we are done. | |
var pluginContext = new AssemblyLoadContext("PluginContext", true); | |
// load the assembly | |
var assembly = pluginContext.LoadFromStream(stream); | |
// finds the plugin type | |
var pluginType = assembly.GetExportedTypes().First(x => x.Name.Equals("Plugin")); | |
// we wrap with a try catch so if something goes wrong we can report it to the log | |
try | |
{ | |
// create plugin and invoke the RunCode method passing in the app model reference | |
var objInst = Activator.CreateInstance(pluginType); | |
var method = pluginType.GetMethods().Where(x => x.IsPublic).FirstOrDefault(x => x.Name.Equals("RunCode")); | |
method.Invoke(objInst, new[] { this.model }); | |
} | |
catch (Exception ex) | |
{ | |
// something went wrong so report it | |
this.OutputLog.AppendText(ex.ToString() + Environment.NewLine); | |
} | |
// Unload the context and release the plugin | |
pluginContext.Unload(); | |
} | |
public class AppModel | |
{ | |
private readonly TextBox log; | |
public AppModel(TextBox outputLog) | |
{ | |
this.log = outputLog ?? throw new ArgumentNullException(nameof(outputLog)); | |
} | |
public void Write(string text) | |
{ | |
this.log.AppendText(text + Environment.NewLine); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment