Up until now, we’ve mostly looked at how we can use Roslyn to analyze and manipulate source code. Now we’ll take a look at finishing the compilation process by emitting it disk or to memory. To start, we’ll just try emitting a simple compilation to disk and checking whether or not it succeeded.
This file contains 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
var tree = CSharpSyntaxTree.ParseText(@" | |
using System; | |
public class C | |
{ | |
public static void Main() | |
{ | |
Console.WriteLine(""Hello World!""); | |
Console.ReadLine(); | |
} | |
}"); | |
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { mscorlib }); | |
//Emitting to file is available through an extension method in the Microsoft.CodeAnalysis namespace | |
var emitResult = compilation.Emit("output.exe", "output.pdb"); | |
//If our compilation failed, we can discover exactly why. | |
if(!emitResult.Success) | |
{ | |
foreach(var diagnostic in emitResult.Diagnostics) | |
{ | |
Console.WriteLine(diagnostic.ToString()); | |
} | |
} |
After running this code we can see that our executable and .pdb
have been emitted to Debug/bin/
. We can double click output.exe
and see that our program runs as expected. Keep in mind that the .pdb
file is optional. I’ve only chosen to emit it here to show off the API. Writing the .pdb
file to disk can take a fairly long time and it often pays to omit this argument unless you really need it.
Sometimes we might not want to emit to disk. We might just want to compile the code, emit it to memory and then execute it from memory. Keep in mind that for most cases where we’d want to do this, the scripting API probably makes more sense to use. Still, it pays to know our options.
This file contains 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
var tree = CSharpSyntaxTree.ParseText(@" | |
using System; | |
public class MyClass | |
{ | |
public static void Main() | |
{ | |
Console.WriteLine(""Hello World!""); | |
Console.ReadLine(); | |
} | |
}"); | |
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { mscorlib }); | |
//Emit to stream | |
var ms = new MemoryStream(); | |
var emitResult = compilation.Emit(ms); | |
//Load into currently running assembly. Normally we'd probably | |
//want to do this in an AppDomain | |
var ourAssembly = Assembly.Load(ms.ToArray()); | |
var type = ourAssembly.GetType("MyClass"); | |
//Invokes our main method and writes "Hello World" 🙂 | |
type.InvokeMember("Main", BindingFlags.Default | BindingFlags.InvokeMethod, null, null, null); |
Finally, what if we want to influence how our code is compiled? We might want to allow unsafe code, mark warnings as errors or delay sign the assembly. All of these options can be customized by passing a CSharpCompilationOptions
object to CSharpCompilation.Create()
. We’ll take a look at how we can interact with a few of these properties below.
This file contains 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
var tree = CSharpSyntaxTree.ParseText(@" | |
using System; | |
public class MyClass | |
{ | |
public static void Main() | |
{ | |
Console.WriteLine(""Hello World!""); | |
Console.ReadLine(); | |
} | |
}"); | |
//We first have to choose what kind of output we're creating: DLL, .exe etc. | |
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication); | |
options = options.WithAllowUnsafe(true); //Allow unsafe code; | |
options = options.WithOptimizationLevel(OptimizationLevel.Release); //Set optimization level | |
options = options.WithPlatform(Platform.X64); //Set platform | |
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, | |
references: new[] { mscorlib }, | |
options: options); //Pass options to compilation |
In total there are about twenty-five different options available for customization. Basically any option you have within the Visual Studio’s project property page should be available here.
Advanced options
There are a few optional parameters available in Compilation.Emit()
that are worth discussing. Some of them I’m familiar with, but others I’ve never used.
xmlDocPath
– Auto generates XML documentation based on the documentation comments present on your classes, methods, properties etc.manifestResources
– Allows you to manually embed resources such as strings and images within the emitted assembly. Batteries are not included with this API and it requires some heavy lifting if you want to embed.resx
resources within your assembly. We’ll explore this overload in a future blog post.win32ResourcesPath
– Path of the file from which the compilation’s Win32 resources will be read (in RES format). Unfortunately I haven’t used this API yet and I’m not at all familiar with Win32 Resources.- There is also the option to
EmitDifference
between two compilations. I’m not familiar with this API, and I’m not familiar with how you can apply these deltas to existing assemblies on disk or in memory. I hope to learn more about this API in the coming months.
That just about wraps up the Emit API. If you have any questions, feel free to ask them in the comments below.
Nice post as always.
The EmitDifference is how Edit And Continue works. In general, you need to create a baseline compilation and then you can get the deltas between the baesline and the current state.
Thanks 🙂
RE: EmitDifference: I can create the baseline and I think the EmitDifference, but I’m unsure of how I can apply this to an existing compilation either on disk or in memory. Do you have experience with this?
I can send you example but I think the best examples is here: https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs
Hope its help:)
Thanks for the examples. I’ve looked at them closely, but I don’t think they allow users to apply changes to existing DLLs. They emit the differences as two delta streams, one for IL and one for PDBs which are then passed to the CLR. The CLR does the heavy lifting here and applies the deltas to the existing process.
The interface VS’s debugger uses: https://msdn.microsoft.com/en-us/library/ms231880%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
The implementation in CoreCLR: https://github.com/dotnet/coreclr/blob/bc146608854d1db9cdbcc0b08029a87754e12b49/src/debug/di/module.cpp#L2157
Great articles! I was curious if there was a way to Emit to memory streams like:
using( var dllStream = new System.IO.MemoryStream() )
using( var pdbStream = new System.IO.MemoryStream() )
{
var result = compilation.Emit( dllStream, pdbStream );
and then load the new assembly and pdb:
var assembly = System.Reflection.Assembly.Load( dllStream.ToArray(), pdbStream.ToArray() );
When I do this the assembly loads, but Visual Studio’s output logs:
Loaded ‘Test.dll’. Cannot find or open the PDB file.
Is there something I’m doing wrong, or is it just not possible to do this?
It should be possible. The compilation is succeeding in your example? (No compilation errors or anything?)
Oddly it is still logging the same error, but it seems to be working and I’m able to set breakpoints in the test file. I was passing in the wrong Text.Encoding to ParseText() at first (I was passing in UTF8 instead of ASCII) and that was causing the debugger to think the file had changed. So it seems to be working fine now except for the VS log that claims it cannot find or open the PDB file.
One last question. Do you know if there is a way to enable Edit & Continue when loading using Assembly.Load?
Sorry I just noticed your last comment today. EnC as I understand requires two processes. One (the debugger process) must pause the other’s CLR so code stops being executed and instead tell the CLR to load new code before telling it to continue. Is this how you’re trying to do things with Assembly.Load?
I’m not aware of any restrictions on assemblies loaded with Assembly.Load. In fact, that’s how I’m using EnC. Are you seeing errors?
I’m doing a CSharpCompilation.Emit() into a memory stream and then a Assembly.Load() on that. The debugger will hit breakpoints in the file loaded by the assembly load, but EnC does not occur if I make a change and then step. The (older) docs seem to mention it not working when using ‘Attach To’ (vs. starting the application in VS), so I’m guessing it’s something along those lines. I’ll have to try EnC on an assembly built normally, but loaded with Assembly.Load.
Nice examples.
There is a typo is in the first main: Madin
Thanks! I’ve fixed that example.
error CS0103: The name ‘Console’ does not exist in the current context
on console.writeline() and console.readline()
why do you think this is happening?
Make sure Console and WriteLine are capitalized properly. Also make sure that you have a using System; at the top of your syntax tree.
At first – Great articles!!
Is it possible to compile code dynamically from a signed assembly and run this code from the signed assembly? Has the compiled code access to the code of another signed assembly in my infrastructure?
Can I call the signed Assembly code from my dynamically generated code?
Questions about questions 😉
Thanks a lot for your time and your help.
I don’t know whether it’s bug or not.
When we generate class using Roslyn and build it into a dll, then we consume that dll in another project, IF we use a decimal (System.Decimal) type inside our generated class, then when we compile that another project, it always give this error:
The type ‘Decimal’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e’.
any clue?
Are you targeting UWP? I have never targeted it myself or seen that error. Here are two links regarding that DLL:
https://stackoverflow.com/questions/50010855/could-not-load-file-or-assembly-system-private-corelib
https://blogs.msdn.microsoft.com/appconsult/2018/05/15/could-not-load-file-or-assembly-system-private-corelib/
If you can provide a minimum repro they may be able to help you directly at the Roslyn GitHub: https://github.com/dotnet/roslyn
I keep getting System.BadImageFormatException : Bad IL format. Dow anyone have any clue of what’s wrong?
I’m using roslyn (Microsoft.CodeAnalysis.CSharp, Microsoft.CodeAnalysis.CSharp.Workspaces) in a .NET Framework v4.6.2 console application. I dynamically create the syntax tree and build it (Emit) into an assembly. This assembly target framework and version was the same as the console application framework and version so it was quite straightforward to do that. That worked without issues.
Now, in this very console application, using the same logic I want to build (Emit) an assembly targeting UWP. I’m completely stuck. Somewhere I found a few hints to use a TargetFrameworkAttribute. I tried that. I was even able to generate an assembly targeting .NET Standard by adding [assembly: TargetFramework(“netstandard2.0”)] but that is not what I need. That only proved that TargetFrameworkAttribute works at least in some cases. I particularly need to target UWP. I tried “uap”, “uap10.0”, “netcore50”, “.NETCore,Version=v5.0”, “win10” as the TargetFrameworkAttribute string. Nothing gives me the desired result.
I found another hint that most likely I need to specify RuntimeMetadataVersion for the Emit in the EmitOptions but I have no idea what to specify there. Also, I don’t fully understand what to do with the references. For the .NET Framework I specified “mscorlib.dll”, “System.Core.dll”, “Serialize.Linq.dll”. That was all that I needed for my dynamically generated code. It seems that for UWP I should not specify “mscorlib.dll”. I analyzed other UWP assemblies, none of them have a direct reference to “mscorlib.dll”. But if I don’t specify it I get an error that I have to specify RuntimeMetadataVersion. For the UWP I tried this list of references “UAP\System.Runtime.dll”, “UAP\System.Collections.dll”, “UAP\Serialize.Linq.Universal.dll”. Basically, I used a list that I found in another UWP assembly with the similar code.
Can anybody share an example how to use Roslyn and generate UWP assembly when the Roslyn is used in a .NET Framework console application?