Mono Compiler as a Service (MCS)
Asked Answered
F

2

8

I'd like to consume Mono's compiler as a service from my regular .NET 3.5 application.

I've downloaded the latest bits (2.6.7), created a simple console application in Visual Studio and referenced the Mono.CSharp dll.

Then, in my console app (straight out of a sample online):

    Evaluator.Run("using System; using System.Linq;");
    bool ress;
    object res;
    Evaluator.Evaluate(
         "from x in System.IO.Directory.GetFiles (\"C:\\\") select x;",
         out res, out ress);

    foreach (var v in (IEnumerable)res)
    {
        Console.Write(v);
        Console.Write(' ');
    }

This throws an exception at Evaluator.Run (the first line):

Illegal enum value: 2049.
Parameter name: access

This is because the dll was compiled using Mono.exe, not csc.exe, I believe.

I've tried downloading the Mono.CSharp dll directly from http://tirania.org/blog/archive/2010/Apr-27.html in the demo-repl.zip file...and that does not throw an exception...However the out parameter (res) after calling Evaluator.Evaluate is null...so I'm not sure what's going wrong. No exception is thrown...

So, I'd like to figure out why the dll I downloaded from the demo-repl.zip returns null.

EDIT: I figured out why it returns null. It seems like for some reason the compiler isn't picking up the System.Linq namespace...though I can't tell why...If I just Evaluate "System.IO.Directory.GetFiles (\"C:\\")", it works fine.

UPDATE: It definitely seems like there's something wrong with the Mono compiler picking up referenced System assemblies. If I directly copy the sample of their csharp console tool:

csharp> var list = new int [] {1,2,3};
csharp> var b = from x in list
   >    where x > 1
   >    select x;
csharp> b;

I get the exception:

{interactive}(1,25): error CS1935: An implementation of `Select' query expressio
n pattern could not be found. Are you missing `System.Linq' using directive or `
System.Core.dll' assembly reference?

Also, in order for the MCS to actually be a feasible solution, I'll need to modify the compiler so that it emits to one single dynamic assembly, instead of emitting one assembly per evaluate call (otherwise it presents a major memory leak, which I've dealt with before in the form of the CSharpCodeProvider). Does anyone have an idea of how difficult this will be or can anyone point me in the right direction here?

Thanks.

Forsook answered 4/8, 2010 at 15:49 Comment(3)
why not to use msdn.microsoft.com/en-us/library/… ?Espagnole
CSharpCodeProvider emits and loads an assembly per compile (even if you use in-memory only option). I will be performing thousands of evaluations and thus would have thousands of assemblies loaded into the executing AppDomain (memory leak). Also, CSharpCodeProvider internally uses csc.exe, which is far more processor intensive than Reflection.Emit. In a previous project I used CSharpCodeProvider, and ran evaluations in a separate app domain that was recycled based on assembly count, but this proved to be an enormous maintenance overhead and very error prone, so I'd like avoid that approach.Forsook
Also, I realize that by default MCS also does a single assembly per evaluate, but since it relies on Reflection.Emit internally, I'm hoping I can change that behavior to emit to a single assembly as defined by AppDomain.DefineDynamicAssembly.Forsook
F
2

Ok, I think I have some answers.

To resolve the assembly load problem, I can either place a call to Assembly.LoadWithPartialName inside Mono.CSharp.Driver.LoadAssembly, or do the following in my application

        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

        private static bool isResolving;
        static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            if (!isResolving)
            {
                isResolving = true;
                var a = Assembly.LoadWithPartialName(args.Name);
                isResolving = false;
                return a;
            }
            return null;
        }

To make Mono reuse the same dynamic assembly for each Evaluate/Compile call, all I had to change is the following (although there are probably complexities I'm missing here).....

Inside Mono.CSharp.Evaluator, I added the property:

/// <summary>
/// Gets or sets a value indicating whether to auto reset when evaluations are performed and create a new assembly.
/// </summary>
/// <value><c>true</c> if [auto reset]; otherwise, <c>false</c>.</value>
public static bool AutoReset { get; set; }

Then...make sure Reset is called at least once in Init:

    static void Init ()
    {
        Init (new string [0]);
        Reset();
    }

And finally, in ParseString, simply don't reset unless AutoReset is true...

        static CSharpParser ParseString (ParseMode mode, string input, out bool partial_input)
        {
.
.
.
            if (AutoReset) Reset ();
Forsook answered 4/8, 2010 at 21:19 Comment(8)
So, I'm having this same issue. Is there a compiled version of Mono.CSharp.dll that has the changes you describe above? I'd like to use this in an application but I'm finding it very unstable.Runkle
What issue are you having? Have you tried making the changes I mention above? If you really need it, I can provide a dll, but you're probably better off just making these changes and compiling the dll yourself.Forsook
Also, on my end, after making these changes I haven't really encountered any other problems...have you?Forsook
Have you got a compiled version of this modified DLL you could share? I'm trying to do the same thing (as per my other question you answered about LINQ compilation) using the Mono 2.7 versioned DLL, but even evaluating 1+1 gives the Illegal enum value 2049 error.Housewares
The 2049 error definitely means you're using the version that's compiled for the Mono CLR, not the MS CLR (see the very top of my post). I used Mono's source control, opened two projects, compiled them in VS and that fixed my 2049 error. I can upload the dll and my modified sources if you post a location, but I'd still suggest compiling it yourself, in case you want to make some additional changes (for example implementing method call security for your LINQ queries, so someone can't run File.Delete(...)Forsook
Ok, got the code out and recompiled it and I can evaluate c# strings now. Thanks! Now I just need to figure out how to inject a variable into the evaluator and get the IQueryable back from a LINQ query.Housewares
What kind of variable? Make sure you follow the steps regarding the AppDomain assembly resolution so that you can use IQueryable.Forsook
I've got that part working hundreds. If I have a dbContext variable that I want to run a LINQ query against, I need to get that into the scope of the Mono.CSharp evaluator right? Otherwise how will I address the dbContext? edit -> Opened a new question: #3788596Housewares
D
1

According to Miguel's blog page you linked, you have to add a reference to System.Core in order to use LINQ on .Net.

csharp> using System.Linq;
csharp> from x in "Foo" select x;
Disaccord answered 4/8, 2010 at 20:51 Comment(1)
Ah...I see, but adding a reference isn't enough because it doesn't copy GAC assemblies or framework assemblies to the bin directory.Forsook

© 2022 - 2024 — McMap. All rights reserved.