I’m using Roslyn CSharpCompilation to generate dll files for my plugins – files have OptimizationLevel.Debug and pdb file is generated. Next I’m loading those files to my program (UWP + .NET Standard 2.0 libs) using Assembly.Load and create instance of types I’m interested in. My problem is that I can’t get Visual Studio (version 2017 15.7.3) to find source code when I’m debugging – it is treading it like external library, so when exception is thrown inside I can't find where. I have tired to search solution on stackoverflow but all solution are not working. I have checked this:
Pdb is generated
Module window in VS shows that symbols are loaded
Tried different version of Assembly Load/LoadFrom
Setting “Use
Managed Compatibility Mode” in debug options
Is there any way to make the file debuggable? Maybe I have to use some roslyn option while compiling or change something in VS?
The code sample below should help you on your way. It`s based on the code generation part of thlamare IOC container lamar, the successor of StructureMap made by Jeremy D Miller.
I have only added debugging capabilities. The trick was to make the source text embeddable, choosing the right formats, and setting encoding values where needed.
Check out the original work for more details of e.g. adding references.
public Assembly CreateAssembly(string code)
{
var encoding = Encoding.UTF8;
var assemblyName = Path.GetRandomFileName();
var symbolsName = Path.ChangeExtension(assemblyName, "pdb");
var sourceCodePath = "generated.cs";
var buffer = encoding.GetBytes(code);
var sourceText = SourceText.From(buffer, buffer.Length, encoding, canBeEmbedded: true);
var syntaxTree = CSharpSyntaxTree.ParseText(
sourceText,
new CSharpParseOptions(),
path: sourceCodePath);
var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode;
var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, sourceCodePath, encoding);
var optimizationLevel = OptimizationLevel.Debug;
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { encoded },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOptimizationLevel(optimizationLevel)
.WithPlatform(Platform.AnyCpu)
);
using (var assemblyStream = new MemoryStream())
using (var symbolsStream = new MemoryStream())
{
var emitOptions = new EmitOptions(
debugInformationFormat: DebugInformationFormat.PortablePdb,
pdbFilePath: symbolsName);
var embeddedTexts = new List<EmbeddedText>
{
EmbeddedText.FromSource(sourceCodePath, sourceText),
};
EmitResult result = compilation.Emit(
peStream: assemblyStream,
pdbStream: symbolsStream,
embeddedTexts: embeddedTexts,
options: emitOptions);
if (!result.Success)
{
var errors = new List<string>();
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
throw new Exception(String.Join("\n", errors));
}
Console.WriteLine(code);
assemblyStream.Seek(0, SeekOrigin.Begin);
symbolsStream?.Seek(0, SeekOrigin.Begin);
var assembly = AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream);
return assembly;
}
}
Usage:
[Test]
public void Verify()
{
var code =
#"namespace Debuggable
{
public class HelloWorld
{
public string Greet(string name)
{
var result = ""Hello, "" + name;
return result;
}
}
}
";
var codeGenerator = new CodeGenerator();
var assembly = codeGenerator.CreateAssembly(code);
dynamic instance = assembly.CreateInstance("Debuggable.HelloWorld");
// Set breakpoint here
string result = instance.Greet("Roslyn");
result.Should().Be("Hello, Roslyn");
}
Related
I'm writing a project that dynamically compiles and executes c# code. The problem is that sometimes I want the code to call another DLL (for the sake of this sample I called it "ANOTHER.DLL"). It works fine in .Net 4.5, but fails in .Net Core and I can't figure out why. Any help is appreciated!
Code compiles successfully, but gives an error when the method is executed. Error is:
FileNotFoundException: Could not load file or assembly 'ANOTHER,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system
cannot find the file specified.
The ANOTHER.dll is located in the same /bin/debug folder, and is definitely accessible (code compiles!)
I noticed I can fix the issue by adding reference to ANOTHER.DLL to the project, but it defeats the purpose of dynamic compilation.
I tried this in .Net Core 2.0 - 3.1
ANOTHER.DLL is .Net Standard 2.0 (but same result with .Net Standard 2.1, or .Net Framework).
Also tried various versions of Microsoft.CodeAnalysis package, all giving me same error.
var eval = new Evaluator();
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List < MetadataReference > references = new List < MetadataReference > ();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
string ReferenceList = "";
ReferenceList += "netstandard.dll\n";
ReferenceList += "System.Runtime.dll\n";
ReferenceList += "ANOTHER.dll\n";
string[] assemblies = ReferenceList.Split('\n');
foreach(string a in assemblies) {
if (File.Exists(Path.Combine(assemblyPath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, a.Trim())));
}
else if (File.Exists(a.Trim())) {
string currDirectory = Directory.GetCurrentDirectory();
references.Add(MetadataReference.CreateFromFile(Path.Combine(currDirectory, a.Trim())));
}
else {
string exepath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (File.Exists(Path.Combine(exepath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(exepath, a.Trim())));
}
}
}
CSharpCompilation compilation = CSharpCompilation.Create("assembly", syntaxTrees: new[] {
syntaxTree
},
references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
Assembly assembly;
using(var ms = new MemoryStream()) {
EmitResult result = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(ms.ToArray());
}
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x =>x.Name == "Hello");
var info = all.FirstOrDefault(x =>x.DeclaringType == type) ? ?all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] {}); // this is where the error occurs
Solution is based on my gist
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
var tree = SyntaxFactory.ParseSyntaxTree(code);
string fileName = "mylib.dll";
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "netstandard.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
var anotherDLLReference = MetadataReference.CreateFromFile(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
references.Add(anotherDLLReference);
var compilation = CSharpCompilation.Create(fileName)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(references)
.AddSyntaxTrees(tree);
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
EmitResult compilationResult = compilation.Emit(path);
if (compilationResult.Success)
{
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x => x.Name == "Hello");
var info = all.FirstOrDefault(x => x.DeclaringType == type) ?? all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] { });
}
}
}
}
To be fair, I have 0 idea how it works, since I am not familiar with working with assemblies on this level, but somehow I managed to get rid of exception.
Firstly, I checked AssemblyLoadContext.Default in the debugger. I noticed that reference to "ANOTHER.dll" is missing (although we previously added it)
Then I added AssemblyLoadContext.Default.LoadFromAssemblyPath(#"path to my ANOTHER.dll");. And when I checked it again - ANOTHER.dll was there.
Finally, we can see our hello world message
So the code I added is basically one line
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
var type = assembly.GetType("RoslynCompileSample.Test");
This works with both ANOTHER.dll targeting Standard 2.0 and .NET Core 3.1
Would be nice if someone smart actually told how it works.
I am trying to put all the codes that is needed to create an ASP.NetCore Web Application in a text file and then read it in an ASP.Net Core 3.1 application and compile it using Roslyn and save it as a dll file.
I tried a lot. I could do that for a Console application but not for a web application.
This is what I did
public void Load(string id, string code, IEnumerable<string> allowedAssemblyNames, IEnumerable<Type> allowedTypes, string path)
{
try
{
var _references = new List<MetadataReference>();
foreach (var assemblyName in allowedAssemblyNames)
{
_references.Add(MetadataReference.CreateFromFile(RuntimeEnvironment.GetRuntimeDirectory() + assemblyName + ".dll"));
}
foreach (var type in allowedTypes)
{
_references.Add(MetadataReference.CreateFromFile(type.Assembly.Location));
}
_references.Add(MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location));
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
reportSuppressedDiagnostics: true,
optimizationLevel: OptimizationLevel.Release,
generalDiagnosticOption: ReportDiagnostic.Error,
allowUnsafe: false);
var syntaxTree = CSharpSyntaxTree.ParseText(code, options: new CSharpParseOptions(LanguageVersion.Latest, kind: SourceCodeKind.Regular));
var compilation = CSharpCompilation.Create(id, new[] { syntaxTree }, _references, options);
assemblyLoadContext = new AssemblyLoadContext(id, true);
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
bytes = ms.ToArray();
File.WriteAllBytes(path, bytes);
ms.Seek(0, SeekOrigin.Begin);
assembly = assemblyLoadContext.LoadFromStream(ms);
}
}
}
catch (Exception ex)
{
throw;
}
}
Is that possible at all?
After spending some time on it, based on a discussion on GitHub, it seems it is not possible to do that.
The answer given was:
I believe it's considered unsupported by .NET Core to compile directly
against System.Private.CoreLib.dll. In general, it will probably be
quite difficult to compile an application without using the dotnet
tool and MSBuild, as you will have to effectively replicate all the
logic that goes into picking the appropriate reference assemblies,
then generating the right executing environment for the .NET Core
shared loader.
I'm a beginner of roslyn, so I tried to start learning it by making a very simple console application, which is introduced in the famous tutorial site. (https://riptutorial.com/roslyn/example/16545/introspective-analysis-of-an-analyzer-in-csharp), and it didn't work well.
The Cosole Application I made is of .NET Framework (target Framework version is 4.7.2), and not of .NET Core nor .NET standard.
I added the NuGet package Microsoft.CodeAnalysis, and Microsoft.CodeAnalysis.Workspaces.MSBuild, then wrote a simple code as I show below.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;
namespace SimpleRoslynConsole
{
class Program
{
static void Main(string[] args)
{
// Declaring a variable with the current project file path.
// *** You have to change this path to fit your development environment.
const string projectPath =
#"C:\Users\[MyName]\Source\Repos\RoslynTrialConsole01\RoslynTrialConsole01.csproj";
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
// [**1]Getting the compilation.
var compilation = project.GetCompilationAsync().Result;
// [**2]As this is a simple single file program, the first syntax tree will be the current file.
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
if (syntaxTree != null)
{
var rootSyntaxNode = syntaxTree.GetRootAsync().Result;
var firstLocalVariablesDeclaration = rootSyntaxNode.DescendantNodesAndSelf()
.OfType<LocalDeclarationStatementSyntax>().First();
var firstVariable = firstLocalVariablesDeclaration.Declaration.Variables.First();
var variableInitializer = firstVariable.Initializer.Value.GetFirstToken().ValueText;
Console.WriteLine(variableInitializer);
}
else
{
Console.WriteLine("Could not get SyntaxTrees from this projects.");
}
Console.WriteLine("Hit any key.");
Console.ReadKey();
}
}
}
My problem is that, SyntaxTrees property of Compilation object returns null in [**2]mark. Naturally, following FirstOrDefault method returns null.
I've tried several other code. I found I could get SyntaxTree from CSharp code text, by using CSharpSyntaxTree.ParseText method. But I couldn't get any from source code, by the sequence of
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
var compilation = project.GetCompilationAsync().Result;
What I'd like to know is if I miss something to get Syntax information from source code by using above process.
I'll appreciate someone give me a good advice.
I think the issue is that .net framework projects have their source files paths within their .csproj. And opening project works right away.
For .net core project you have no such information and, maybe, this is why Workspace instance doesn't know what to load and so loads nothing.
At least specifying .cs files as added documents does the trick. Try to apply this:
static class ProjectExtensions
{
public static Project AddDocuments(this Project project, IEnumerable<string> files)
{
foreach (string file in files)
{
project = project.AddDocument(file, File.ReadAllText(file)).Project;
}
return project;
}
private static IEnumerable<string> GetAllSourceFiles(string directoryPath)
{
var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
return res;
}
public static Project WithAllSourceFiles(this Project project)
{
string projectDirectory = Directory.GetParent(project.FilePath).FullName;
var files = GetAllSourceFiles(projectDirectory);
var newProject = project.AddDocuments(files);
return newProject;
}
}
Method WithAllsourceFiles will return you the project, compilation of which will in its turn have all syntax trees you would expect of it, as you would have in Visual Studio
MsBuildWorkspace won't work correctly unless you have all the same redirects in your app's app.config file that msbuild.exe.config has in it. Without the redirects, it's probably failing to load the msbuild libraries. You need to find the msbuild.exe.config file that is on your system and copy the <assemblyBinding> elements related to Microsoft.Build assemblies into your app.config. Make sure you place them under the correct elements configuration/runtime.
I searched various sample programs on the net and found the most reliable and safest method. The solution is to create a static method which returns SyntaxTrees in designated File as follow.
private static Compilation CreateTestCompilation()
{
var found = false;
var di = new DirectoryInfo(Environment.CurrentDirectory);
var fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
while ((fi == null) || (di.Parent == null))
{
di = new DirectoryInfo(di.Parent.FullName);
fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
if (fi != null)
{
found = true;
break;
}
}
if (!found)
{
return null;
}
var targetPath = di.FullName + #"\Program.cs";
var targetText = File.ReadAllText(targetPath);
var targetTree =
CSharpSyntaxTree.ParseText(targetText)
.WithFilePath(targetPath);
var target2Path = di.FullName + #"\TypeInferenceRewriter.cs";
var target2Text = File.ReadAllText(target2Path);
var target2Tree =
CSharpSyntaxTree.ParseText(target2Text)
.WithFilePath(target2Path);
SyntaxTree[] sourceTrees = { programTree, target2Tree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(
OutputKind.ConsoleApplication));
}
And the caller program will be like this.
static void Main(string[] args)
{
var test = CreateTestCompilation();
if (test == null)
{
return;
}
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
Console.WriteLine(souceTree.ToFullString());
}
}
Of course, many improvements are needed to put it to practical use.
Is it possible to compile and run C# code at runtime in the new .NET Core (better .NET Standard Platform)?
I have seen some examples (.NET Framework), but they used NuGet packages that are not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0)
Option #1: Use the full C# compiler to compile an assembly, load it and then execute a method from it.
This requires the following packages as dependencies in your project.json:
"Microsoft.CodeAnalysis.CSharp": "1.3.0-beta1-20160429-01",
"System.Runtime.Loader": "4.0.0-rc2-24027",
Then you can use code like this:
var compilation = CSharpCompilation.Create("a")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(
#"
using System;
public static class C
{
public static void M()
{
Console.WriteLine(""Hello Roslyn."");
}
}"));
var fileName = "a.dll";
compilation.Emit(fileName);
var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(fileName));
a.GetType("C").GetMethod("M").Invoke(null, null);
Option #2: Use Roslyn Scripting. This will result in much simpler code, but it currently requires more setup:
Create NuGet.config to get packages from the Roslyn nightly feed:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="Roslyn Nightly" value="https://www.myget.org/F/roslyn-nightly/api/v3/index.json" />
</packageSources>
</configuration>
Add the following package as a dependency to project.json (notice that this is package from today. You will need different version in the future):
"Microsoft.CodeAnalysis.CSharp.Scripting": "1.3.0-beta1-20160530-01",
You also need to import dotnet (obsolete "Target Framework Moniker", which is nevertheless still used by Roslyn):
"frameworks": {
"netcoreapp1.0": {
"imports": "dotnet5.6"
}
}
Now you can finally use Scripting:
CSharpScript.EvaluateAsync(#"using System;Console.WriteLine(""Hello Roslyn."");").Wait();
I am just adding to svick's answer. If you want to keep the assembly in memory (rather than writing to a file) you can use the following method:
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);
This is different than in .NET 4.5.1 where the code is:
Assembly assembly = Assembly.Load(ms.ToArray());
My code targets both .NET 4.5.1 and .NET Standard, so I had to use directives to get around this problem. The full code example is here:
string code = CreateFunctionCode();
var syntaxTree = CSharpSyntaxTree.ParseText(code);
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Hashtable).GetTypeInfo().Assembly.Location)
};
var compilation = CSharpCompilation.Create("Function.dll",
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
StringBuilder message = new StringBuilder();
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)
{
message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
return new ReturnValue<MethodInfo>(false, "The following compile errors were encountered: " + message.ToString(), null);
}
else
{
ms.Seek(0, SeekOrigin.Begin);
#if NET451
Assembly assembly = Assembly.Load(ms.ToArray());
#else
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);
#endif
Type mappingFunction = assembly.GetType("Program");
_functionMethod = mappingFunction.GetMethod("CustomFunction");
_resetMethod = mappingFunction.GetMethod("Reset");
}
}
Both previous answers didn't work for me in a .NET Core 2.2 environment on Windows. More references are needed.
So with the help of the https://stackoverflow.com/a/39260735/710069 solution, I have ended up with this code:
var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
var compilation = CSharpCompilation.Create("LibraryName")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(
#"public static class ClassName
{
public static void MethodName() => System.Console.WriteLine(""Hello C# Compilation."");
}"));
// Debug output. In case your environment is different it may show some messages.
foreach (var compilerMessage in compilation.GetDiagnostics())
Console.WriteLine(compilerMessage);
Than output library to file:
var fileName = "LibraryName.dll";
var emitResult = compilation.Emit(fileName);
if (emitResult.Success)
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(fileName));
assembly.GetType("ClassName").GetMethod("MethodName").Invoke(null, null);
}
or to memory stream:
using (var memoryStream = new MemoryStream())
{
var emitResult = compilation.Emit(memoryStream);
if (emitResult.Success)
{
memoryStream.Seek(0, SeekOrigin.Begin);
var context = AssemblyLoadContext.Default;
var assembly = context.LoadFromStream(memoryStream);
assembly.GetType("ClassName").GetMethod("MethodName").Invoke(null, null);
}
}
I'm using Roslyn to emit a CSharpCompilation object in Visual Studio to a file. The DLL that is generated does not contain any assembly info other than the assembly metadata, and I'd like to add the version and sign it if possible. How can these be done with Roslyn?
You need to include source code which sets the Assembly* attributes just like in the VS C# project templates. If you have done that, the .NET version info is set. You can read that information with Reflection or tools like ILSpy.
That way Explorer won't show any version info in its property page. Explorer is only showing Win32 VersionInfo not .NET version info. You need to emit Win32 resource code with Rosyln to set these values. Luckily there's a method to auto generate the Win32 info from the .NET ones: CreateDefaultWin32Resources.
Here's a complete and working code sample:
public void VersionInfoExample()
{
// 1. Generate AssemblyInfo.cs-like C# code and parse syntax tree
StringBuilder asmInfo = new StringBuilder();
asmInfo.AppendLine("using System.Reflection;");
asmInfo.AppendLine("[assembly: AssemblyTitle(\"Test\")]");
asmInfo.AppendLine("[assembly: AssemblyVersion(\"1.1.0\")]");
asmInfo.AppendLine("[assembly: AssemblyFileVersion(\"1.1.0\")]");
// Product Info
asmInfo.AppendLine("[assembly: AssemblyProduct(\"Foo\")]");
asmInfo.AppendLine("[assembly: AssemblyInformationalVersion(\"1.3.3.7\")]");
var syntaxTree = CSharpSyntaxTree.ParseText(asmInfo.ToString(), encoding: Encoding.Default);
// 2. Create compilation
string mscorlibPath = typeof(object).Assembly.Location;
MetadataReference mscorlib = MetadataReference.CreateFromFile(mscorlibPath, new MetadataReferenceProperties(MetadataImageKind.Assembly));
CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
CSharpCompilation compilation = CSharpCompilation.Create("Test.dll",
references: new[] { mscorlib },
syntaxTrees: new[] { syntaxTree },
options: options);
// 3. Emit code including win32 version info
using (MemoryStream dllStream = new MemoryStream())
using (MemoryStream pdbStream = new MemoryStream())
using (Stream win32resStream = compilation.CreateDefaultWin32Resources(
versionResource: true, // Important!
noManifest: false,
manifestContents: null,
iconInIcoFormat: null))
{
EmitResult result = compilation.Emit(
peStream: dllStream,
pdbStream: pdbStream,
win32Resources: win32resStream);
System.IO.File.WriteAllBytes("Test.dll", dllStream.ToArray());
}
}