Help with invoking C++ dll in Grasshopper component


#1

After browsing through many discussions on Grasshopper forums, I understand that here I can find the experts that will be able to help me with a problem I have encountered. I have built a dll in C++ which is called from within a GH component, this is the statement I am currently using in the top of my code:

[DllImport(“C:\TopoptForGH\MinC3dmgcg.dll”, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr RunTop3D(int nx, int ny, int nz, double sx, double sy, double sz,
double fr, double vf, double p, int nl, double[] Fin, int nFIXin, int[] FIXin);

Within the main component, the input variables are gathered from several other sub-components where the user can interactively make changes that affect their values. Finally, the component executes the procedure RunTop3D and organizes the output as follows:

protected override void SolveInstance(IGH_DataAccess DA)
{



IntPtr ResultPtr = new IntPtr(0);
ResultPtr = RunTop3D(nelx, nely, nelz, sx, sy, sz, filtrad,
volfrac, penal, nl, RHS, nsup, FIX);
double[] ResultArrayOut = new double[nelx * nely * nelz + 3];
Marshal.Copy(ResultPtr, ResultArrayOut, 0, ResultArrayOut.Length);
ResultPtr = IntPtr.Zero;



DA.SetDataList(0, ResultArrayOut);
}

The problem is: in its first run, the result returned is perfectly correct; in the following runs after the input variables are modified, the results are sometimes meaningless or simply wrong. I tested the same sequence of operations in a standard C# code and it worked fine after modifying input parameters. So I assume I’m doing something wrong with respect to the memory management in Grasshopper and the marshalling of variables. I must admit I am not an expert programmer and quite new to Grasshopper, but in our previous version things worked perfectly – however there were no C++ dll’s there. I understand that many built-in Grasshopper components actually invoke C++ dll’s, so I believe there should be a simple way to do it right – any chance you can give me some idea about what am I doing wrong?

Many thanks,
Oded


(Menno Deij - van Rijswijk) #2

When I look at your code, I see a problem with the result pointer. This is, as far as I can see, a memory leak. You allocate the memory in C++ and the code you show does not free it after the results have been copied into ResultArrayOut. If you know the length of ResultArrayOut (which is nelx*nely*nelz+3), it is better to use it as a function argument instead of a return argument. To make sure that the array is marshaled by pointer you can attribute it with [In, Out]

Secondly, because of possible garbage collection re-allocation (moving) of the ResultArrayOut during the operation in native code, you need to instruct the garbage collector that it should not move the array. This is done using the GCHandle (see code below).

In C++ your function would look like this

extern "C"
{
    bool RunTop3D(int nx, int ny, int nz, double dx, double sy, double sz, double fr, double vf, double p, int nI, double* Fin, int nFIXin, int* FIXin, double* resultArrayOut) { return true; // C++ code comes here
    }
}

And in C# the extern declaration is then

[DllImport("C:\TopoptForGH\MinC3dmgcg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool RunTop3D(int nx, int ny, int nz, double sx, double sy, double sz, double fr, double vf, double p, int nl, double[] Fin, int nFIXin, int[] FIXin,
[In, Out] double[] ResultArrayOut);

And you call it like

protected override void SolveInstance(IGH_DataAccess DA)
{
...
...
... 
// allocate the result array in C# (garbage collected after use)
double[] ResultArrayOut = new double[nelx * nely * nelz + 3];

// pin the result array with a GCHandle so GC does not move it
GCHandle handle = GCHandle.Alloc(ResultArrayOut, GCHandleType.Pinned);

// use a try-finally construct to always free the handle, even if an exception occurs
try {
    bool success = RunTop3D(nelx, nely, nelz, sx, sy, sz, filtrad, volfrac, penal, nl, RHS, nsup, FIX, ResultArrayOut);
}
finally {
    handle.Free();
}
...
...
...
DA.SetDataList(0, ResultArrayOut);
}

(Menno Deij - van Rijswijk) #3

And, depending on how the other arrays are used, you may need to attribute them with [In, Out] too and use the GC Handle on them as well.


#4

Many many thanks for this advice, I imagined that I was doing something wrong with respect to memory but didn’t have a clue of how to do it right.
I will modify my code as soon as possible and hopefully all will work smoothly :slight_smile:

Thanks again
Oded


#5

OK, I’ve now tried several approaches and still no luck. In a C# version I do get good behavior but it breaks down after a few consecutive calls to the imported function (with parameter changes in between). In the Grasshopper implementation it breaks down frequently, returning NaN in most entries of ResultArrayOut.

For clarity of the code, I grouped input data and now the dllimport looks like this:

[DllImport(“C:\Users\odedamir\Work\Research\architecture\TopoptGH2015\MinC3dmgcg\x64\Release\MinC3dmgcg.dll”, CallingConvention = CallingConvention.Cdecl)]
public static extern bool RunTop3D([In] int[] datai, [In] double[] datad, [In, Out] double[] ResultArrayOut);

Then I use three GC handles for the input/output:


// Organize integer data
int[] datai = new int[nsup + 5];


GCHandle handle1 = GCHandle.Alloc(datai, GCHandleType.Pinned);
// Organize double data
double[] datad = new double[ndof + 6];


GCHandle handle2 = GCHandle.Alloc(datad, GCHandleType.Pinned);
// Allocate the result array in C# (garbage collected after use)
double[] ResultArrayOut = new double[nelx * nely * nelz + 3];
// Pin the result array with a GCHandle so GC does not move it
GCHandle handle3 = GCHandle.Alloc(ResultArrayOut, GCHandleType.Pinned);


try
{
bool success = RunTop3D(datai, datad, ResultArrayOut);
}
finally
{
handle1.Free();
handle2.Free();
handle3.Free();
}


DA.SetDataList(0, ResultArrayOut);

Any idea why this still creates memory leaks? Or - am I missing something essential with respect to clearing memory in the dll itself? I didn’t code most of it myself but I did the conversion from a C++ application to a dll, and there’s extensive usage of the CHOLMOD library within.

Any advice will be deeply appreciated…


(Steve Baer) #6

I write a lot of pInvoke calls and never really use GCHandles for this. Do you know how big your ResultArrayOut is going to be before calling the C++ function? If you do, then allocate the array in C# first and pass it to the C++ function.


#7

If you’re ok with auto generated code, I’ve found SWIG to be a great tool for this kind of thing.


#8

I actually do know the size of ResultArrayOut and previously I tried to pass it to C++ as IntPtr.
This worked similarly to the current code – meaning ran perfectly in the first instance, once parameters were changed the returned array was wrong or just NaN.

Is there anything special that needs to be taken care of in the C++ side? Perhaps there is something I’m missing there?


(Steve Baer) #9

Here’s an example that may make things clearer.

Say in C++, you have a function that looks like

extern "C" __declspec(dllexport) void TweakArray(int count,  const int* inputArray, int* outputArray)
{
  //set values in output array
  if(inputArray && outputArray)
  {
    for( int i=0; i<count; i++ )
      outputArray[i] = inputArray[i]*2;
  }
}

The pInvoke call in C# would look like

[DllImport("myfuncs.dll", CallingConvention=CallingConvention.Cdecl )]
internal static extern void TweakArray(
      int count,
      int[] input,
      [MarshalAs(UnmanagedType.LPArray), In, Out] int[] output);

you would then call this in a C# function like the following

...
int count = 12;
int[] input = new int[count];
for( int i=0; i<count; i++ )
  input[i] = i;
int[] output = new int[count];
TweakArray(count, input, output);
for( int i=0; i<count; i++ )
  RhinoApp.WriteLine(output[i].ToString());
...