It works fine and looks for the .dylib when I am simply debugging the app, but when I build, and placed the .gha and .dylib in the same directory as ~/Library/Application\ Support/McNeel/Rhinoceros/MacPlugIns/Grasshopper/Libraries/MyCustomComponent/MyCustomComponent.gha, it failed to look up the .dylib.
For now, when I place the .dylib under /usr/local/lib/, the GH component looks it up just fine. but it is better if the .dylib lives in the same directory as .gha.
How should I configure Visual Studio / Xamarin to include the .dylib in the same dir as .gha?
I tried it by adding libdl.dylib to access dlopen as following, but didn’t work…
[DllImport("libdl.dylib")] static extern IntPtr dlopen(String path, int mode);
[DllImport("libSimple.dylib")] static extern int SimpleFn(int a);
/// </summary>
public SimpleDynamicLibTestComponent()
: base("SimpleDynamicLibTest", "ASpi",
"Construct an Archimedean, or arithmetic, spiral given its radii and number of turns.",
"Curve", "Primitive")
{
const int RTLD_NOW = 2;
var ptr = dlopen("/Users/${USERNAME}/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/Grasshopper/Libraries/SimpleDynamicLibTest/libSimple.dylib", RTLD_NOW);
}
/// to store data in output parameters.</param>
protected override void SolveInstance(IGH_DataAccess DA)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Result from Simple Fn" + SimpleFn(1).ToString());
}
...
Even though the `ptr` is returning non-zero value, it still raised `1. Solution exception:libSimple.dylib`.
And realized that I need to get the handler to the .dylib then, get the pointer to the lib, then retrieve the function by its name.
// [DllImport("libSimple.dylib")] static extern int SimpleFn(int a);
IntPtr libraryHandle;
[DllImport("__Internal")]
public static extern IntPtr dlopen(string path, int flag);
[DllImport("__Internal")]
public static extern IntPtr dlsym(IntPtr handle, string symbolName);
[DllImport("__Internal")]
public static extern int dlclose(IntPtr handle);
public static IntPtr OpenLibrary(string path)
{
IntPtr handle = dlopen(path, 0);
if (handle == IntPtr.Zero)
{
throw new Exception("Couldn't open native library: " + path);
}
return handle;
}
public static void CloseLibrary(IntPtr libraryHandle)
{
dlclose(libraryHandle);
}
public static T GetDelegate<T>(IntPtr libraryHandle, string functionName) where T : class
{
IntPtr symbol = dlsym(libraryHandle, functionName);
if (symbol == IntPtr.Zero)
{
throw new Exception("Couldn't get function: " + functionName);
}
return Marshal.GetDelegateForFunctionPointer(symbol, typeof(T)) as T;
}
/// <summary>
/// Each implementation of GH_Component must provide a public
/// constructor without any arguments.
/// Category represents the Tab in which the component will appear,
/// Subcategory the panel. If you use non-existing tab or panel names,
/// new tabs/panels will automatically be created.
/// </summary>
public SimpleDynamicLibTestComponent()
: base("SimpleDynamicLibTest", "ASpi",
"Construct an Archimedean, or arithmetic, spiral given its radii and number of turns.",
"Curve", "Primitive")
{
libraryHandle = OpenLibrary("/Users/mnmly/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/Grasshopper/Libraries/SimpleDynamicLibTest/libSimple.dylib");
}
public delegate int SimpleFn(int a);
/// <summary>
/// Registers all the input parameters for this component.
/// </summary>
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
// Use the pManager object to register your input parameters.
// You can often supply default values when creating parameters.
// All parameters must have the correct access type. If you want
// to import lists or trees of values, modify the ParamAccess flag.
pManager.AddTextParameter("Test", "T", "Test", GH_ParamAccess.item);
pManager[0].Optional = true;
}
/// <summary>
/// Registers all the output parameters for this component.
/// </summary>
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
}
/// <summary>
/// This is the method that actually does the work.
/// </summary>
/// <param name="DA">The DA object can be used to retrieve data from input parameters and
/// to store data in output parameters.</param>
protected override void SolveInstance(IGH_DataAccess DA)
{
SimpleFn fn = GetDelegate<SimpleFn>(libraryHandle, "SimpleFn");
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Result from Simple Fn " + fn(10).ToString());
}
public override void RemovedFromDocument(GH_Document document)
{
CloseLibrary(libraryHandle);
base.RemovedFromDocument(document);
}
But then, the next question will be on how I should be getting the .gha's location dynamically.
From your own plug-in you can use the Assembly.GetExecutingAssembly().Location. You have access to your assembly at least through your implementation of GH_AssemblyInfo, which has a property called Assembly.
public SimpleDynamicLibTestComponent()
: base("SimpleDynamicLibTest", "ASpi",
"Construct an Archimedean, or arithmetic, spiral given its radii and number of turns.",
"Curve", "Primitive")
{
GH_AssemblyInfo info = Instances.ComponentServer.FindAssembly(new Guid("3a4acc40-c327-4de9-ac25-51b4014c9fd6")); // _Info.cs's GUID
var path = Path.GetDirectoryName(info.Location);
libraryHandle = OpenLibrary(path + "/libSimple.dylib");
}
Just an update, I also found out that I could keep using the DllImport("libName") attributes, by adding Directory.SetCurrentDirectory(ComponentDirectoryPath) as well.
[DllImport("libSimple.dylib")] static extern int SimpleFn(int a);
/// </summary>
public SimpleDynamicLibTestComponent()
: base("SimpleDynamicLibTest", "ASpi",
"Construct an Archimedean, or arithmetic, spiral given its radii and number of turns.",
"Curve", "Primitive")
{
GH_AssemblyInfo info = Instances.ComponentServer.FindAssembly(new Guid("3a4acc40-c327-4de9-ac25-51b4014c9fd6")); // _Info.cs's GUID
var path = Path.GetDirectoryName(info.Location);
// Setting the path to the current component directory, therefore `.dylib`s put on the same folder will be discovered.
Directory.SetCurrentDirectory(path);
}
/// to store data in output parameters.</param>
protected override void SolveInstance(IGH_DataAccess DA)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Result from Simple Fn " + SimpleFn(10).ToString());
}