oleg-shilo / cs-script

C# scripting platform
http://www.cs-script.net
MIT License
1.57k stars 234 forks source link

//css_ref 'ed library in the script folder not found #304

Closed rp0815 closed 1 year ago

rp0815 commented 1 year ago

Hi Oleg, I checked some scripts that are already delivered to customers and found a problem I cannot solve. Some of the script need libraries wich implement more expensive UI or algorithms. These libraries are located in the script folder. When I reference them using //css_ref and try to use them the library cannot be found.

using System;
using System.Diagnostics;
using System.Windows;

//css_ref WpfUiLib

namespace Scripts.First
{
    public class TheFirstScript
    {
        public TheFirstScript()
        {
            var m = new WpfUiLib.SomeActions();
            m.ShowMessage("Message from another lib");
        }
    }
}

I attached a little solution. The test case is to start the WpfApp and try to compile/start the Scripts\TheFirst\TheFirstScript.cs It brings up an error.

Hope you have an idea on what I'm doing wrong.

Best regards Ralf

ScriptTest.zip

oleg-shilo commented 1 year ago

This is an interesting problem that is very easy to overlook. Let's dissect your code.

The the comment at the line #2.

var assembly = evaluator.CompileCode(File.ReadAllText(path));

// !!!! if you have reached this point your scripting is done. Now you have a fully loaded assembly ready for execution

var scriptClassType = assembly.ExportedTypes.FirstOrDefault(t => t.Name.EndsWith("Script"));
if (scriptClassType == null)
    throw new Exception("found no script class (that ends with 'Script')");

return Activator.CreateInstance(scriptClassType);

What you have is a canonical assembly probing problem that is not related to scripting in any way. You have an assembly loaded and ready for the execution. But when you instantiate a type from this assembly CLR is trying to locate all dependencies. CLR is looking for them in the entry assembly directory (your host app dir) and in the shared assemblies folder (equivalent of GAC). CLR has no idea that some other libraries are located in the TheFirst folder so it fails to locate it. CS-Script knows all the dependencies but CLR does not "ask" it :)

The solution is quite simple. You need to provide your custom assembly probing algorithm with AppDomain.Current.

Fortunately CS-Script simplifies this dramatically. Modify your code like this:

var assembly = evaluator.CompileCode(File.ReadAllText(path));
var scriptClassType = assembly.ExportedTypes.FirstOrDefault(t => t.Name.EndsWith("Script"));
if (scriptClassType == null)
    throw new Exception("found no script class (that ends with 'Script')");

using (SimpleAsmProbing.For(path.GetDirName()))
{
    return Activator.CreateInstance(scriptClassType);
}

And your project will start working as expected:

image

rp0815 commented 1 year ago

Thank you, nice solution.

oleg-shilo commented 1 year ago

It is really simple under the hood.

 AppDomain.CurrentDomain.AssemblyResolve += ( sender, args )=>
{
    return Directory.GetFiles("<probing dir>", arg.Name.Split(',').First()+".*")
                             .Select(file =>Assembly.LoadFile(file))
                             .FirstOrDefault();
}

And the IDisposable implementation makes it into a very readable intuitive syntax SimpleAsmProbing.For

rp0815 commented 1 year ago

Thanks again. I meanwhile had a little look at the code of SimpleAsmProbing and made a tiny tool class that fits our needs: no caching but logging errors.