Recently I try to compile and run C# code stored somewhere else. My goal is to import a .txt file, compile it and run it. I followed this article on Simeon's blog about compiling and running C# code within the program, and everything work well.
Then I try making something a bit more complex by importing the C# code from my computer, so I created a .txt file with the following lines that is store for instance at "C:\program.txt" :
(the text file)
using System;
namespace Test
{
public class DynaCore
{
static public int Main(string str)
{
Console.WriteLine("Cool it work !");
return str.Length;
}
}
}
I do some coding based on the same article and that is my code :
(the C# program)
using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
namespace DynaCode
{
class Program
{
static void Main(string[] args)
{
string[] lines = System.IO.File.ReadAllLines(#"C:\program.txt");
string bigLine = string.Empty;
foreach(string s in lines)
{
bigLine += s;
}
string[] finalLine = new string[1] { bigLine };
CompileAndRun(finalLine);
Console.ReadKey();
}
static void CompileAndRun(string[] code)
{
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = true;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
if (compile.Errors.HasErrors)
{
string text = "Compile error: ";
foreach (CompilerError ce in compile.Errors)
{
text += "rn" + ce.ToString();
}
throw new Exception(text);
}
Module module = compile.CompiledAssembly.GetModules()[0];
Type mt = null;
MethodInfo methInfo = null;
if (module != null)
{
mt = module.GetType("Test.DynaCore");
}
if (mt != null)
{
methInfo = mt.GetMethod("Main");
}
if (methInfo != null)
{
Console.WriteLine(methInfo.Invoke(null, new object[] { "here in dyna code. Yes it work !!" }));
}
}
}
}
This work well, and I got the following output as expected :
Cool it work !
33
Note that I put all the code of the .txt file in one big line that I do myseft, because as Simeon said :
CompileAssemblyFromSource consumes is a single string for each block (file) worth of C# code, not for each line.
Even now this sentence still a bit obscure for me.
( I tried CompileAndRun(new string[1] { lines.ToString() }); before but there was an error when compiling the .txt file, that's why I do the big line myself. )
And here is my problem : I ask myself : "What if I add a comment in my .txt file ?", so I edit it and that how it look : (the text file)
using System;
namespace Test
{
//This is a super simple test
public class DynaCore
{
static public int Main(string str)
{
Console.WriteLine("Cool it work !");
return str.Length;
}
}
}
And of course I got an error (CS1513) because I convert the .txt file in one big string, so everything after the // is ignored. So how can I use comment using // inside my .txt file and got the program work ?
I also try CompileAndRun(lines);, but after launching the program it crash when compiling the .txt file because of the exception.
I do some search about it and I didn't find anythings about comment. I guess there is somethings wrong about passing only one big line in the CompileAndRun method, but passing several lines don't work as I say upper.
(Another note : Comment using /* insert comment */ works.)
Each element given to CompileAssemblyFromSource is supposed to be a file, not a single line of code. So read the whole file into a single string and give it to the method and it'll work just fine.
static void Main(string[] args)
{
var code = System.IO.File.ReadAllText(#"C:\program.txt");
CompileAndRun(code);
Console.ReadKey();
}
static void CompileAndRun(string code)
{
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = true;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
// ...
}
Related
I am making a program in c#. I am trying to access a file (named counter.txt, in the Logs folder) using C#'s File class. I am reading the file, storing the only line in a variable named count, I increase it by 1, and then I write it back into the file. The reading and incrementation work, but the writing doesn't. I've tried to use different datatypes for the output container (string[], List<string>, ...). Nothing works. I am following a tutorial, and the code works fine there. I suppose it is a Visual Studio 19 or an antivirus/permission issue? Here is the code, any help would be greatly appreciated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
namespace Program
{
class Logger
{
private int count = 0;
// setup
public void Start()
{
string counterFile = #".\\Logs\\counter.txt";
string line = File.ReadAllLines(counterFile)[0];
count = int.Parse(line);
count++;
string[] toWrite = {count.ToString()};
Console.WriteLine(counterFile);
File.WriteAllLines(counterFile, toWrite);
Console.WriteLine(count);
}
}
}
The file structure (Logger.cs is the file I'm doing this in):
As identified in the comments, the problem is that you've added counter.txt to Visual Studio, with a "Copy to output directory" property of "Always".
This means that whenever you build your project, Visual Studio will copy that counter.txt to your bin directory, overwriting whatever file was there before.
Your application is, of course, modifying the copy in the bin directory.
A better approach would be to remove counter.txt from your file list in Visual Studio entirely, and instead all logic to your problem to create the file if it doesn't already exist. Something like:
class Logger
{
private int count = 0;
// setup
public void Start()
{
string counterDirectory = "Logs";
string counterFile = Path.Join(counterDirectory, "counter.txt");
if (!Directory.Exists(counterDirectory))
{
Directory.CreateDirectory(counterDirectory);
}
if (File.Exists(counterFile))
{
string line = File.ReadAllLines(counterFile)[0];
count = int.Parse(line);
count++;
}
else
{
count = 1;
}
string[] toWrite = {count.ToString()};
Console.WriteLine(counterFile);
File.WriteAllLines(counterFile, toWrite);
Console.WriteLine(count);
}
}
Another problem you are going to run into at some point is that the path to your file is relative, and therefore relative to the user's current working directory.
This might be the directory containing your application, but it might not be: try opening a command prompt inside your bin folder and then running Debug\YourApplication.exe, and see that Logs is created in your current directory!
Instead, you probably want to do something like:
class Logger
{
private int count = 0;
// setup
public void Start()
{
string applicationDirectory = Path.GetDirectoryName(typeof(Logger).Assembly.Location);
string counterDirectory = Path.Join(applicationDirectory , "Logs");
string counterFile = Path.Join(counterDirectory, "counter.txt");
if (!Directory.Exists(counterDirectory))
{
Directory.CreateDirectory(counterDirectory);
}
if (File.Exists(counterFile))
{
string line = File.ReadAllLines(counterFile)[0];
count = int.Parse(line);
count++;
}
else
{
count = 1;
}
string[] toWrite = {count.ToString()};
Console.WriteLine(counterFile);
File.WriteAllLines(counterFile, toWrite);
Console.WriteLine(count);
}
}
Also your code to read/write the file is overly complex: there's no need to use arrays here, as the file only contains a single line! You can get away with something like:
class Logger
{
private int count = 0;
// setup
public void Start()
{
string applicationDirectory = Path.GetDirectoryName(typeof(Logger).Assembly.Location);
string counterDirectory = Path.Join(applicationDirectory , "Logs");
string counterFile = Path.Join(counterDirectory, "counter.txt");
if (!Directory.Exists(counterDirectory))
{
Directory.CreateDirectory(counterDirectory);
}
if (File.Exists(counterFile))
{
string contents = File.ReadAllText(counterFile);
count = int.Parse(contents);
count++;
}
else
{
count = 1;
}
Console.WriteLine(counterFile);
File.WriteAllText(counterFile, count.ToString());
Console.WriteLine(count);
}
}
Your code works just fine if the path to the file is correct and your app is able to access that folder, the file is not locked, etc.
I was trying this code:
using System;
using System.IO;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
int count;
string counterFile = #"D:\tmp\counter.txt";
string line = File.ReadAllLines(counterFile)[0];
count = int.Parse(line);
count++;
string[] toWrite = { count.ToString() };
Console.WriteLine(counterFile);
File.WriteAllLines(counterFile, toWrite);
Console.WriteLine(count);
}
}
}
With the file containing 5. Here is the result:
The updated file looks like:
Update. You have to check that your file is copied on build. That's might be your issue
I'm trying to compile multiple C# files into one executable at runtime and I keep getting compiler errors that aren't present if I run this code from Visual Studio.
The specific errors I'm getting from my runtime compiler is "; expected" and "Method must have a return type" at line 7 in 'Program.cs'. Obviously, I would fix these if there was something to fix.
So the code. Here's the actual compiler code: (with some parts removed, like the actual error reporting)
private CSharpCodeProvider provider = new CSharpCodeProvider();
private CompilerParameters parameters = new CompilerParameters();
private void RunOption_Click(object sender, RoutedEventArgs e)
{
parameters.GenerateInMemory = false;
parameters.GenerateExecutable = true;
parameters.OutputAssembly = Path + "/builds/debug/bot.exe";
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Threading.Tasks.dll");
parameters.CompilerOptions = "/optimize";
parameters.WarningLevel = 3;
parameters.TreatWarningsAsErrors = false;
string[] filesToCompile = Directory.GetFiles(Path + "\\src\\", "*.cs");
CompilerResults results = provider.CompileAssemblyFromFile(parameters, filesToCompile);
}
And here are the two files it's trying to compile.
Program.cs
using System.Threading.Tasks;
namespace MyBot
{
class Program
{
static void Main(string[] args) => new MyBot().RunAsync().GetAwaiter().GetResult();
}
}
MyBot.cs
using System;
using System.Threading.Tasks;
namespace MyBot
{
public class MyBot
{
public async Task RunAsync()
{
Console.Title = "MyBot";
Console.Read();
}
}
}
What am I missing here? As mentioned, if I run the code from 'Program.cs' and 'MyBot.cs' in Visual Studio, everything works fine, so there are no errors in the actual code.
As Eric Lippert pointed out, I was using a newer .NET version than the compiler. So I simply changed the compiler to the new Roslyn compiler that supports C# 6, which I just what I need. So lesson learned: Always make sure you target the right framework version.
OLD ANSWER
Welp, I'm going to declare myself idiot once again. Better to do it myself before anyone else does.
I got it working by changing the Program.cs file.
Instead of
static void Main(string[] args) => new MyBot().RunAsync().GetAwaiter().GetResult();
I did
static void Main(string[] args)
{
new MyBot().RunAsync().GetAwaiter().GetResult();
}
But I still have no idea why it didn't work in the first place since it worked in Visual Studio.
I was wondering if there is any way to pass a variable value in a code that will be compiled One Time using CSharpCodeProvider .
for example :
string code = #"
using System;
namespace First
{
public class Program
{
public int Value; // pass this value
public static void Main()
{
" +
"Console.WriteLine(\"Hello + Value\");"
+ #"
}
}
}
";
Compile Method :
public void Compile(String Code)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, Code);
}
So i want to be able to pass a value of the Value example 2
and what i meant by ONE TIME is like compile time so the compiled code will always in its run-time will display the value : 2 whenever i executed the application .
I hope its clear !
Solved Using Mono.Cecil reference details Documentatin
I have a piece of software that generates code for a C# project based on user actions. I would like to create a GUI to automatically compile the solution so I don't have to load up Visual Studio just to trigger a recompile.
I've been looking for a chance to play with Roslyn a bit and decided to try and use Roslyn instead of msbuild to do this. Unfortunately, I can't seem to find any good resources on using Roslyn in this fashion.
Can anyone point me in the right direction?
You can load the solution by using Roslyn.Services.Workspace.LoadSolution. Once you have done so, you need to go through each of the projects in dependency order, get the Compilation for the project and call Emit on it.
You can get the compilations in dependency order with code like below. (Yes, I know that having to cast to IHaveWorkspaceServices sucks. It'll be better in the next public release, I promise).
using Roslyn.Services;
using Roslyn.Services.Host;
using System;
using System.Collections.Generic;
using System.IO;
class Program
{
static void Main(string[] args)
{
var solution = Solution.Create(SolutionId.CreateNewId()).AddCSharpProject("Foo", "Foo").Solution;
var workspaceServices = (IHaveWorkspaceServices)solution;
var projectDependencyService = workspaceServices.WorkspaceServices.GetService<IProjectDependencyService>();
var assemblies = new List<Stream>();
foreach (var projectId in projectDependencyService.GetDependencyGraph(solution).GetTopologicallySortedProjects())
{
using (var stream = new MemoryStream())
{
solution.GetProject(projectId).GetCompilation().Emit(stream);
assemblies.Add(stream);
}
}
}
}
Note1: LoadSolution still does use msbuild under the covers to parse the .csproj files and determine the files/references/compiler options.
Note2: As Roslyn is not yet language complete, there will likely be projects that don't compile successfully when you attempt this.
I also wanted to compile a full solution on the fly. Building from Kevin Pilch-Bisson's answer and Josh E's comment, I wrote code to compile itself and write it to files.
Software Used
Visual Studio Community 2015 Update 1
Microsoft.CodeAnalysis v1.1.0.0 (Installed using Package Manager Console with command Install-Package Microsoft.CodeAnalysis).
Code
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
namespace Roslyn.TryItOut
{
class Program
{
static void Main(string[] args)
{
string solutionUrl = "C:\\Dev\\Roslyn.TryItOut\\Roslyn.TryItOut.sln";
string outputDir = "C:\\Dev\\Roslyn.TryItOut\\output";
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
bool success = CompileSolution(solutionUrl, outputDir);
if (success)
{
Console.WriteLine("Compilation completed successfully.");
Console.WriteLine("Output directory:");
Console.WriteLine(outputDir);
}
else
{
Console.WriteLine("Compilation failed.");
}
Console.WriteLine("Press the any key to exit.");
Console.ReadKey();
}
private static bool CompileSolution(string solutionUrl, string outputDir)
{
bool success = true;
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
Dictionary<string, Stream> assemblies = new Dictionary<string, Stream>();
foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
{
Compilation projectCompilation = solution.GetProject(projectId).GetCompilationAsync().Result;
if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
{
using (var stream = new MemoryStream())
{
EmitResult result = projectCompilation.Emit(stream);
if (result.Success)
{
string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
using (FileStream file = File.Create(outputDir + '\\' + fileName))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
else
{
success = false;
}
}
}
else
{
success = false;
}
}
return success;
}
}
}
How can I open mp3 file with RealPlayer while the default is MediaPlayer
I know Process and ProcessStartInfo method, but I would like to know how to "open program with..."
Can you help me, plz?
Okay, so thought I'd make this possible for you before I clock off for the night. I have thrown together a working console application which loads (known) installed programs from the registry's App Path key. The solution is far from perfect, won't be the safest, fastest, or most reliable solution, and it certainly shouldn't be seen amongst any production code, but it is more than enough to aid you, hopefully, in developing what it is you need:
So, here is the code, minus the namespace...
using System;
using System.IO;
using Microsoft.Win32;
using System.Diagnostics;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
if (args.Length >= 0 && !string.IsNullOrEmpty(args[0]) && File.Exists(args[0]))
{
var programs = new InstalledPrograms();
var programKey = "RealPlay.exe".ToLowerInvariant();
if (programs.ContainsKey(programKey))
{
var programPath = programs[programKey];
if (!string.IsNullOrEmpty(programPath) && File.Exists(programPath))
{
var process = new Process();
process.StartInfo = new ProcessStartInfo(programPath);
process.StartInfo.Arguments = args[0];
if (process.Start())
{
Console.WriteLine("That was easy!");
}
else
{
Console.WriteLine("Hell's bells and buckets of blood, we seem to have hit a snag!");
}
}
}
}
else
{
Console.WriteLine("Specify a file as an argument, silly!");
}
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
class InstalledPrograms : Dictionary<string, string>
{
static string PathKeyName = "Path";
static string RegistryKeyToAppPaths = #"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
public InstalledPrograms()
{
Refresh();
}
public void Refresh()
{
Clear();
using (var registryKey = Registry.LocalMachine.OpenSubKey(RegistryKeyToAppPaths))
{
var executableFullPath = string.Empty;
foreach (var registrySubKeyName in registryKey.GetSubKeyNames())
{
using (var registrySubKey = registryKey.OpenSubKey(registrySubKeyName))
{
executableFullPath = registrySubKey.GetValue(string.Empty) as string;
Add(registrySubKeyName.ToLowerInvariant(), executableFullPath);
}
}
}
}
}
}
Though we check for file existence, and other minor but necessary checks are made, you would still need to tighten this up further when plugged into the environment of your own code, including, among other things, exception handling for, but not limited to, registry access issues.