Its not that simple but basically what we have is 3 projects:
MyGrasshopper.Contracts
MyGrasshopper.Core
MyGrasshopper.Plugin
In the contracts library we have only interfaces and basically these need to be defined for any logic you want to consume in the Plugins library.
The Core library references the Contracts library and implements the interfaces.
The Plugin project references both Contracts and Core, but the ReferenceOutputAssembly is set to false for Core so that you can’t actually use it in your code, but it is copied as part of build so that you can load it at runtime.
In the contracts project we have 1 “master” type which exposes everything, we call this ICore. And in the Core project we implement ICore to expose all the Core logic we require in the Plugin project.
The we just pretty much have verbatim followed this tutorial Create a .NET Core application with plugins - .NET | Microsoft Learn
The following is from the tutorial the only difference is our naming is a little different (as we aren’t loading multiple).
using System;
using System.Reflection;
using System.Runtime.Loader;
namespace AppWithPlugin
{
class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
So PlugInLoadContext
was just removed and we new up the AssemblyDependencyResolver
in a constructor (as we only have 1).
The constructor is
public PluginLoadContext()
{
var pluginDll = Assembly.GetExecutingAssembly().Location;
var coreDll = Path.Combine(Path.GetDirectoryName(pluginDll)!, "Core",
"MyGrasshopper.Core.dll");
_resolver = new AssemblyDependencyResolver(coreDll);
}
Then we just have a helper method to create and instance of the “master” type Core
public static ICore LoadCore()
{
var loadContext = new CoreLoadContext();
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName("MyGrasshopper.Core"));
foreach (Type type in assembly.GetTypes())
{
if (typeof(ICore).IsAssignableFrom(type))
{
if (Activator.CreateInstance(type) is ICore result)
{
return result;
}
}
}
throw new ReflectionTypeLoadException(
new Type[] { typeof(ICore) },
null,
"Could not find ICore implementation"
);
}
Finally, we just threw it in the MyGrasshopperInfo type created by the grasshopper template as.
public class MyGrasshopperInfo : GH_AssemblyInfo
{
internal static readonly ICore Core = CoreLoadContext.LoadCore();
}
Then anywhere we need to use logic from our Core library in code it is:
MyGrasshopperInfo.Core.MyLogic()