oleg-shilo / cs-script

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

Supports both .Net Framework and .Net 5 #253

Closed yejinmo closed 2 years ago

yejinmo commented 3 years ago

In my solution, Linux platform uses .Net 5 and Windows platform uses .Net Framework 4.6.1. So I wrote a library based on .Net Standard 2.0 and added a CS-Script nuget reference to this project. But when I run the program on the Windows, I received the following error:

Could not load type 'System.Runtime.Loader.AssemblyLoadContext' from assembly 'System.Runtime.Loader, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at CSScripting.ReflectionExtensions.IsDynamic(Assembly asm)
   at CSScriptLib.EvaluatorBase`1.<>c.<ReferenceDomainAssemblies>b__54_1(Assembly x)
   at System.Linq.Enumerable.WhereArrayIterator`1.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at CSScriptLib.EvaluatorBase`1.ReferenceDomainAssemblies(DomainAssemblies assemblies)
   at CSScriptLib.EvaluatorBase`1..ctor()

It seems that .Net Framework does not support System.Runtime.Loader

https://github.com/dotnet/runtime/issues/22732

So is it possible to write only one library and support both .Net 5 and .Net Framework projects?

oleg-shilo commented 3 years ago

Yes it is possible. have a look at these examples: https://github.com/oleg-shilo/cs-script/tree/master/src/CSScriptLib/src

yejinmo commented 3 years ago

Thank you.

I added CS-Script 4.1.0 from Nuget in project Client.NET-Framework (Roslyn+CodeDom) Then I ran the project and got the same error:

Could not load type 'System.Runtime.Loader.AssemblyLoadContext' from assembly 'System.Runtime.Loader, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
yejinmo commented 3 years ago

error

oleg-shilo commented 3 years ago

It is a bug. The sample itself is also broken. Started working on it. Thank you for reporting

oleg-shilo commented 3 years ago

Solved. It is a very interesting and rather exotic side effect of assembly probing when invoking extension method of ReflectionExtensions class. On .NET Framework CLR only the methods of this class that are compatible with the runtime are invoked. Good. However, the whole class (type) itself has recently become dependant on AssemblyLoadContext because some other (.NET Core) methods have been added recently even though these methods are not invoked on .NET Framework.

A simple split of the class does solve the problem without even any change of the code:

// BEFORE
static class ReflectionExtensions
{
    public static void Method_for_net_framework(this string arg)
    {
    }

    static ReflectionExtensions()
    {
        var type = typeof (AssemblyLoadContext );
    } 

    public static void Method_for_net_5(this AssemblyLoadContext arg)
    {
    }
}

// AFTER
static class ReflectionExtensions
{
    public static void Method_for_net_framework(this string arg)
    {
    }
}

static class ReflectionExtensions_Dotnet_Core
{
    static ReflectionExtensions()
    {
        var type = typeof (AssemblyLoadContext );
    } 

    public static void Method_for_net_5(this AssemblyLoadContext arg)
    {
    }
}

Will prepare a hotfix nuget package on weekend.

oleg-shilo commented 3 years ago

Done. The prerelease package can be obtained either from the releases page or from NuGet: https://www.nuget.org/packages/CS-Script/4.1.1-pre

yejinmo commented 3 years ago

The problem is solved, thank you. But there is another question specific to .Net Framework, can I use Costura.Fody with CS-Script ? When I added Costura.Fody to the reference, the sample program threw an exception:

System.ArgumentException: 'The path is not of a legal form.'
   at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   at System.IO.Path.InternalGetDirectoryName(String path)
   at CSScripting.PathExtensions.GetDirName(String path)
   at CSScriptLib.CodeDomEvaluator.CompileAssemblyFromFileBatch_with_Csc(String[] fileNames, String[] ReferencedAssemblies, String outAssembly, Boolean IncludeDebugInformation, CompileInfo info)
   at CSScriptLib.CodeDomEvaluator.Compile(String scriptText, String scriptFile, CompileInfo info)
   at CSScriptLib.EvaluatorBase`1.CompileCode(String scriptText, String scriptFile, CompileInfo info)
   at CSScriptLib.EvaluatorBase`1.LoadMethod(String code)
   at ConsoleApp1.Program.Test_CodeDom() in D:\Project\CSScriptLib\src\Client.NET-Framework (Roslyn+CodeDom)\Program.cs:line 39
   at ConsoleApp1.Program.Main(String[] args) in D:\Project\CSScriptLib\src\Client.NET-Framework (Roslyn+CodeDom)\Program.cs:line 22

Costura Fody

For your sample program, I did not make any changes. I just added the Nuget references of CS-Script 4.1.1-pre and Costura.Fody 5.6.0 . On .Net Framework, people often use Costura.Fody to reduce the number of files, so if Costura.Fody can coexist with CS-Script, it will be a great thing. I'm not sure this is because the script compilation does not support the mechanism of Costura.Fody, a similar problem is here. Thank you again.

oleg-shilo commented 3 years ago

Fody is making strong use of custom assembly probing and also packing the dependency assemblies in the executable resources section. These are very clever techniques and CS-Script is also using asm probing heavily.

Though Foddy has a defect - it does not set the Assembly.Location property for the assemblies it embedded. This screws any assembly/nuget package that relies on this property for their own assembly probing (e.g. CSScriptLib). Interestingly enough Fody author advice to migrate to .NET Core that hase "singel-exe" feature outof box. And... .NET Core documentation advice not to use this feature if your app is relying on Reflection (our case).

Anyway, I have implemented the work around by reading the assembly location from Assembly.CodeBase if Assembly.Location is empty.

You can get the updated hot-fix package from NuGet.

=====================

Though, about the use of resources as with Fody... I got burnt by it very badly just a few days ago :( I had to completely rework my assembly bundeling Fody-like solution for my Multiclip product on Chocolaty because it treats embedded assemblies as a melware sign. Really silly :(

yejinmo commented 3 years ago

Problem is solved.But... another question:

Call CSScript.Evaluator.ReferenceAssemblyOf

System.Exception:“Current version of Roslyn-based evaluator does not support referencing assemblies which are not loaded from the file location.”

This is definitely a problem caused by Costura.Fody. The best solution may be to migrate all the code to the .Net platform and persuade all customers to upgrade their systems.

Another slightly compromised solution is to add CreateTemporaryAssemblies="true" to FodyWeavers.xml. https://github.com/Fody/Costura/issues/180

oleg-shilo commented 3 years ago

The best solution may be to migrate all the code to the .Net platform and persuade all customers to upgrade their systems.

Despite being a dramatic shift it is the right way of addressing the issues. Let's face it .NET Framework is over. But I do appreciate the gravity of such a decision when it comes to dealing with the existing customers 😢