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.
@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).
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'.
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 ?
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.