0

I have this console type thing that accepts a line of C# code, wraps it up in some surrounding code and compiles it into an assembly. Then, I invoke the method from that assembly, output the result and that's it.

The problem is, the assembly needs to have a name so I can set it as a friend assembly so it could access the non-public classes. I named it "console".

Everything worked as expected but the problem is that I can't run a second script after one has finished because the file named "console" already exists in the directory and can't be overwritten.

I've tried Disposing of everything that has a Dispose method. I've tried manually deleting the file with File.Delete. Nothing helped.

So here's the code I'm using. I hope someone can help me.

CSharpCodeProvider provider = new CSharpCodeProvider();
var param = new CompilerParameters();
param.GenerateInMemory = false;
param.GenerateExecutable = false;
param.OutputAssembly = "console";
param.ReferencedAssemblies.Add("System.dll");
param.ReferencedAssemblies.Add("System.Core.dll");
param.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
var results = provider.CompileAssemblyFromSource(param,
@"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace FireflyGL
{
class DebugConsole
{
    static void Run(StringWriter writer)
    {
        var stdOut = Console.Out;
        Console.SetOut(writer);
        " + input.Text + @"
        Console.SetOut(stdOut);
    }
}
}");
            if (results.Errors.HasErrors)
            {
                for (int i = 0; i < results.Errors.Count; ++i)
                {
                    PushText(results.Errors[i].ErrorText);
                }
            }
            else
            {
                try
                {
                    var writter = new StringWriter();
                    results.CompiledAssembly.GetType("FireflyGL.DebugConsole").GetMethod("Run", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { writter });
                    PushText(writter.ToString());
                    history.Add(input.Text);
                    currentHistory = history.Count;
                    input.Text = "";
                    writter.Dispose();
                }
                catch (Exception)
                {

                }
            }
            provider.Dispose();
            File.Delete(results.CompiledAssembly.Location);
Luka Horvat
  • 4,283
  • 3
  • 30
  • 48

1 Answers1

1

You have to unload the assembly to get rid of all the references. Unfortunately, you can't do that. You can however unload an AppDomain and provided you reference your assembly in that AppDomain, it will be unloaded as well.

If you don't care about creating a memory leak, you could also just create unique names for your assembly (console1, console2 etc.)...

Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
  • I did see an example online where the AppDomain was used but I don't really understand how. How do I make my assembly be referenced in a certain AppDomain? – Luka Horvat Jun 05 '12 at 13:27
  • Never mind. I found the answer here: http://stackoverflow.com/questions/1799373/how-can-i-prevent-compileassemblyfromsource-from-leaking-memory – Luka Horvat Jun 05 '12 at 13:56
  • Is there any other way to go about it? I've been playing around and AppDomain is giving me tons of trouble. I need to be able to send object references to the script but then it throws a weird "object not serializable" exception. – Luka Horvat Jun 05 '12 at 22:02
  • @Darwin, I'm sorry, I don't know enough about AppDomains myself. Your objects need to derive from a special base class, though. Was it `MarshalByRef`? – Daren Thomas Jun 06 '12 at 06:50
  • That's why I'm looking for other options because I'm in a situation where I can't make everything derive from MarshalByRef. – Luka Horvat Jun 06 '12 at 08:20