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 };
Related
Trying to compile simple C# code at runtime on .NET Core but have this error:
System.PlatformNotSupportedException: 'Operation is not supported on
this platform.'
on this line:
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
My code:
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
string code = #"
using System;
namespace First
{
public class Program
{
public static void Main()
{
" +
"Console.WriteLine(\"Hello, world!\");"
+ #"
}
}
}
";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("First.Program");
MethodInfo main = program.GetMethod("Main");
main.Invoke(null, null);
I recommend using the Roslyn compiler. You'll need to add references Microsoft.CodeAnalysis and Microsoft.CodeAnalysis.CSharp for the following example to work. Note, that the RoslynCompiler class loads the assembly dynamically. You can modify the class fairly easily to use a FileStream instead of a MemoryStream if you want to save the compilation to disk for reuse.
Sample Usage of RoslynCompiler Class (below)
string code = #"
using System;
namespace First
{
public class Program
{
public static void Main()
{
Console.WriteLine(\"Hello, world!\");
}
public static void WithParams(string message)
{
Console.WriteLine(message);
}
}
}
";
var compiler = new RoslynCompiler("First.Program", code, new[] {typeof(Console)});
var type = compiler.Compile();
type.GetMethod("Main").Invoke(null, null);
//result: Hellow World!
// pass an object array to the second null parameter to pass arguments
type.GetMethod("WithParams").Invoke(null, new object[] {"Hi there from invoke!"});
//result: Hi from invoke
Roslyn Compiler Class (Quick and Dirty Example)
public class RoslynCompiler
{
readonly CSharpCompilation _compilation;
Assembly _generatedAssembly;
Type? _proxyType;
string _assemblyName;
string _typeName;
public RoslynCompiler(string typeName, string code, Type[] typesToReference)
{
_typeName = typeName;
var refs = typesToReference.Select(h => MetadataReference.CreateFromFile(h.Assembly.Location) as MetadataReference).ToList();
//some default refeerences
refs.Add(MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll")));
refs.Add(MetadataReference.CreateFromFile(typeof(Object).Assembly.Location));
//generate syntax tree from code and config compilation options
var syntax = CSharpSyntaxTree.ParseText(code);
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
allowUnsafe: true,
optimizationLevel: OptimizationLevel.Release);
_compilation = CSharpCompilation.Create(_assemblyName = Guid.NewGuid().ToString(), new List<SyntaxTree> { syntax }, refs, options);
}
public Type Compile()
{
if (_proxyType != null) return _proxyType;
using (var ms = new MemoryStream())
{
var result = _compilation.Emit(ms);
if (!result.Success)
{
var compilationErrors = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error)
.ToList();
if (compilationErrors.Any())
{
var firstError = compilationErrors.First();
var errorNumber = firstError.Id;
var errorDescription = firstError.GetMessage();
var firstErrorMessage = $"{errorNumber}: {errorDescription};";
var exception = new Exception($"Compilation failed, first error is: {firstErrorMessage}");
compilationErrors.ForEach(e => { if (!exception.Data.Contains(e.Id)) exception.Data.Add(e.Id, e.GetMessage()); });
throw exception;
}
}
ms.Seek(0, SeekOrigin.Begin);
_generatedAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);
_proxyType = _generatedAssembly.GetType(_typeName);
return _proxyType;
}
}
}
Performance Tip
If performance matters, use delegates as opposed to Invoke as follows to achieve near pre-compiled throughput:
void Main()
{
string code = #"OMITTED EXAMPLE CODE FROM SAMPLE ABOVE";
var compiler = new RoslynCompiler("First.Program", code, new[] { typeof(Console) });
var type = compiler.Compile();
// If perf matters used delegates to get near pre-compiled througput vs Invoke()
var cachedDelegate = new DynamicDelegateCacheExample(type);
cachedDelegate.Main();
//result: Hellow world!
cachedDelegate.Main("Hi there from cached delegate!");
//result: Hi there from cached delegate!
}
public class DynamicDelegateCacheExample
{
delegate void methodNoParams();
delegate void methodWithParamas(string message);
private static methodNoParams cachedDelegate;
private static methodWithParamas cachedDelegateWeithParams;
public DynamicDelegateCacheExample(Type myDynamicType)
{
cachedDelegate = myDynamicType.GetMethod("Main").CreateDelegate<methodNoParams>();
cachedDelegateWeithParams = myDynamicType.GetMethod("WithParams").CreateDelegate<methodWithParamas>();
}
public void Main() => cachedDelegate();
public void Main(string message) => cachedDelegateWeithParams(message);
}
With .net core netstandard and publishing to a self contained exe there are a couple more tricks you'll need;
public static ModuleMetadata GetMetadata(this Assembly assembly)
{
// based on https://github.com/dotnet/runtime/issues/36590#issuecomment-689883856
unsafe
{
return assembly.TryGetRawMetadata(out var blob, out var len)
? ModuleMetadata.CreateFromMetadata((IntPtr)blob, len)
: throw new InvalidOperationException($"Could not get metadata from {assembly.FullName}");
}
}
#pragma warning disable IL3000
public static MetadataReference GetReference(this Assembly assembly)
=> (assembly.Location == "")
? AssemblyMetadata.Create(assembly.GetMetadata()).GetReference()
: MetadataReference.CreateFromFile(assembly.Location);
#pragma warning restore IL3000
public static Assembly Compile(string source, IEnumerable<Type> references)
{
var refs = new HashSet<Assembly>(){
typeof(object).Assembly
};
foreach (var t in references)
refs.Add(t.Assembly);
foreach (var a in AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic
&& a.ExportedTypes.Count() == 0
&& (a.FullName.Contains("netstandard") || a.FullName.Contains("System.Runtime,"))))
refs.Add(a);
var options = CSharpParseOptions.Default
.WithLanguageVersion(LanguageVersion.Latest);
var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);
var compilation = CSharpCompilation.Create("Dynamic",
new[] { SyntaxFactory.ParseSyntaxTree(source, options) },
refs.Select(a => a.GetReference()),
compileOptions
);
using var ms = new MemoryStream();
var e = compilation.Emit(ms);
if (!e.Success)
throw new Exception("Compilation failed");
ms.Seek(0, SeekOrigin.Begin);
var context = new AssemblyLoadContext(null, true);
return context.LoadFromStream(ms);
}
// for dynamically implementing some interface;
public static C CompileInstance<C>(string source, IEnumerable<Type> references)
{
var assembly = Compile(source, references);
var modelType = assembly.DefinedTypes.Where(t => typeof(C).IsAssignableFrom(t)).Single();
return (C)Activator.CreateInstance(modelType);
}
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);
}
}
}
}
TLDR;
How do I find all the const string parameters of the references to the index property Microsoft.Extensions.Localization.IStringLocalizer.Item[String] in my Visual Studio solution? All source code is written in C#. The solution must also support MVC razor views.
Additional info
I believe that Roslyn is the answer to the question. I, however, haven't yet found my way through the API to achieve this. I'm also uncertain about whether to use syntax tree, compilation or semantic model. The following is an attempt based on other Q&A here on stackoverflow. Any help to make it work is highly appreciated :-) If you are curious you can read about the reason for this need here.
namespace AspNetCoreLocalizationKeysExtractor
{
using System;
using System.Linq;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
class Program
{
static void Main(string[] args)
{
string solutionPath = #"..\source\MySolution.sln";
var msWorkspace = MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
foreach (var project in solution.Projects.Where(p => p.AssemblyName.StartsWith("MyCompanyNamespace.")))
{
var compilation = project.GetCompilationAsync().Result;
var interfaceType = compilation.GetTypeByMetadataName("Microsoft.Extensions.Localization.IStringLocalizer");
// TODO: Find the indexer based on the name ("Item"/"this"?) and/or on the parameter and return type
var indexer = interfaceType.GetMembers().First();
var indexReferences = SymbolFinder.FindReferencesAsync(indexer, solution).Result.ToList();
foreach (var symbol in indexReferences)
{
// TODO: How to get output comprised by "a location" like e.g. a namespace qualified name and the parameter of the index call. E.g:
//
// MyCompanyNamespace.MyLib.SomeClass: "Please try again"
// MyCompanyNamespace.MyWebApp.Views.Shared._Layout: "Welcome to our cool website"
Console.WriteLine(symbol.Definition.ToDisplayString());
}
}
}
}
}
Update: Workaround
Despite the great help from #Oxoron I've chosen to resort to a simple workaround. Currently Roslyn doesn't find any references using SymbolFinder.FindReferencesAsync. It appears to be according to "silent" msbuild failures. These errors are available like this:
msWorkspace.WorkspaceFailed += (sender, eventArgs) =>
{
Console.Error.WriteLine($"{eventArgs.Diagnostic.Kind}: {eventArgs.Diagnostic.Message}");
Console.Error.WriteLine();
};
and
var compilation = project.GetCompilationAsync().Result;
foreach (var diagnostic in compilation.GetDiagnostics())
Console.Error.WriteLine(diagnostic);
My workaround is roughly like this:
public void ParseSource()
{
var sourceFiles = from f in Directory.GetFiles(SourceDir, "*.cs*", SearchOption.AllDirectories)
where f.EndsWith(".cs") || f.EndsWith(".cshtml")
where !f.Contains(#"\obj\") && !f.Contains(#"\packages\")
select f;
// _["Hello, World!"]
// _[#"Hello, World!"]
// _localizer["Hello, World!"]
var regex = new Regex(#"_(localizer)?\[""(.*?)""\]");
foreach (var sourceFile in sourceFiles)
{
foreach (var line in File.ReadLines(sourceFile))
{
var matches = regex.Matches(line);
foreach (Match match in matches)
{
var resourceKey = GetResourceKeyFromFileName(sourceFile);
var key = match.Groups[2].Value;
Console.WriteLine($"{resourceKey}: {key}");
}
}
}
}
Of course the solution isn't bullet proof and relies on naming conventions and doesn't handle multiline verbatim strings. But it'll probably do the job for us :-)
Take a look on this and this questions, they will help with indexers.
Determine namespaces - it's a bit more difficult.
You can determine it using code like
int spanStart = symbol.Locations[0].Location.SourceSpan.Start;
Document doc = symbol.Locations[0].Location.Document;
var indexerInvokation = doc.GetSyntaxRootAsync().Result.DescendantNodes()
.FirstOrDefault(node => node.GetLocation().SourceSpan.Start == spanStart );
After that just find indexerInvokation parents nodes until MethodDeclarationSyntax, ClassDeclarationSyntax, etc.
Upd1.
Test project code:
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
int test0 = new A().GetInt();
int test1 = new IndexedUno()[2];
int test2 = new IndexedDo()[2];
}
}
public interface IIndexed
{
int this[int i] { get; }
}
public class IndexedUno : IIndexed
{
public int this[int i] => i;
}
public class IndexedDo : IIndexed
{
public int this[int i] => i;
}
public class A
{
public int GetInt() { return new IndexedUno()[1]; }
}
public class B
{
public int GetInt() { return new IndexedDo()[4]; }
}
}
Search code:
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
namespace AnalyzeIndexers
{
class Program
{
static void Main(string[] args)
{
string solutionPath = #"PathToSolution.sln";
var msWorkspace = MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
foreach (var project in solution.Projects.Where(p => p.AssemblyName.StartsWith("TestApp")))
{
var compilation = project.GetCompilationAsync().Result;
var interfaceType = compilation.GetTypeByMetadataName("TestApp.IIndexed");
var indexer = interfaceType
.GetMembers()
.OfType<IPropertySymbol>()
.First(member => member.IsIndexer);
var indexReferences = SymbolFinder.FindReferencesAsync(indexer, solution).Result.ToList();
foreach (var indexReference in indexReferences)
{
foreach (ReferenceLocation indexReferenceLocation in indexReference.Locations)
{
int spanStart = indexReferenceLocation.Location.SourceSpan.Start;
var doc = indexReferenceLocation.Document;
var indexerInvokation = doc.GetSyntaxRootAsync().Result
.DescendantNodes()
.FirstOrDefault(node => node.GetLocation().SourceSpan.Start == spanStart);
var className = indexerInvokation.Ancestors()
.OfType<ClassDeclarationSyntax>()
.FirstOrDefault()
?.Identifier.Text ?? String.Empty;
var #namespace = indexerInvokation.Ancestors()
.OfType<NamespaceDeclarationSyntax>()
.FirstOrDefault()
?.Name.ToString() ?? String.Empty;
Console.WriteLine($"{#namespace}.{className} : {indexerInvokation.GetText()}");
}
}
}
Console.WriteLine();
Console.ReadKey();
}
}
}
Take a look at the var indexer = ... code - it extracts indexer from a type. Maybe you'll need to work with getter\setter.
Other point of interest: indexerInvokation computation. We get SyntaxRoot too often, maybe you'll need some kind of cache.
Next: class and namespace search. I didn't find a method, but recommend not to find it: there can be properties, other indexers, anonymous methods used your indexers. If you don't really care about this - just find ancestors of type MethodDeclarationSyntax.
I am trying to add a static constructor using Mono Cecil to a program like the following:
namespace SimpleTarget
{
class C
{
public void M()
{
Console.WriteLine("Hello, World!");
}
}
}
The following code adds the static constructor:
namespace AddStaticConstructor
{
class Program
{
static void Main(string[] args)
{
var assemblyPath = args[0];
var module = ModuleDefinition.ReadModule(assemblyPath);
var corlib = ModuleDefinition.ReadModule(typeof(object).Module.FullyQualifiedName);
var method = corlib.Types.First(t => t.Name.Equals("Console")).Methods.First(m => m.Name.Contains("WriteLine"));
var methodToCall = module.Import(method);
foreach (var type in module.Types)
{
if (!type.Name.Contains("C")) continue;
var staticConstructorAttributes =
Mono.Cecil.MethodAttributes.Private |
Mono.Cecil.MethodAttributes.HideBySig |
Mono.Cecil.MethodAttributes.Static |
Mono.Cecil.MethodAttributes.SpecialName |
Mono.Cecil.MethodAttributes.RTSpecialName;
MethodDefinition staticConstructor = new MethodDefinition(".cctor", staticConstructorAttributes, module.TypeSystem.Void);
type.Methods.Add(staticConstructor);
type.IsBeforeFieldInit = false;
var il = staticConstructor.Body.GetILProcessor();
il.Append(Instruction.Create(OpCodes.Ret));
Instruction ldMethodName = il.Create(OpCodes.Ldstr, type.FullName);
Instruction callOurMethod = il.Create(OpCodes.Call, methodToCall);
Instruction firstInstruction = staticConstructor.Body.Instructions[0];
// Inserts the callOurMethod instruction before the first instruction
il.InsertBefore(firstInstruction, ldMethodName);
il.InsertAfter(ldMethodName, callOurMethod);
}
module.Write(assemblyPath);
}
}
}
Looking at the decompiled binary in dotPeek, it appears as if everything is setup correctly. When trying to use the modified C type, I get a TypeInitializationException with the inner exception "System.InvalidProgramException: JIT Compiler encountered an internal limitation"
Is there anything else I need to set correctly before using a static constructor?
Thanks!
The problem is that you are getting the wrong overload of System.WriteLine here:
var corlib = ModuleDefinition.ReadModule(typeof(object).Module.FullyQualifiedName);
var method = corlib.Types.First(t => t.Name.Equals("Console")).Methods.First(m => m.Name.Contains("WriteLine"));
var methodToCall = module.Import(method);
use this simple code the get the overload you want to use:
var wlMethod = typeof (Console).GetMethod(nameof(Console.WriteLine), new[] {typeof (string)});
var methodToCall = module.ImportReference(wlMethod);
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);
}