C#/C++ OpenNURBS - Getting an ON_NurbsCurve from an IntPtr

Hi again,

I am able to get a pointer to a managed Brep and use it as an ON_Brep* in C++:

C++

int brep_get_num_verts(ON_Brep* brep)
{
    // Useless test function
    int N = 0;
    // Iterate over the vertices just to make sure we can
    for (int i = 0; i < brep->m_V.Count(); ++i)
    {
        ON_BrepVertex& vert = brep->m_V[i];
        N++;
    }
    return N; // Returns the correct number of vertices
}

C#

[DllImport(Api.ApiPath, SetLastError=false, CallingConvention=CallingConvention.Cdecl)]
private static extern int brep_get_num_verts(IntPtr brep);

Brep b = ... // A valid Brep from Rhino
IntPtr ptr = Rhino.Runtime.Interop.NativeGeometryConstPointer(b);
int N = brep_get_num_verts(ptr); // success

This works fine. It also works in a similar way with MeshON_Mesh*, also using Rhino.Runtime.Interop.NativeGeometryConstPointer().

But I can’t get this to work:

C++

const char* inspect_nurbscurve(ON_Curve* curve)
{
    // Useless function to test casting
    std::stringstream str;
    // Sanity check to make sure the type is correct. It is TL_NurbsCurve as expected.
    str << curve->ClassId()->ClassName() << std::endl;

    // Try to explicitly cast it to ON_NurbsCurve, but...
    ON_NurbsCurve* nurbs_curve = ON_NurbsCurve::Cast(curve);

    if (nullptr == nurbs_curve) // ... FAILS HERE
        str << "Couldn't cast ON_Curve to ON_NurbsCurve: " << curve->ClassId()->ClassName() << " " << curve->ClassId()->ClassIdVersion() << std::endl;
    else
    {
        // Write some data about the NurbsCurve
        str << "Curve points: " << nurbs_curve->CVCount() << std::endl;
        str << "Knots: " << nurbs_curve->KnotCount() << std::endl;
    }

    // Boilerplate stuff to return a string
    const std::string tmp = str.str();
    ULONG ulSize = strlen(tmp.c_str()) + sizeof(char);
    char* pszReturn = NULL;
    pszReturn = (char*)::CoTaskMemAlloc(ulSize);
    if (pszReturn != nullptr)
        strcpy_s(pszReturn, ulSize, tmp.c_str());
    return pszReturn;
}

C#

[DllImport(Api.ApiPath, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string inspect_nurbscurve(IntPtr curve);

NurbsCurve crv = ... // A valid NurbsCurve from Rhino
IntPtr ptr = Rhino.Runtime.Interop.NativeGeometryConstPointer(crv);
string res = inspect_nurbscurve(ptr); // fails

Always returns "Couldn't cast ON_Curve to ..."

It never yields a ON_NurbsCurve correctly. I tried swapping out ON_Curve* for ON_Object*, ON_Geometry*, and ON_NurbsCurve* and cast from those, but with the same problem.

I could not find an example in the OpenNURBS docs that handles this type of situation.

I suspect it may have something to do with the pointer to the managed object being moved around, but in that case it should not work either for ON_Brep* or ON_Mesh*

I have seen references to something like:

ON_Curve* curve = ...
ON_NurbsCurve nurbs_curve;
  if ( 0 == curve ->GetNurbForm(nurbs_curve) )

but this always throws an access violation error for me, suggesting that either nurbs_curve isn’t properly initialized or that something is wrong with curve.

Is there something basic that I am missing, or a more proper way that I should be managing this? Again, no problems with ON_Brep* or ON_Mesh*, both of which also inherit from ON_Geometry

Thanks!

The issue might be related to the fact that ON_NurbsCurve is not a derived class of ON_Curve, but a separate class that is not related to ON_Curve in the inheritance hierarchy. Therefore, casting an ON_Curve pointer to an ON_NurbsCurve pointer might not work.

Looks like it is:
image

ON_Curve inherits from ON_Geometry, which in turn inherits from ON_Object.

At least that is the way things appear…

@dale Could you please clarify this one?

Hi @tom_svilans,

I’ve added a few new samples to my Moose project. You might pull and review.

– Dale

Thanks @dale!

Unfortunately, I lifted the ON_NurbsCurve_Inspect function from your latest example and it doesn’t work.

  • Line 0 - In GH, printing out the curve object to make sure it is, in fact, a NurbsCurvePrint("{0}", crv);
  • Line 1 - The output from my inspect_nurbscurve function in C++:
    • str << nurbs_curve->ClassId()->ClassName() << std::endl;TL_NurbsCurve
    • nurbs_curve->Degree() → 0
    • nurbs_curve->IsValid() → 1
    • nurbs_curve->CVCount() → 0
    • nurbs_curve->KnotCount() → 1
  • Line 2 - The output from the function I lifted from Moose → ON_NurbsCurve_inspect()Print("{0} : points {1} knots {2}", crv, pc, kc);
  • Line 3 - The output from GH → Print("{0} : points {1} knots {2}", crv, crv.Points.Count, crv.Knots.Count);

Maybe some more relevant info:

  • I pulled the OpenNURBS Github repository and built it in Visual Studio 2022, using the 2019 compiler (14.2) on x64 Windows. It seems to have built OK, and other functions - such as accessing ON_Brep and ON_Mesh objects seem to work fine.
  • I am running a recent build of Rhino 7 (7.26.23009.7001, 2023-01-09)
  • My .NET wrapper uses .NET Framework 4.8

Could it be some version mismatch between Rhino and the newest OpenNURBS?

I see that your stdafx.h header includes RhinoSdk.h and rhinoSdkPlugInLinkingPragmas.h - I have simply followed the instructions in the OpenNURBS repo and am only including opennurbs_public.h and defining OPENNURBS_IMPORTS and OPENNURBS_PUBLIC_INSTALL_DIR for all the linking stuff. Is the Rhino SDK required for marshaling pointers across yonder management divide?

Digging in the forum a bit more, it seems like I should indeed be using the Rhino SDK rather than the OpenNURBS from the Github repo. I will give it a go.

2 Likes