clr.CompileModules giving an error in R8

When I try to run clr.CompileModules() to compile a .ghpy file in the Rhino 8 WIP (8.0.23003.14305, 2023-01-03), it’s giving me an error:

“Specified Method is not supported”. The error is on the same line as the clr.CompileModules() call.

I haven’t changed anything from my Rhino 7 setup AFAIK. I also did a small test which did not work either. Maybe the formatting changed? Having trouble finding an answer.

@piac, any ideas?

1 Like

Oh, I found this. I imagine this is at least part of the issue?

Hi Chris -

You could check that hypothesis by running the SetDotNetRuntime command and making Rhino 8 run in .NET Framework 4.8.
-wim

Thanks, Wim. This works, but I want to create ghpy files that actually run for users in Rhino 8.

@wim or @piac

Any more ideas about how compiling ghpy in the new Rhino 8 - NetCore - Grasshopper 2 universe will look?

Will this stuff be covered in the upcoming webinar for developers?

@chrislandau having the same issue here, did you find an alternative to clr.CompileModules ?

@wim The problem with setting the .NET runtime to Framework is that other users won’t do it when using our scripts, they will use the default .NET runtime offered by Rhino 8, which is .Net Core 7. And using .NET Core to load DLLs produced with .NET Framework doesn’t work well. I tried, and calling any function of a module compiled into the DLL produces the following error :

Could not load type 'System.Runtime.CompilerServices.Closure' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

And that’s to be expected, it’s pointless to try to mix .Net Framework 4.8 and .Net Core 7 projects, they aren’t compatible.

We need a way to compile DLLs with .NET Core 7, so it’s unfortunate that clr.CompileModules isn’t supported.

I’m still compiling with .NET 4.8, in Rhino 7, actually. But my plugin is running nicely in Rhino 8 for PC and Mac (Thanks @curtisw !) in what is presumably Net Core. I think this process works as long as you aren’t doing anything that is a breaking change between 4.8 and NetCore (.NET 7).

1 Like

Thank you for the quick reply !

I don’t understand, I made a very simple test that doesn’t seem to do anything that could be a breaking change in .NET 7, and yet I still get the same error on line 8 in using.py:

Could not load type 'System.Runtime.CompilerServices.Closure' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

Here are my files :

module.py contains the code to be compiled as a DLL.
compiler.py is where I call clr.CompileModules.
using.py is the simplest example I could find of how to use the DLL.

@chrislandau do you see anything wrong with my code ?

You are going to need to define a component somewhere using something that looks like what you get out of the ghpy component:

class MyComponent(component):
    def __new__(cls):
        instance = Grasshopper.Kernel.GH_Component.__new__(cls,....

Each component in your plugin needs it’s own py file as far as I know (unless someone can explain another way). Like: component_name.py

Not sure I understand this. I’m not trying to make GhPy files for Grasshopper components, I’m trying to make a DLL from a massive python script for use in Rhino, to obfuscate the code before a public distribution. It worked really well with Rhino 7.

I give up anyway, I spent the whole day on this. Rhino 8 is a mess, with the switch to python 3, the switch to .NET 7, the switch to the new script editor and the new package publishing that’s actually broken… I unpublished my script from food4rhino because I don’t want to publish a clear version for Rhino 8, and I don’t want people to harass me because there is no Rhino 8 support.

Hey @felix.mariotto, the reason you’re running into this is because IronPython’s implementation of clr.CompileModules uses that type in the compiled .dll which no longer exists in .NET Core. You can see some details of that issue here.

When loading ghpy’s, we automatically swap that type out with one of ours using Mono.Cecil when running in .NET Core. Since you’re not loading it as a ghpy you’d have to do this after compiling your .dll. We do something like this:

    public static Assembly LoadWithFixups(string assemblyPath)
    {
      var resolver = new CustomResolver();
      resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(Closure).Assembly.Location));
      resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(GH_Component).Assembly.Location));
      resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(HostUtils).Assembly.Location));

      var parameters = new ReaderParameters { AssemblyResolver = resolver };
      var assemblyDef = AssemblyDefinition.ReadAssembly(assemblyPath, parameters);

      // seems to be the only type we need to swap with so far.  There may be others.
      SwapTypes(assemblyDef.MainModule, "System.Runtime.CompilerServices.Closure", typeof(Closure));

      using (var stream = new MemoryStream())
      {
        assemblyDef.Write(stream);
        stream.Flush();
        var assembly = Assembly.Load(stream.ToArray());

        return assembly;
      }
    }

    private static void SwapTypes(ModuleDefinition module, string fullName, Type newType)
    {
      var assemblyRef = module.AssemblyReferences.FirstOrDefault(r => r.FullName == newType.Assembly.FullName);
      if (assemblyRef == null)
      {
        assemblyRef = AssemblyNameReference.Parse(newType.Assembly.FullName);
        module.AssemblyReferences.Add(assemblyRef);
      }

      foreach (var typeReference in module.GetTypeReferences())
      {
        if (typeReference.FullName == fullName)
        {
          typeReference.Name = newType.Name;
          typeReference.Namespace = newType.Namespace;
          typeReference.Scope = assemblyRef;
        }
      }
    }

The Closure type is very simple:

  public sealed class Closure
  {
    public readonly object[] Constants;
    public readonly object[] Locals;

    public Closure(object[] constants, object[] locals)
    {
      Constants = constants;
      Locals = locals;
    }
  }

You could probably turn this into a little utility to fix up your .dll’s to use your own implementation of Closure.

Hope this helps.

4 Likes

@curtisw Thank you for your detailed explanation !