Injecting GeneratedCodeAttribute with Mono.Cecil - c#

I'm manupulating my .net 2.0 assemblies with Mono.Cecil.
After manipulation I want to mark assembly as processed by injecting a module attribute
var stringType = _module.Import(typeof(string));
var baseCtor = _module.Import(typeof(GeneratedCodeAttribute).GetConstructor(new[] { typeof(string), typeof(string) }));
var result = new CustomAttribute(baseCtor);
result.ConstructorArguments.Add(new CustomAttributeArgument(stringType, "ProcessedBySomething"));
result.ConstructorArguments.Add(new CustomAttributeArgument(stringType, "1.0"));
After saving the assembly it become dependent on .net 4.0, since manipulating app is written in .net 4.0.
GeneratedCodeAttribute exists in .net 2.0, so what am I doing wrong?

You're guessing right. Since the manipulating application is running on .net 4.0, typeof being a runtime feature, it will return a type for the current runtime version.
To fix it, the simple thing to do is to create references for the mscorlib version referenced by the module you're modifying, using Cecil to open the assembly. Your code would become:
var stringType = _module.TypeSystem.String;
var corlib = (AssemblyNameReference) _module.TypeSystem.Corlib;
var system = _module.AssemblyResolver.Resolve (new AssemblyNameReference ("System", corlib.Version) {
PublicKeyToken = corlib.PublicKeyToken,
});
var generatedCodeAttribute = system.MainModule.GetType ("System.CodeDom.Compiler.GeneratedCodeAttribute");
var generatedCodeCtor = generatedCodeAttribute.Methods.First (m => m.IsConstructor && m.Parameters.Count == 2);
var result = new CustomAttribute (_module.Import (generatedCodeCtor));
result.ConstructorArguments.Add(new CustomAttributeArgument(stringType, "ProcessedBySomething"));
result.ConstructorArguments.Add(new CustomAttributeArgument(stringType, "1.0"));

Related

How do you do Create Class of Assembly using Reflection in .Net Standard 2.1

How do you do this in .Net Standard 2.1
var instance = Activator.CreateInstance("SomeAssemblyName", "SomeClass");
instance.Unwrap()
I used to create the assemblies in .netCore using the below code,
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(#"Directory_Path");
var myType = myAssembly.GetType("Class_Name");
var myInstance = Activator.CreateInstance(myType);
In .Net Standard also, it should work.
Edit:
Try this for .NetStandard,
var assembly = Assembly.LoadFrom("directoryPath");
var type = assembly.GetType("ClassName");
var instance = Activator.CreateInstance(type);

c# - compile c# code at runtime with roslyn

I checked some resource about roslyn,and i not found how to compile c# sources to executable with Roslyn.I can easily compile some .cs files to .exe using CodeDom:
/// <summary>
/// "anycpu" || "anycpu32bitpreferred" || "x86" || "x64" || "ARM" || "Itanium"
/// </summary>
public static string param = "anycpu";
public static string BCS(string[] sources,string[] libs,string outPath,bool exef)
{
var options = new Dictionary<string, string> {
{ "CompilerVersion", "v4.0.0" }
};
CSharpCodeProvider codeProvider = new CSharpCodeProvider(options);
CompilerParameters parameters = new CompilerParameters(libs);
parameters.GenerateExecutable = exef;
parameters.OutputAssembly = outPath;
parameters.CompilerOptions = "-platform:" + param;
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sources);
if (results.Errors.Count > 0)
{
string errsText = "";
foreach (CompilerError CompErr in results.Errors)
{
errsText = "("+CompErr.ErrorNumber +
")Line " + CompErr.Line +
",Column "+CompErr.Column +
":"+CompErr.ErrorText + "" +
Environment.NewLine;
}
return errsText;
}
else
{
return "Success";
}
}
but problem of CodeDom - he can compile only c# with .NET Framework 4.0,but i need to compile c# files with 4.6.1 .NET Framework version.So,question: Can i compile some c# files(.cs) with 4.6.1 .NET Framework version using Roslyn Compiler?
The CodeDom has been deprecated in favor of the Roslyn APIs. On .NET Framework (ie. .NET 4.x) you can continue to use the CSharpCodeProvider which uses the built in compiler which supports up to C# 6 if memory serves. If you want to use C# versions later than that you need to use Roslyn and there's a shim CodeProvider that uses Roslyn that gives you access to later C# versions.
Here's what this looks like using either the 'classic' provider or Roslyn provider with the CodeDom:
if (CompilerMode == ScriptCompilerModes.Roslyn)
// NuGet Package: Microsoft.CodeDom.Providers.DotNetCompilerPlatform
_compiler = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();
else
_compiler = new Microsoft.CSharp.CSharpCodeProvider(); // classic
Ultimately though the CodeProvider interface is deprecated and it's a better idea to use the Roslyn APIs directly.
There's a lot more info both on using the old CSharpCodeProvider with Roslyn and using the Roslyn APIs in this post of mine.

