Roslyn scripting on CoreCLR (.NET CLI and DNX) and in memory assemblies

For a while now, the Roslyn C# scripting APIs (Microsoft.CodeAnalysis.CSharp.Scripting) have been portable, and supported cross platform usage.

However, I recently ran into a few difficulties regarding using the Roslyn Scripting APIs in .NET CLI (which is replacing DNX) context. The solution was to use a lower level unmanaged CoreCLR API – and since they it’s not that well documented, I thought it would be beneficial to document it in a blog post.

The Problem

The scripting APIs generally work just fine when you try to use them against .NET Core targets.

The problem arises when you are trying to pass in a reference to the current application assembly to your script. This can be needed for a number of reasons – for example you may want to use the results of the scripting operations in your main program or simply use the types from your main program inside the scripting code.

Roslyn provides you two ways of doing this if you have an instance of an Assembly object:

  • using the regular MetadataReference.CreateFromAssembly API – unfortunately that will only work on assemblies that have a physical, accessible location (the Location property of an assembly object is not null or empty)
  • for in memory assemblies you can use the InteractiveAssemblyLoader APIs, but that only supports dekstop at the moment (not CoreCLR)

The example below shows the approach with InteractiveAssemblyLoader in play (suitable for desktop usage, i.e. DNX against Desktop CLR):

Now, a DNX program, or a .NET CLI program, running against CoreCLR, will potentially exist only in memory with no physical Assembly.Location – making neither of the above approaches usable.

DNX also offers an ILibraryExporter platform service, which can be used for grabbing MetadataReferences. There is however a more general solution, that can be generally applied to obtain MetadataReference from any Assembly instance – no matter who emitted it, and whether it exists on disk or just in memory.

The Solution

Last summer CoreCLR introduced a new API, surfaced via System.Runtime.Loader package, allowing us to grab the metadata section (the format is defined here in ECMA-335) of an assembly via an instance of that assembly. It is just the metadata, not the full PE image, but Roslyn doesn’t need full PE image to provide a reference to the scripting APIs.

The example for .NET CLI is shown below. Since the CoreCLR API uses unmanaged code, it’s necessary to use unsafe modifier from the C# code (unsafe compilation needs to be enabled in your project.json file too).

So we grab the current assembly, then use the new CoreCLR TryGetRawMetadata method to get the metadata from it, and then we build up a MetadataReference object which Roslyn can use in its scripting APIs. This is very powerful as it opens up a lot of interesting avenues in .NET CLI scripting – allowing your script to interact with the host application, even if the host application exists only in memory or consume in memory assemblies which can be dynamically emitted by anyone, at any point!

You can even use the object from the .NET CLI in memory application as a host (global) scripting type and exchange the information between the script and the main app this way. Here is the above code modified to use a host object from the parent in memory application:

For the record, here is the project.json used against the sample code used here:

UWP Limitations

While it’s not really related to the rest of this post, it is worth noting that Roslyn scripting APIs will not work in UWP (Universal Windows Platform) applications. That’s just the limitation of the platform itself – UWP apps use .NET Native, which is ahead of time compilation. Since there is no JIT at runtime (no runtime code generation), there is no way to support C# scripting there.

Be Sociable, Share!

  • BSalita

    Ah, thanks for the UWP Limitations comment. I was actually hoping you would head there.

    • http://www.strathweb.com/ Filip W

      np

  • Jared Thirsk

    Have you gotten providing global variables to work? With .NET Core 1.0 and Microsoft.CodeAnalysis.CSharp.Scripting 1.3.2, I am getting this, even if I try to reference System.Runtime:

    Unhandled Exception: Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,1): error CS0012: The type ‘Object’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘System.Runtime, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.
    at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, DiagnosticFormatter formatter)
    at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, CancellationToken cancellationToken)
    at Microsoft.CodeAnalysis.Scripting.Script1.GetExecutor(CancellationToken cancellationToken)
    at Microsoft.CodeAnalysis.Scripting.Script
    1.RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken)
    at ConsoleApplication.Program.Main(String[] args)

    I tried referencing System.Runtime a few ways but still no dice.

    • Alan

      I get the same error when I try to call a function. Without the function it runs.

      var scriptSrc = @”var foo = new TestClass(); foo.Tester()”;

  • aggieben

    Have you tried supporting #r directives? I’m working on a tool that needs to support it, and I’m not having much luck.