c# - compile c# code at runtime with roslyn - c#

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.

Related

Why C# PowerShell.Create() returns null in case of .NET Core?

I have library built for .NET Standard 2.0.
I am using this library in
a .NET Core project and
a .NET Framework project.
I have integrated this PowerShellStandard.Library (Version 5.1.0) nuget into my .NET Standard library. It is used to fetch some date which is connected to PC.
private string RunPowerShellCommand(string paramValue)
{
using (PowerShell powershell = PowerShell.Create().AddCommand("Get-PnpDeviceProperty").AddParameter("InstanceId", paramValue).AddParameter("KeyName", "DEVPKEY_Device_Parent"))
{
var result = powershell.Invoke();
string deviceId = "";
foreach (var r in result)
{
var properties = r.Properties;
var device = (string)properties.Where(t => t.Name == "Data").FirstOrDefault().Value;
deviceId = (string)device;
}
return deviceId.Split('\\')[2];
}
}
When I call above code from the .NET Framework project then the PowerShell.Create() returns the proper PowerShell object
When I call same code from the .NET Core project then it returns null.
Could anyone me please ?!

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

Guaranteed way to find the filepath of the ildasm.exe and ilasm.exe files regardless of .NET version/environment?

Is there a way to programmatically get the FileInfo/Path of the ildasm.exe/ilasm.exe executables? I'm attempting to decompile and recompile a dll/exe file appropriately after making some alterations to it (I'm guessing PostSharp does something similar to alter the IL after the compilation).
I found a blog post that pointed to:
var pfDir = Environment.GetFolderPath(Environment.SpecialFolders.ProgramFiles));
var sdkDir = Path.Combine(pfDir, #"Microsoft SDKs\Windows\v6.0A\bin");
...
However, when I ran this code the directory did not exist (mainly because my SDK version is 7.1), so on my local machine the correct path is #"Microsoft SDKs\Windows\v7.1\bin". How do I ensure I can actually find the ildasm.exe?
Similarly, I found another blog post on how to get access to ilasm.exe as:
string windows = Environment.GetFolderPath(Environment.SpecialFolder.System);
string fwork = Path.Combine(windows, #"..\Microsoft.NET\Framework\v2.0.50727");
...
While this works, I noticed that I have Framework and Framework64, and within Framework itself I have all of the versions up to v4.0.30319 (same with Framework64). So, how do I know which one to use? Should it be based on the .NET Framework version I'm targetting?
Summary:
How do I appropriately guarantee to find the correct path to ildasm.exe?
How do I appropriately select the correct ilasm.exe to compile?
One option would be to reference Microsoft.Build.Utilities.Core and use:
var ildasm = Microsoft.Build.Utilities.ToolLocationHelper.GetPathToDotNetFrameworkSdkFile("ildasm.exe", TargetDotNetFrameworkVersion.VersionLatest);
var ilasm = Microsoft.Build.Utilities.ToolLocationHelper.GetPathToDotNetFrameworkFile("ilasm.exe", TargetDotNetFrameworkVersion.VersionLatest);
Right now on my machine this returns:
ildasm = C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools\ildasm.exe
ilasm = C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe
I recently needed to do this so I trawled the interwebs for all the possible paths of the Windows SDK and search through those in most recent known order. I also check for whether the OS and process is 64bit and then just use that version by looking in the appropriate Program Files folders. I don't think choosing 64-bit over the 32-bit versions of the tools has any great significance. The x86 version of ILAsm can happily assemble 64-bit preferred assemblies without a hitch, it's all IL and not actually executing any of the code.
ILDasm is part of the Windows SDK where ILAsm is just the .NET Framework SDK so here are some static methods to hunt them down with. The code is baked for .NET 4.0 but you could make some minor tweaks to get it building on .NET 2.0 if you want.
// ILDasm.exe will be somewhere in here
private static string FindPathForWindowsSdk()
{
string[] windowsSdkPaths = new[]
{
#"Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\",
#"Microsoft SDKs\Windows\v8.0A\bin\",
#"Microsoft SDKs\Windows\v8.0\bin\NETFX 4.0 Tools\",
#"Microsoft SDKs\Windows\v8.0\bin\",
#"Microsoft SDKs\Windows\v7.1A\bin\NETFX 4.0 Tools\",
#"Microsoft SDKs\Windows\v7.1A\bin\",
#"Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\",
#"Microsoft SDKs\Windows\v7.0A\bin\",
#"Microsoft SDKs\Windows\v6.1A\bin\",
#"Microsoft SDKs\Windows\v6.0A\bin\",
#"Microsoft SDKs\Windows\v6.0\bin\",
#"Microsoft.NET\FrameworkSDK\bin"
};
foreach (var possiblePath in windowsSdkPaths)
{
string fullPath = string.Empty;
// Check alternate program file paths as well as 64-bit versions.
if (Environment.Is64BitProcess)
{
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath, "x64");
if (Directory.Exists(fullPath))
{
return fullPath;
}
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath, "x64");
if (Directory.Exists(fullPath))
{
return fullPath;
}
}
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath);
if (Directory.Exists(fullPath))
{
return fullPath;
}
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath);
if (Directory.Exists(fullPath))
{
return fullPath;
}
}
return null;
}
// ILAsm.exe will be somewhere in here
private static string FindPathForDotNetFramework()
{
string[] frameworkPaths = new[]
{
#"Microsoft.NET\Framework\v4.0.30319",
#"Microsoft.NET\Framework\v2.0.50727"
};
foreach (var possiblePath in frameworkPaths)
{
string fullPath = string.Empty;
if (Environment.Is64BitProcess)
{
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath.Replace(#"\Framework\", #"\Framework64\"));
if (Directory.Exists(fullPath))
{
return fullPath;
}
}
fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath);
if (Directory.Exists(fullPath))
{
return fullPath;
}
}
return null;
}
You can augment this by passing in the executable you are looking for and change Directory.Exists with File.Exists as well, up to you. You could also take the possible lists and put them in a config file so you can add more without recompiling.