Compiling and running code at runtime in .NET Core 1.0

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);
}
}

Writing Over Assemblies Using Mono.Cecil

I am trying to write over an assembly that is currently loaded into the AppDomain using Mono.Cecil and I keep getting an IO error saying the file is in use. What I'm doing at the moment is creating an assembly using AssemblyDefinitiion.Create() making any modifications I need than then writing over the Assembly using AssemblyDefinition.Write(). From what I understand about Mono.Cecil it should be possible to write over an existing assembly, but are there other steps to do so?
For 0.10.0, adding the ReaderParameters helped with this:
using (AssemblyDefinition a = AssemblyDefinition.ReadAssembly(file, new ReaderParameters { ReadWrite = true }))
{
var assemblyFileVersionCtor = a.CustomAttributes.Where(attribute => attribute.AttributeType.FullName == typeof(AssemblyFileVersionAttribute).FullName)
.FirstOrDefault();
if (assemblyFileVersionCtor != null)
{
assemblyFileVersionCtor.ConstructorArguments[0] = new CustomAttributeArgument(a.MainModule.TypeSystem.String, versionToSet.ToString());
a.Write();
}
}
I also had to set the InMemory property of the ReaderParameters to true.
var rp = new ReaderParameters();
rp.ReadingMode = ReadingMode.Immediate;
rp.ReadWrite = true;
rp.InMemory = true;

How can I target a specific language version using CodeDOM?

Using the C# code provider and the ICodeCompiler.CompileAssemblyFromSource method, I am attempting to compile a code file in order to produce an executable assembly.
The code that I would like to compile makes use of features such as optional parameters and extension methods that are only available when using the language C# 4.
Having said that, the code that I would like to compile only requires (and needs) to target version 2.0 of the .NET Framework.
Using the proceeding code it is possible to avoid any compile-time errors pertaining to syntax however, the resulting assembly will target version 4.0 of the framework which is undesirable.
var compiler = new CSharpCodeProvider(
new Dictionary<string, string> { { "CompilerVersion", "v4.0" } } );
How can I make is so that the code provider targets language version 4.0 but produces an assembly that only requires version 2.0 of the framework?
You need to instruct the C# compiler (that CSharpCodeProvider uses indirectly) that you want to link to another mscorlib.dll, using the /nostdlib option. Here is a sample that should do it:
static void Main(string[] args)
{
// defines references
List<string> references = new List<string>();
// get a reference to the mscorlib you want
var mscorlib_2_x86 = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Windows),
#"Microsoft.NET\Framework\v2.0.50727\mscorlib.dll");
references.Add(mscorlib_2_x86);
// ... add other references (System.dll, etc.)
var provider = new CSharpCodeProvider(
new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
var parameters = new CompilerParameters(references.ToArray(), "program.exe");
parameters.GenerateExecutable = true;
// instruct the compiler not to use the default mscorlib
parameters.CompilerOptions = "/nostdlib";
var results = provider.CompileAssemblyFromSource(parameters,
#"using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello world from CLR version: "" + Environment.Version);
}
}");
}
If you run this, it should compile a program.exe file. If you run that file, it should display something like this:
Hello world from CLR version: 2.0.50727.7905

Categories

Resources