3

I want to get the stdout of a dynamically compiled code.

My code:

using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.IO;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var source = File.ReadAllText("form.cs");

        Dictionary<string, string> providerOptions = new Dictionary<string, string>
                {
                    {"CompilerVersion", "v4.0"}
                };
            CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

            CompilerParameters compilerParams = new CompilerParameters
            {
                GenerateInMemory = true,
                GenerateExecutable = false,
                ReferencedAssemblies =  {"System.dll" ,"mscorlib.dll"}
        };
            CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
            Assembly assembly = results.CompiledAssembly;
            Type program = assembly.GetType("program.TestPerson");
            MethodInfo main = program.GetMethod("Main");
            var outp= main.Invoke(null, null);
            //Console.WriteLine(outp);
            Console.ReadLine();
        }
    }
}

The content of form.cs:

using System;
namespace program {
    public class TestPerson
    {
         public static void Main()
        {
            var person1 = new Person();
            Console.WriteLine(person1.Name);
        }
    }
}

public class Person
{
    public Person()
    {
        Name = "unknown";
    }
        public Person(string name)
        {
            Name = name;
        }
        public string Name { get;set; }
        public override string ToString()
        {
            return Name;
        }
    }

What I exactly want is to have the stdout of form.cs (Console.WriteLine) after compilation in a variable in parent applicaton, by the way, I do NOT want to build the code into the file and run it as process and read its output. Also assume the content of form.cs is NOT editable.

Emily Wong
  • 297
  • 3
  • 10
  • 1
    You want to get the IL output? Or you want to catch the `Console.WriteLine` from your application that you would be calling? – Icepickle Jan 02 '19 at 13:19
  • 1
    I might be wrong, but it looks you have confusion. The generated code is not a stand-alone process, it will be running as part of your currently running process, thus it won't have an `stdout` of its own. – ZorgoZ Jan 02 '19 at 13:22
  • @Icepickle, I want to catch the Console.WriteLine output in form.cs – Emily Wong Jan 02 '19 at 13:22
  • @ZorgoZ, yes I know that, I think you didn't get my question, what I want to do is to dynamically compile a piece of code in memory and gets its output in a variable without building any file. more specific, lets assume the code that I want to compile is a standalone command line application which have main() function, and writing the output into console. but i want to compile it in memory and get what it would write in console in a variable. – Emily Wong Jan 02 '19 at 13:25
  • Would [setting the out](https://learn.microsoft.com/en-us/dotnet/api/system.console.setout?view=netframework-4.7.2) help you? – Icepickle Jan 02 '19 at 13:26
  • @EmilyWong Ok, that was my thought also. But what I wrote means that this question is not related to whether this is dynamic or not, you need to capture the stdout of your process, indifferent of the code that wants to write to it. But in general, it would be better not to use the console for that, but to inject a dedicated stream for example. – ZorgoZ Jan 02 '19 at 13:27
  • @ZorgoZ, I don't understand your answer. could you come up with a simple snippet of code please. – Emily Wong Jan 02 '19 at 13:30
  • @Icepickle As mentioned, I want to get output of Console.WriteLine in a variable in main application. Not sure how setout will help? Assume I cant change the form.cs code – Emily Wong Jan 02 '19 at 13:40
  • @EmilyWong I see that ZorgoZ already answered it with the SetOut :) – Icepickle Jan 02 '19 at 14:30

1 Answers1

3

The main might have been confused you, but as I wrote in my comment, your dynamically compiled code does not run in its own process (that can be also achieved, but it is far more complicated), thus it does not have its own output. The method main is just another method in just another class in your default AppDomain in your current process. This means it will write to the console of your outer, hosting process. You will have to capture that output with Console.SetOut. See following linqpad snippet:

string source = @"using System;
namespace program {
    public class TestPerson
    {
        public static void Main()
        {
            Console.WriteLine(""TEST"");
        }
    }
}";

void Main()
{
    Dictionary<string, string> providerOptions = new Dictionary<string, string>
            {
                {"CompilerVersion", "v4.0"}
            };
    CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

    CompilerParameters compilerParams = new CompilerParameters
    {
        GenerateInMemory = true,
        GenerateExecutable = false,
        ReferencedAssemblies = { "System.dll", "mscorlib.dll" }
    };
    CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
    Assembly assembly = results.CompiledAssembly;
    Type program = assembly.GetType("program.TestPerson");
    MethodInfo main = program.GetMethod("Main");

    var sb = new StringBuilder();
    var writer = new StringWriter(sb);
    Console.SetOut(writer);

    var outp = main.Invoke(null, null);

    sb.ToString().Dump(); // this Dump is from linqpad, do what you want with the StringBuilder content 

    Console.ReadLine();
 }

If you want to write to the original standard output, save it first, like this:

...
var oldOut = Console.Out;
Console.SetOut(writer);

var outp = main.Invoke(null, null);

oldOut.WriteLine($"The result is: {sb.ToString()}");
ZorgoZ
  • 2,974
  • 1
  • 12
  • 34
  • Console.WriteLine(sb.ToString()); does not return anything. Am I doing it correctly? – Emily Wong Jan 02 '19 at 13:53
  • @EmilyWong. No, remember, SetOut is called before, the output redirection is valid. If you want the output back, save `Console.Out` in a variable before calling `SetOut`, then use it to restore the original output before trying to use it. – ZorgoZ Jan 02 '19 at 13:55