Loading an assembly generated by the Roslyn compiler - c#

I'm generating a Greeter.dll using the Roslyn compiler. My problem occurs trying to load the DLL file.
Here's the code:
using System;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using System.IO;
using System.Reflection;
using System.Linq;
namespace LoadingAClass
{
class Program
{
static void Main(string[] args)
{
var syntaxTree = SyntaxTree.ParseCompilationUnit(#"
class Greeter
{
static void Greet()
{
Console.WriteLine(""Hello, World"");
}
}");
var compilation = Compilation.Create("Greeter.dll",
syntaxTrees: new[] { syntaxTree },
references: new[] {
new AssemblyFileReference(typeof(object).Assembly.Location),
new AssemblyFileReference(typeof(Enumerable).Assembly.Location),
});
Assembly assembly;
using (var file = new FileStream("Greeter.dll", FileMode.Create))
{
EmitResult result = compilation.Emit(file);
}
assembly = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), #"Greeter.dll"));
Type type = assembly.GetType("Greeter");
var obj = Activator.CreateInstance(type);
type.InvokeMember("Greet",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
null);
Console.WriteLine("<ENTER> to continue");
Console.ReadLine();
}
}
// Thanks, http://blogs.msdn.com/b/csharpfaq/archive/2011/11/23/using-the-roslyn-symbol-api.aspx
}
The error message occurs on the line assembly = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), #"Greeter.dll")); and reads
Im Modul wurde ein Assemblymanifest erwartet. (Ausnahme von HRESULT: 0x80131018)
Which roughly translates to
An assembly manifest was expected in the module.
Does anyone know what I'm missing here?

