I'm having a strange problem that I can't fix. I have been compiling dynamic assemblies successfully for the most part but have come up with a strange problem compiling the following line:
return new JObject().Properties().ElementAt(0).Value();
with the error:
System.ApplicationException: 'Error creating dynamic code assembly 'IEnumerable<JProperty>' does not contain a definition for 'ElementAt' and no accessible extension method 'ElementAt' accepting a first argument of type 'IEnumerable<JProperty>' could be found (are you missing a using directive or an assembly reference?) '
The emitted text output works fine when created as a real class in the project but not when in a dynamic assembly. The project is an asp.net core 2.2 project and it references an assembly which creates the dynamic assemblies.
Here is the code that creates the assembly:
public static class Class2
{
public static Assembly GenerateAssenbly()
{
//generate the code
StringBuilder sb = new StringBuilder("");
sb.AppendLine("using System;");
sb.AppendLine("using System.Linq;");
sb.AppendLine("using Newtonsoft.Json;");
sb.AppendLine("using Newtonsoft.Json.Linq;");
sb.AppendLine("namespace test");
sb.AppendLine("{");
sb.AppendLine($"class Parser");
sb.AppendLine("{");
sb.AppendLine($"public object test() ");
sb.AppendLine("{");
sb.AppendLine("return new JObject().Properties().ElementAt(0).Value<string>();");
sb.AppendLine("}");
sb.AppendLine("}"); //class
sb.AppendLine("}"); //namespace
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sb.ToString());
var runtimeAssemblyDirectory = Path.GetDirectoryName(typeof(object).Assembly.Location);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "System.Runtime.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "System.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "netstandard.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "System.Core.dll")),
MetadataReference.CreateFromFile(typeof(JObject).GetTypeInfo().Assembly.Location),
};
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
Debug.Print(sb.ToString()); // copy output to a class to test
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
throw new ApplicationException($"Error creating dynamic code assembly " + GetCompilerResultsErrors(result));
}
else
{
return Assembly.Load(ms.GetBuffer());
}
}
}
private static string GetCompilerResultsErrors(EmitResult result)
{
StringBuilder sb = new StringBuilder();
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
sb.AppendLine(diagnostic.GetMessage());
}
return sb.ToString();
}
}
(the code shown is not intended for working purposes, it is simplified to demonstrate the problem)
Thanks in advance,
solution was to add specific references:
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "System.Linq.Expressions.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeAssemblyDirectory, "System.Linq.dll")),
Related
We are generating code dynamically to produce a .NET Core console application and then compiling it using:
var csharpParseOptions = new CSharpParseOptions(LanguageVersion.Latest);
csharpParseOptions = csharpParseOptions.WithPreprocessorSymbols(new[] { "TRACE", "DEBUG" });
var syntaxTree = CSharpSyntaxTree.ParseText(code, options: csharpParseOptions);
var compilationUnitSyntax = syntaxTree.GetCompilationUnitRoot();
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Debug, platform: Platform.X64)
.WithModuleName("TestConsole")
.WithMetadataImportOptions(MetadataImportOptions.All)
.WithDeterministic(true)
.WithConcurrentBuild(true);
var csharpCompilation = CSharpCompilation.Create(#"TestConsole", syntaxTrees: new[] { syntaxTree }, references: references, options: options);
We can then work without any problems against the generated assembly (in memory) obtained using:
using (var memoryStream = new MemoryStream())
{
var emitResult = csharpCompilation.Emit(memoryStream);
memoryStream.Position = 0;
_assembly = Assembly.Load(memoryStream.ToArray());
}
However, when we write the console.exe to disk using:
csharpCompilation.Emit(fileNameOnDisk, Path.Combine(Path.GetDirectoryName(fileNameOnDisk), Path.GetFileNameWithoutExtension(fileNameOnDisk)) + ".pdb");
and try to run it from there we get the following exception:
System.TypeLoadException: Could not load type 'System.Object' from assembly 'System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' because the parent does not exist.
Copying the same generated code (Program.cs) into an empty Console project works perfectly but we notice that the size of the executable is significantly larger.
Does anyone have any ideas how to go about fixing this problem? Thanks.
I think your problem is the lack of runtime configuration. From what I read above if you add a file named testconsole.runtimeconfig.json with the following info or similar:
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
You'll see that it runs. I'm also attaching a complete example with compilation.Emit that I validated in LINQPad. It does not require an additional file since it generates it in the example. Best of luck and hope it is still useful.
var tree = CSharpSyntaxTree.ParseText (#"class Program
{
static void Main() => System.Console.WriteLine (""Hello"");
}");
string trustedAssemblies = (string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES");
var trustedAssemblyPaths = trustedAssemblies.Split (Path.PathSeparator);
var references = trustedAssemblyPaths.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation
.Create ("test")
.WithOptions (new CSharpCompilationOptions (OutputKind.ConsoleApplication))
.AddSyntaxTrees (tree)
.AddReferences (references);
string outputPath = "test.dll";
Path.GetFullPath (outputPath).Dump();
EmitResult result = compilation.Emit (outputPath);
Console.WriteLine (result.Success);
File.WriteAllText ("test.runtimeconfig.json", #$"{{
""runtimeOptions"": {{
""tfm"": ""net{Environment.Version.Major}.{Environment.Version.Minor}"",
""framework"": {{
""name"": ""Microsoft.NETCore.App"",
""version"": ""{Environment.Version.Major}.{Environment.Version.Minor}.{Environment.Version.Build}""
}}
}}
}}");
// Execute the program we just compiled.
Util.Cmd (#"dotnet.exe", "\"" + Path.GetFullPath (outputPath) + "\"");
I have the following piece of code in .NET Framework 4.8,
sourceCode = $#"
using System;
{string.Format(Constants.Assembly.UsingDirective, Constants.Assembly.DapperNamespace)}
namespace {Constants.Assembly.DynamicTypeNamespace} {{
{sourceCode}
}}";
// Create Compilation Parameters
CompilerParameters compileParams = new CompilerParameters()
{
CompilerOptions = Constants.Assembly.CompileToLibrary,
GenerateInMemory = true
};
compileParams.ReferencedAssemblies.AddRange(baseAssemblyLocations.ToArray());
// Create Code Provider
CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<string, string>() {
{ Constants.Assembly.CompilerVersion, Constants.Assembly.CompilerVersion4 }
});
// Attempt compilation
CompilerResults compileResult = provider.CompileAssemblyFromSource(compileParams, sourceCode);
if (compileResult.Errors.Count > 0)
{
throw new Exception(compileResult.Errors[0].ErrorText);
}
// Store the assembly
Assembly = compileResult.CompiledAssembly;
I am looking into Roslyn APIs, but can't get it working using CSharpCompilationOptions.
How should I pass compilerParams, and sourceCode to the CSharpCompilationOptions?
How should I pass compilerParams
You don't, CompilerParameters is part of System.CodeDom.Compiler API, not a Roslyn API
To create a compilation you can use CSharpCompilation.Create:
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(
"compilation",
new[] {ParseText(sourceCode)},
GetGlobalReferences(),
options
);
Where ParseText is just:
SyntaxTree ParseText(string s)
{
return CSharpSyntaxTree.ParseText(s, new CSharpParseOptions(LanguageVersion.Latest));
}
And GetGlobalReferences is something like (rename and modify it to find all needed references):
private static PortableExecutableReference[] GetGlobalReferences()
{
var assemblies = new[]
{
typeof(object).Assembly,
typeof(Console).Assembly
};
var returnList = assemblies
.Select(a => MetadataReference.CreateFromFile(a.Location))
.ToList();
//The location of the .NET assemblies
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
/*
* Adding some necessary .NET assemblies
* These assemblies couldn't be loaded correctly via the same construction as above,
* in specific the System.Runtime.
*/
returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
return returnList.ToArray();
}
And then you should be able to emit assembly:
compilation.Emit("path_to_save");
I use similar code to unit test my pet project source generator.
I ask with example,
Lets say I have the following code.
fullcommand = #"public class oldTest
{
public static void oldTestMethod(){
Console.WriteLine(""oldTest Class"");
}
}"
var syntaxTree = CSharpSyntaxTree.ParseText(fullCommand);
var compilation = CSharpCompilation.Create(
assemblyName,
new[] {syntaxTree},
references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,allowUnsafe:true));
var ms = new MemoryStream();
var result = compilation.Emit(ms);
And I will compile the above code with Roslyn in memory.
next i want to compile another code in memory to use the above compiled class, lets say the below.
new_fullcommand = #"public class newTest
{
public static void newTest(){
oldTest.oldTestMethod();
}
}"
var syntaxTree = CSharpSyntaxTree.ParseText(new_fullcommand);
var compilation = CSharpCompilation.Create(
assemblyName,
new[] {syntaxTree},
references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,allowUnsafe:true));
var ms = new MemoryStream();
var result = compilation.Emit(ms);
how can i make the second code to use the first code as its reference? or use it?
The easiest way would probably be to pass in multiple syntaxTree objects when you create your compilation.
However, if you want to build up your compilation incrementally I believe you can use Compilation.AddSyntaxTrees to your first compilation object.
I am working on evaluating of user expressions from debugger. I want to compile expression in method context, and then inject IL-code with debugger.
Is it possible to compile expression, which contains non-public class/class-fields from external assembly to IL-code with Roslyn?
I've got 'MyNamespace.dll' with public class 'Test' and private method 'PrivateMethod', and I want to call it from Roslyn compilation.
I am trying to do it with next code:
public class TestCompilationOptions
{
public void Test()
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Output.dll");
Console.WriteLine("Preparing syntax tree");
string expressionString = #"
using System;
class XXX
{
public static void Main()
{
Console.WriteLine(MyNamespace.Test.PrivateMethod(2));
}
}";
//SyntaxTree targetTree = SyntaxFactory.ParseSyntaxTree(expressionString);
SyntaxTree targetTree = CSharpSyntaxTree.ParseText(expressionString);
Console.WriteLine("Preparing metadata references");
Assembly[] assemblys = new Assembly[4];
assemblys[0] = typeof(MyNamespace.Test).Assembly;
assemblys[1] = typeof(Console).Assembly;
assemblys[2] = typeof(object).Assembly;
assemblys[3] = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), "System.Runtime.dll"));
MetadataReference[] metadataReferences = MetadataFromAssembly(assemblys);
Console.WriteLine("Preparing default namespaces");
IEnumerable<string> DefaultNamespaces = new[] {"System", "System.Runtime"};
Console.WriteLine("Preparing compilation options");
CSharpCompilationOptions ReleaseDll = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release);
CSharpCompilationOptions cOptions = ReleaseDll.WithUsings(DefaultNamespaces);
//.WithMetadataImportOptions(MetadataImportOptions.All);
Console.WriteLine("Getting compilation");
CSharpCompilation compilation = CSharpCompilation.Create("Output.dll", new SyntaxTree[] {targetTree}, metadataReferences, cOptions);
Console.WriteLine("Emitting compilation");
using (var dll = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
var emitRes = compilation.Emit(dll);
if (!emitRes.Success)
{
Console.WriteLine("Emited unsuccessfully!");
foreach (var d in emitRes.Diagnostics)
Console.WriteLine(d.ToString());
return;
}
}
}
public unsafe MetadataReference[] MetadataFromAssembly(Assembly[] assemblys)
{
MetadataReference[] result = new MetadataReference[assemblys.Length];
byte *b;
int length;
for (int i = 0; i < assemblys.Length; i++)
{
if (assemblys[i].TryGetRawMetadata(out b, out length))
{
var moduleMetadata = ModuleMetadata.CreateFromMetadata((IntPtr) b, length);
var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
result[i] = assemblyMetadata.GetReference();
}
else
{
return null;
}
}
return result;
}
And got following error:
(8,44): error CS0117: 'Test' does not contain a definition for 'privateMember'
I've made 'WithMetadataImportOptions' and 'MetadataImportOptions' public inside Roslyn and uncomennted line
//.WithMetadataImportOptions(MetadataImportOptions.All);
And then got following error:
(8,44): error CS0122: 'Test.privateMember' is inaccessible due to its protection level
So may be it could be done using some Roslyn API?
P.S.
I know, that I can get non-public fields symbols using System.Reflection, but how do I compile the expression then?
If a member is private, you can't access it with normal code in another class. Nothing to do with Roslyn in particular.
If you really do actually need to access a private member in a different class, and you fully understand why it may not be a good idea, the code that accesses it must do so using reflection.
I have some types whose objects i need to pass to a dynamic assembly. But my types are not recognised and i am getting compile errors
Here is my code to compile
using System.Linq;
using System.Text;
using System.Collections.Generic;
using SkillBuilder.AutoGens.Libs;
namespace TempNs
{
public class MyClass
{
public MyClass()
{}
public Autozen ag;
//do stuff
}
}
The above code throws error saying that type Autozen could not be found. Are you missing an Assembly reference?. The type is in SkillBuilder.AutoGens.Libs.
This is how i compile it.
public static Assembly Compile(string sourceCode)
{
var provider_options = new Dictionary<string, string>
{
{"CompilerVersion","v4.0"}
};
CodeDomProvider cpd = new CSharpCodeProvider(provider_options);
var cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.Dll");
// True - memory generation, false - external file generation
cp.GenerateInMemory = true;
cp.GenerateExecutable = false;
CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);
if (cr.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in cr.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
return cr.CompiledAssembly;
}
I want to be able to assign the value of "ag" by the calling application.
How do i make Autozen available during compile time ? Is there any other way?
EDIT :
The code is generated and compiled by an EXE. The compiled output is again going to be used by the same EXE.
thanks for your help.
EDIT :
After adding Assembly.GetExecutingAssembly().Location to the referenced assembly collection now i get System.Core.dll missing referece error. Since i have included CompilerVersion, it should pickup the assembly from the GAC. Then why am i still getting this error ?