Injecting GeneratedCodeAttribute with Mono.Cecil

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

Compile simple string

Was just wondering if there are any built in functions in c++ OR c# that lets you use the compiler at runtime? Like for example if i want to translate:
!print "hello world";
into:
MessageBox.Show("hello world");
and then generate an exe which will then be able to display the above message? I've seen sample project around the web few years ago that did this but can't find it anymore.
It is possible using C#. Have a look at this Sample Project from the CodeProject.
Code Extract
private Assembly BuildAssembly(string code)
{
Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters compilerparams = new CompilerParameters();
compilerparams.GenerateExecutable = false;
compilerparams.GenerateInMemory = true;
CompilerResults results = compiler.CompileAssemblyFromSource(compilerparams, code);
if (results.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors )
{
errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
}
throw new Exception(errors.ToString());
}
else
{
return results.CompiledAssembly;
}
}
public object ExecuteCode(string code, string namespacename, string classname, string functionname, bool isstatic, params object[] args)
{
object returnval = null;
Assembly asm = BuildAssembly(code);
object instance = null;
Type type = null;
if (isstatic)
{
type = asm.GetType(namespacename + "." + classname);
}
else
{
instance = asm.CreateInstance(namespacename + "." + classname);
type = instance.GetType();
}
MethodInfo method = type.GetMethod(functionname);
returnval = method.Invoke(instance, args);
return returnval;
}
In C++ you can't use the compiler at runtime but you can embed an interpreter in your project, like CINT.
You can always do it in the dirty way, with system() and calling the compiler "gcc..." or your equivalent
Nick's suggestion is good, but there is an alternative which is probably simpler to implement (but might not be appropriate for all projects). If you can assume that your user has a compiler installed you can generate a file and then compile it using their compiler.
The .NET-framework provides a few classes which give you access to compilers and code generators for C# and VB.NET, resulting in either an assembly loaded into memory or a simple .exe-file. See CSharpCodeProvider and this article.
Alternately, you can just create the source files and compile them manually (command-line calls (system) to the compiler, makefiles).
Concerning the translation of your source: You'll have to use parsing mechanisms like regular expressions here, or use a compiler-compiler tool like Coco/R, yacc etc. (Note that under C++, boost::spirit can also be quite useful)
In C# you can create a .NET "CodeDom" tree and then compile this using the .NET compiler. This gives you full access to most features of .NET.
See the "System.CodeDom" namespace or the MSDN help for CodeCompileUnit for details.

Categories

Resources