I stumbled across this and, even though you have an accepted answer, I don't think it's helpful in general. So, I'll just leave this here for future searchers like myself.
The problem with the code is two things, which you would have found out by looking at the returned value from
EmitResult result = compilation.Emit(file);
If you look at the properties on the EmitResult object, you would have found that there were 2 errors in the results.Diagnostics member.
Main method not found
Couldn't find class Console
So, to fix the problem,
1. You need to mark the output as a dll
2. You need to add 'using System;' to the code you're passing into the API or say 'System.Console.WriteLine'
The following code works making changes to fix those two issues:
var outputFile = "Greeter.dll";
var syntaxTree = SyntaxTree.ParseCompilationUnit(#"
// ADDED THE FOLLOWING LINE
using System;
class Greeter
{
public void Greet()
{
Console.WriteLine(""Hello, World"");
}
}");
var compilation = Compilation.Create(outputFile,
syntaxTrees: new[] { syntaxTree },
references: new[] {
new AssemblyFileReference(typeof(object).Assembly.Location),
new AssemblyFileReference(typeof(Enumerable).Assembly.Location),
},
// ADDED THE FOLLOWING LINE
options: new CompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var file = new FileStream(outputFile, FileMode.Create))
{
EmitResult result = compilation.Emit(file);
}
Assembly assembly = Assembly.LoadFrom("Greeter.dll");
Type type = assembly.GetType("Greeter");
var obj = Activator.CreateInstance(type);
type.InvokeMember("Greet",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
null);
Console.WriteLine("<ENTER> to continue");
Console.ReadLine();

I have been adding Roslyn support to the O2 Plarform and here is how you can use its Roslyn support to compile (code), create (and assembly) and invoke (its method) one line of code:
return #"using System; class Greeter { static string Greet() { return ""Another hello!!""; }}"
.tree().compiler("Great").create_Assembly().type("Greeter").invokeStatic("Greet");
//O2Ref:O2_FluentSharp_Roslyn.dll
Here is a version that executes a code snippet that looks like yours (I added a return value):
panel.clear().add_ConsoleOut();
var code = #"
using System;
class Greeter
{
static string Greet()
{
Console.WriteLine(""Hello, World"");
return ""hello from here"";
}
}";
var tree = code.astTree();
if (tree.hasErrors())
return tree.errors();
var compiler = tree.compiler("Great")
.add_Reference("mscorlib");
if (compiler.hasErrors())
return compiler.errors();
var assembly =tree.compiler("Great")
.create_Assembly();
return assembly.type("Greeter")
.invokeStatic("Greet");
//O2Ref:O2_FluentSharp_Roslyn.dll
//O2File:_Extra_methods_Roslyn_API.cs
//O2File:API_ConsoleOut.cs
For a couple more details and screenshots of what this looks like, see this blog post: 1 line to compile, create and execute: O2 Script to use Roslyn to Dynamically compile and execute a method
UPDATE: see http://blog.diniscruz.com/search/label/Roslyn for a large number number of Roslyn related posts and tools (created using the O2 Platform)

There is a new API for the References that looks like this:
var compilation = Compilation.Create(outputFile,
syntaxTrees: new[] { syntaxTree },
references: new[] {
new MetadataFileReference(typeof(object).Assembly.Location),
new MetadataFileReference(typeof(Enumerable).Assembly.Location),
},
options: new CompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
This is with the latest Roslyn-CTP 2012 in Sept...

This code worked beautifully:
using System;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using System.IO;
using System.Reflection;
using System.Linq;
namespace LoadingAClass
{
class Program
{
static void Main(string[] args)
{
var syntaxTree = SyntaxTree.ParseCompilationUnit(#"
using System;
namespace HelloWorld
{
class Greeter
{
public static void Greet()
{
Console.WriteLine(""Hello, World"");
}
}
}");
string dllPath = Path.Combine(Directory.GetCurrentDirectory(), "Greeter.dll");
string pdbPath = Path.Combine(Directory.GetCurrentDirectory(), "Greeter.pdb");
var compilation = Compilation.Create(dllPath,
new CompilationOptions(
assemblyKind: AssemblyKind.DynamicallyLinkedLibrary
))
.AddSyntaxTrees( syntaxTree )
.AddReferences(new AssemblyFileReference(typeof(object).Assembly.Location))
.AddReferences(new AssemblyFileReference(typeof(Enumerable).Assembly.Location));
EmitResult result;
using (FileStream dllStream = new FileStream(dllPath, FileMode.OpenOrCreate))
using (FileStream pdbStream = new FileStream(pdbPath, FileMode.OpenOrCreate))
{
result = compilation.Emit(
executableStream: dllStream,
pdbFileName: pdbPath,
pdbStream: pdbStream);
}
if (result.Success)
{
//assembly = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), #"Greeter.dll"));
Assembly assembly = Assembly.LoadFrom(#"Greeter.dll");
Type type = assembly.GetType("HelloWorld.Greeter");
var obj = Activator.CreateInstance(type);
type.InvokeMember("Greet",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
null);
}
else
{
Console.WriteLine("No Go");
Console.WriteLine(result.Diagnostics.ToString());
}
Console.WriteLine("<ENTER> to continue");
Console.ReadLine();
}
}
// Thanks, http://blogs.msdn.com/b/csharpfaq/archive/2011/11/23/using-the-roslyn-symbol-api.aspx
// Thanks, http://social.msdn.microsoft.com/Forums/en-US/roslyn/thread/d620a4a1-3a90-401b-b946-bfa1fc6ad7a2
}

Turns out I needed to create a pdb file.
using (FileStream dllStream = new FileStream(dllPath, FileMode.OpenOrCreate))
using (FileStream pdbStream = new FileStream(pdbPath, FileMode.OpenOrCreate))
{
result = compilation.Emit(
executableStream: dllStream,
pdbFileName: pdbPath,
pdbStream: pdbStream);
}

Related

Need to check if code contains certain identifiers

I am going to be dynamically compiling and executing code using Roslyn like the example below. I want to make sure the code does not violate some of my rules, like:
Does not use Reflection
Does not use HttpClient or WebClient
Does not use File or Directory classes in System.IO namespace
Does not use Source Generators
Does not call unmanaged code
Where in the following code would I insert my rules/checks and how would I do them?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using System.Reflection;
using System.Runtime.CompilerServices;
string code = #"using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
namespace Customization
{
public class Script
{
public async Task<object?> RunAsync(object? data)
{
//The following should not be allowed
File.Delete(#""C:\Temp\log.txt"");
return await Task.FromResult(data);
}
}
}";
var compilation = Compile(code);
var bytes = Build(compilation);
Console.WriteLine("Done");
CSharpCompilation Compile(string code)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string? dotNetCoreDirectoryPath = Path.GetDirectoryName(typeof(object).GetTypeInfo().Assembly.Location);
if (String.IsNullOrWhiteSpace(dotNetCoreDirectoryPath))
{
throw new ArgumentNullException("Cannot determine path to current assembly.");
}
string assemblyName = Path.GetRandomFileName();
List<MetadataReference> references = new();
references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Console).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Dictionary<,>).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(dotNetCoreDirectoryPath, "System.Runtime.dll")));
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
SemanticModel model = compilation.GetSemanticModel(syntaxTree);
CompilationUnitSyntax root = (CompilationUnitSyntax)syntaxTree.GetRoot();
//TODO: Check the code for use classes that are not allowed such as File in the System.IO namespace.
//Not exactly sure how to walk through identifiers.
IEnumerable<IdentifierNameSyntax> identifiers = root.DescendantNodes()
.Where(s => s is IdentifierNameSyntax)
.Cast<IdentifierNameSyntax>();
return compilation;
}
[MethodImpl(MethodImplOptions.NoInlining)]
byte[] Build(CSharpCompilation compilation)
{
using (MemoryStream ms = new())
{
//Emit to catch build errors
EmitResult emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
Diagnostic? firstError =
emitResult
.Diagnostics
.FirstOrDefault
(
diagnostic => diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error
);
throw new Exception(firstError?.GetMessage());
}
return ms.ToArray();
}
}
When checking for the use of a particular class you can look for IdentifierNameSyntax type nodes by using the OfType<>() method and filter the results by class name:
var names = root.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Where(i => string.Equals(i.Identifier.ValueText, className, StringComparison.OrdinalIgnoreCase));
You can then use the SemanticModel to check the namespace of the class:
foreach (var name in names)
{
var typeInfo = model.GetTypeInfo(name);
if (string.Equals(typeInfo.Type?.ContainingNamespace?.ToString(), containingNamespace, StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"Class {containingNamespace}.{className} is not allowed.");
}
}
To check for the use of reflection or unmanaged code you could check for the relevant usings System.Reflection and System.Runtime.InteropServices.
if (root.Usings.Any(u => string.Equals(u.Name.ToString(), disallowedNamespace, StringComparison.OrdinalIgnoreCase)))
{
throw new Exception($"Namespace {disallowedNamespace} is not allowed.");
}
This would catch cases where the usings were unused i.e., no actual reflection or unmanaged code, but that seems like an acceptable trade off.
I'm not sure what to do about the source generator checks as these are normally included as project references so I don't know how they'd run against dynamically compiled code.
Keeping the checks in the same place and updating your code gives:
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
string code = #"using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices
namespace Customization
{
public class Script
{
static readonly HttpClient client = new HttpClient();
public async Task<object?> RunAsync(object? data)
{
//The following should not be allowed
File.Delete(#""C:\Temp\log.txt"");
return await Task.FromResult(data);
}
}
}";
var compilation = Compile(code);
var bytes = Build(compilation);
Console.WriteLine("Done");
CSharpCompilation Compile(string code)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string? dotNetCoreDirectoryPath = Path.GetDirectoryName(typeof(object).GetTypeInfo().Assembly.Location);
if (String.IsNullOrWhiteSpace(dotNetCoreDirectoryPath))
{
throw new InvalidOperationException("Cannot determine path to current assembly.");
}
string assemblyName = Path.GetRandomFileName();
List<MetadataReference> references = new();
references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Console).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Dictionary<,>).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(HttpClient).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(dotNetCoreDirectoryPath, "System.Runtime.dll")));
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
SemanticModel model = compilation.GetSemanticModel(syntaxTree);
CompilationUnitSyntax root = (CompilationUnitSyntax)syntaxTree.GetRoot();
ThrowOnDisallowedClass("File", "System.IO", root, model);
ThrowOnDisallowedClass("HttpClient", "System.Net.Http", root, model);
ThrowOnDisallowedNamespace("System.Reflection", root);
ThrowOnDisallowedNamespace("System.Runtime.InteropServices", root);
return compilation;
}
[MethodImpl(MethodImplOptions.NoInlining)]
byte[] Build(CSharpCompilation compilation)
{
using (MemoryStream ms = new())
{
//Emit to catch build errors
EmitResult emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
Diagnostic? firstError =
emitResult
.Diagnostics
.FirstOrDefault
(
diagnostic => diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error
);
throw new Exception(firstError?.GetMessage());
}
return ms.ToArray();
}
}
void ThrowOnDisallowedClass(string className, string containingNamespace, CompilationUnitSyntax root, SemanticModel model)
{
var names = root.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Where(i => string.Equals(i.Identifier.ValueText, className, StringComparison.OrdinalIgnoreCase));
foreach (var name in names)
{
var typeInfo = model.GetTypeInfo(name);
if (string.Equals(typeInfo.Type?.ContainingNamespace?.ToString(), containingNamespace, StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"Class {containingNamespace}.{className} is not allowed.");
}
}
}
void ThrowOnDisallowedNamespace(string disallowedNamespace, CompilationUnitSyntax root)
{
if (root.Usings.Any(u => string.Equals(u.Name.ToString(), disallowedNamespace, StringComparison.OrdinalIgnoreCase)))
{
throw new Exception($"Namespace {disallowedNamespace} is not allowed.");
}
}
I've used throw for rule violations here which will mean that multiple violations will not be reported all at once so you may want to tweak that so it's a bit more efficient.
The SymbolInfo class provides some of the meatadata needed to create rules to restrict use of certain code. Here is what I came up with so far. Any suggestions on how to improve on this would be appreciated.
//Check for banned namespaces
string[] namespaceBlacklist = new string[] { "System.Net", "System.IO" };
foreach (IdentifierNameSyntax identifier in identifiers)
{
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(identifier);
if (symbolInfo.Symbol is { })
{
if (symbolInfo.Symbol.Kind == SymbolKind.Namespace)
{
if (namespaceBlacklist.Any(ns => ns == symbolInfo.Symbol.ToDisplayString()))
{
throw new Exception($"Declaration of namespace '{symbolInfo.Symbol.ToDisplayString()}' is not allowed.");
}
}
else if (symbolInfo.Symbol.Kind == SymbolKind.NamedType)
{
if (namespaceBlacklist.Any(ns => symbolInfo.Symbol.ToDisplayString().StartsWith(ns + ".")))
{
throw new Exception($"Use of namespace '{identifier.Identifier.ValueText}' is not allowed.");
}
}
}
}

Not getting a namespace/reference in CSharpCompilation .net Core

I have a project that uses some dynamically compiled code and I am upgrading from .net framework to .net core 3.1.
I can't get a simple test case to include newtonsoft.json.dll and get the error "Type or namespace name 'Newtonsoft' could not be found. I had a similar problem when I first tried add the library, but got past it by using the currently loaded assemblies (Can't include Newtonsoft JSON in CSharpCodeProvider script). With "Core" I don't get errors about a library, but it doesn't know the type, like it didn't get loaded.
I tried both using the project libraries (commented out) and specifying them directly, but have the same issue. To recreate, make a new .netCore 3.1 console application called "TestScript" and install nuget packages "Microsoft.CodeAnalysis.CSharp.Scripting" v 3.7.0, "Newtonsoft.Json" v12.0.3 and use the following code.
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
namespace TestScript
{
class Program
{
public static void Example1()
{
var assemblyName = "UserScript";
var code = #"namespace UserScript
{
using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
public class RunScript
{
private const int x = 99;
public int Eval()
{
JObject j = new JObject();
return x;
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var references = new List<MetadataReference>();
//Load project libraries
//var assemblies = AppDomain.CurrentDomain
// .GetAssemblies()
// .Where(a => !a.IsDynamic)
// .Select(a => a.Location);
//foreach (var item in assemblies)
//{
// if (!item.Contains("xunit"))
// references.Add(MetadataReference.CreateFromFile(item));
//}
//or specify the libraries to load.
var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location);
var exeDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
references.Add(MetadataReference.CreateFromFile(typeof(Object).GetTypeInfo().Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Uri).GetTypeInfo().Assembly.Location));
references.Add(MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"));
references.Add(MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Runtime.dll"));
if (File.Exists(exeDir + "\\Newtonsoft.Json.dll"))
references.Add(MetadataReference.CreateFromFile(exeDir + "\\Newtonsoft.Json.dll"));
else
throw new Exception("Missing newtonsoft DLL");
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
new[] { syntaxTree },
new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var memoryStream = new MemoryStream())
{
var result = compilation.Emit(memoryStream);
if (result.Success)
{
memoryStream.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(memoryStream.ToArray());
Type testClassType = assembly.GetType("TestNamespace.TestClass");
var addResult = (int)testClassType.GetMethod("Add").Invoke(null, new object[] { 3, 4 });
Console.WriteLine(addResult);
}
else
{
Console.WriteLine("Failed to compile");
for (var i = 0; i < result.Diagnostics.Length; i++)
{
Console.WriteLine(result.Diagnostics[i].ToString());
}
}
}
}
static void Main(string[] args)
{
JObject j = null; //to make sure newtonsoft is included if loading current projects libraries
Example1();
}
}
}
Your code should work fine if you don't forget to use the references list you've built up.
See test code on .NET Fiddle (link) - I used the AppDomain method there (one you commented out).
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
new[] { syntaxTree },
references, //<-- you're missing this
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
Also, your invocations weren't actually calling the right Class and Method, which I guess you'll find out after you get past the compilation issues.
// Non-existent Type and Method...
Type testClassType = assembly.GetType("TestNamespace.TestClass");
var addResult = (int)testClassType.GetMethod("Add").Invoke(null, new object[] { 3, 4 });
Edit: Here's the full working code in case the Fiddle gets deleted:
public static void Main(string[] args)
{
var j = new JObject(); // ensure assembly is available
Example1();
}
public static void Example1()
{
var assemblyName = "UserScript";
var code = #"namespace UserScript {
using Newtonsoft.Json;
public class RunScript {
public static string Eval() {
return JsonConvert.SerializeObject(new int[] {1,2,3,4});
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
//Load project libraries
var references = AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => !a.IsDynamic)
.Select(a => a.Location)
.Where(s => !string.IsNullOrEmpty(s))
.Where(s => !s.Contains("xunit"))
.Select(s => MetadataReference.CreateFromFile(s))
.ToList()
;
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
new[] { syntaxTree },
references, //<-- you're missing this
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var memoryStream = new MemoryStream())
{
var result = compilation.Emit(memoryStream);
if (result.Success)
{
memoryStream.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(memoryStream.ToArray());
var testClassType = assembly.GetType("UserScript.RunScript");
var invokeResult = (string)testClassType.GetMethod("Eval").Invoke(null, null);
Console.WriteLine(invokeResult);
}
else
{
Console.WriteLine("Failed to compile");
foreach (var diag in result.Diagnostics)
Console.WriteLine(diag);
}
}
}

Dynamically compile and run code using .net core 3.0 for scripting

I want to offer the possibly to compile and run code (Csharp Classes) in .NET core 3 for purpose of scripting. The scripts (classes) shall be loaded from the file system and injected in an existing (static) assembly.
https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (using AssemblyContext) seems to be a valid approach for this.
Is there a simpler solution (with less overhead) if I do not have the need to isolate the script code in an assembly?. (Debugging should be possible)
There is a solution at https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/.
To save time, here is a variant of the program that runs a single file, supports LINQ, and classes from current project DLL :
Program.cs
using System.Linq;
using DynamicRun.Builder;
namespace DynamicRun
{
class Program
{
static void Main(string[] args)
{
var compiler = new Compiler();
var runner = new Runner();
byte[] compiled = compiler.Compile(args.FirstOrDefault());
runner.Execute(compiled, args[1..args.Length]);
}
}
}
Compiler.cs
using System;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
namespace DynamicRun.Builder
{
internal class Compiler
{
public byte[] Compile(string filepath)
{
var sourceCode = File.ReadAllText(filepath);
using (var peStream = new MemoryStream())
{
var result = GenerateCode(sourceCode).Emit(peStream);
if (!result.Success)
{
Console.WriteLine("Compilation done with error.");
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
foreach (var diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
return null;
}
Console.WriteLine("Compilation done without any error.");
peStream.Seek(0, SeekOrigin.Begin);
return peStream.ToArray();
}
}
private static CSharpCompilation GenerateCode(string sourceCode)
{
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
// Todo : to load current dll in the compilation context
MetadataReference.CreateFromFile(typeof(Family.Startup).Assembly.Location),
};
return CSharpCompilation.Create("Hello.dll",
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
}
}
}
Runner.cs
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace DynamicRun.Builder
{
internal class Runner
{
public void Execute(byte[] compiledAssembly, string[] args)
{
var assemblyLoadContext = LoadAndExecute(compiledAssembly, args);
for (var i = 0; i < 8 && assemblyLoadContext.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine(assemblyLoadContext.IsAlive ? "Unloading failed!" : "Unloading success!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference LoadAndExecute(byte[] compiledAssembly, string[] args)
{
using (var asm = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
var assembly = assemblyLoadContext.LoadFromStream(asm);
var entry = assembly.EntryPoint;
_ = entry != null && entry.GetParameters().Length > 0
? entry.Invoke(null, new object[] {args})
: entry.Invoke(null, null);
assemblyLoadContext.Unload();
return new WeakReference(assemblyLoadContext);
}
}
}
}

CSharpCompilation.Emit() throwing error while executing Roslyn API

I am trying to build a Dynamically Linked Library (DLL) from Roslyn API which builds a Syntax tree from the raw text input.
However, I am getting an exception on while emitting the result of the compilation.
The exception thrown here is:
"error CS0234: The type or namespace name 'Console' does not exist in the namespace 'System' (Are you missing an assembly reference)"
.NET Framework used: 4.7.1
App Type: .NET Core 2.0 Console App
Here is the code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
namespace RoslynCompileSample
{
class Program
{
static void Main(string[] args)
{
string code = #"
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Write(string message)
{
System.Console.WriteLine(message);
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code, CSharpParseOptions.Default);
var syntaxRoot = syntaxTree.GetRoot();
var MyClass = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>().First();
var MyMethod = syntaxRoot.DescendantNodes().OfType<MethodDeclarationSyntax>().First();
Console.WriteLine(MyClass.Identifier.ToString());
Console.WriteLine(MyMethod.Identifier.ToString());
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
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 = Assembly.Load(ms.ToArray());
Type type = assembly.GetType("RoslynCompileSample.Writer");
object obj = Activator.CreateInstance(type);
type.InvokeMember("Write",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
new object[] { "Hello World" });
}
}
}
}
}
Can't seem to figure out, what is the issue. System.Dll is already added into the references.
Thanks.

Compiling with CodeDom

I started experimenting a bit with CodeDom and made simple Application which collects sourcecode from the user input and tries to compile it with C#-Syntax.
For those who want to try the whole proccess, type end... to finish up the sourcecode entry.
Here's the example:
using System;
using System.Collections;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace CodeDomTest
{
class Program
{
static void Main(string[] args)
{
getTestCode();
}
public static Assembly getTestCode()
{
CompilerParameters CompilerOptions = new CompilerParameters(
assemblyNames: new String[] { "mscorlib.dll", "System.dll", "System.Core.dll" },
outputName: "test.dll",
includeDebugInformation: false)
{ TreatWarningsAsErrors = true, WarningLevel = 0, GenerateExecutable = false, GenerateInMemory = true };
List<String> newList = new List<String>();
String a = null;
while(a != "end...")
{
a = Console.ReadLine();
if (!a.Equals( "end..."))
newList.Add(a);
}
String[] source = { "class Test {static void test() {System.Console.WriteLine(\"test\");}}" };
source = newList.ToArray();
CSharpCodeProvider zb = new CSharpCodeProvider(new Dictionary<String, String> { { "CompilerVersion", "v4.0" } });
CompilerResults Results = zb.CompileAssemblyFromSource(CompilerOptions, source);
Console.WriteLine(Results.Errors.HasErrors);
CompilerErrorCollection errs = Results.Errors;
foreach(CompilerError z in errs)
{
Console.WriteLine(z.ErrorText);
}
if (!(errs.Count > 0))
{
AssemblyName assemblyRef = Results.CompiledAssembly.GetName();
AppDomain.CurrentDomain.Load(assemblyRef);
//foreach (String a in )
Console.WriteLine(Results.CompiledAssembly.FullName.ToString());
Type tempType = Results.CompiledAssembly.GetType("Test");
MethodInfo tempMethodInfo = tempType.GetMethod("test", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
if (tempMethodInfo != null)
tempMethodInfo.Invoke(null,null);
}
Console.ReadLine();
return null;
}
}
}
Now as you can see, basically it compiles the following code:
class Test {static void test() {System.Console.WriteLine(\"test\");}}
Which works fine if you enter it like that (without the ") as userinput into the program. But as soon as you insert a line break by pressing enter after one finished line, the compiling breaks with several errors. It seems like it would evaluate each line as own program by giving following statements:
} expected
Expected class, delegate, enum, interface, or struct
A namespace cannot directly contain members such as fields or methods
A namespace cannot directly contain members such as fields or methods
Type or namespace definition, or end-of-file expected
Type or namespace definition, or end-of-file expected
For following input:
class Test
{
static void test()
{
System.Console.WriteLine
("test");
}
}
Do I have to break user (custom) entries down to one line then?
Each line in sources should contain complete source code not a single line of code. Since you're gathering the code line by line into your source array, you'll have to collapse it into a single string then add that string to an array to pass to CompileAssemblyFromSource
Try this:
while (a != "end...")
{
a = Console.ReadLine();
if (!a.Equals("end..."))
newList.Add(a);
}
string code = string.Join("\r\n", newList);
string[] source = new string[] { code };

Categories

Resources