RhinoCommon Visual Studio Extension to wrap C++ algorithm in C#

Hello to the community,

I am developing the implementation of a C++ algorithm as a grasshopper component using the RhinoCommon Visual Studio Extension. I am primarily following XingxinHE instructions on github (I highly recommend it: XingxinHE/CGAL_IN_GRASSHOPPER: Wrapping C++ native code in C# with example of CGAL in Grasshopper.:); in a nutshell it works like this: the RhinoCommon Visual Studio Extension solution contains three projects. The first is a C# project containing the definition of the inputs, outputs and computations of the grasshopper module. The second is a C++ project containing the header and source files to the C++ code. Finally a C# class project organizes the communication between the first two projects.

I have some questions regarding the correct usage of the RhinoCommon Visual Studio Extension, correct usage of Visual Studio, as well as C++ coding in general:

  • Question 1: Regarding best practice in using the RhinoCommon Visual Studio Extension: Say I have defined an algorithm (in the form of header and c++ files, containing functions making up the algorithm) which uses libraries like Geometry-Central, Eigen, etc.. Is this the best outset to use the RhinoVisualStudioExtension or would I do better to somehow structure the project differently?

  • Question 2: Would I do better (Option A) to compile that algorithm and somehow include that build inside the MFC dynamic Link Library project, or (Option B) would I rather just move the header and C++ files to the “Header Files” and “Source Files” folders of the MFC dynamic Link Library and build it all together in the end ?

  • Question 3: To introduce a library being used in the C++ project I have to give the path to the headers (in solution explorer with: C++ project ->properties (Configuration “All Configurations” and Platform “All Platforms”)-> Configuration Properties → C/C++ → General → Additional include directories) and the .lib (in solution explorer with: C++ project ->properties (Configuration “All Configurations” and Platform “All Platforms”)-> Configuration Properties → Linker → General → Additional Library Directories), as well as the .lib name itself (… → Linker → Input → Additional Dependencies). For header-only libraries I only give the path to the headers. Is this the correct and complete approach?

  • Question 4: What is the best ressource for understanding the purpose and functioning of the macros used to declare the C++ code:
    // Windows build
    #if defined (_WIN32)
    #if defined (SFCNATIVE_DLL_EXPORTS)
    #define SFCNATIVE_CPP_CLASS __declspec(dllexport)
    #define SFCNATIVE_CPP_FUNCTION __declspec(dllexport)
    #define SFCNATIVE_C_FUNCTION extern “C” __declspec(dllexport)
    #else
    #define SFCNATIVE_CPP_CLASS __declspec(dllimport)
    #define SFCNATIVE_CPP_FUNCTION __declspec(dllimport)
    #define SFCNATIVE_C_FUNCTION extern “C” declspec(dllimport)
    #endif // SFCNATIVE_DLL_EXPORTS
    #endif // WIN32
    // Apple build
    #if defined(APPLE)
    #define SFCNATIVE_CPP_CLASS attribute ((visibility (“default”)))
    #define SFCNATIVE_CPP_FUNCTION ¨__attribute
    ((visibility (“default”)))
    #define SFCNATIVE_C_FUNCTION extern “C” attribute ((visibility (“default”)))
    #endif // __ APPLE

My questions are not only rhino specific but also touch on foundational knowledge in C++ and visual studio and as such might be misplaced here. I would appreciate any pointers towards the answers to those questions.

Thank you for your support!

Kind regards,

Merlin

Hi,

I’m not rating the referenced project. It looks like a good solution, but its just one out of many. By all the abstraction involved, it all boils down to use p/invoke and marshalling, to call unmanaged C++ code in (managed) C#.

I think it is good practise to clearly separate concerns and develop your C++ in isolation. I don’t know if you need a dedicated wrapper project. But its one option.

If you think about it, its just creating a dynamic C++ library (“dll” or “dylib”) and then calling this shared library by its exposed interface in C#. It shouldn’t matter how you build/structure/maintain the library. I think even the C++ compiler might be irrelevant, if you build it in a way that it doesn’t need any Rhino library code.
You can also call System libraries the same way. Just be aware that the interop overhead may eliminates a performance gain you expect from C++. If this is the reason behind a multi-language approach.

1 Like

Hi Tom,

thank you for the response and clarification.

its just creating a dynamic C++ library (“dll” or “dylib”) and then calling this shared library by its exposed interface in C#

Would I then call this shared library by its exposed interface in the “SolveInstance” section of the C# project of the RhinoCommon Visual Studio Extension? Do you know of a tutorial/example where this is done in the RhinoCommon Visual Studio Extension?

if you build it in a way that it doesn’t need any Rhino library code.

It doesn’t use any Rhino Library code.

If this is the reason behind a multi-language approach.

I am working on this solution because rebuilding the algorithm in C# would be cumbersome.

Thank you for the input!

Merlin

Realising the scope of my problem I found a ton of other ressources in other posts on this forum. Thank you Tom!

Yes, I would even say this forum is not the best for this topic. Since many users are not software developers, but designers/architects with programming knowledge.

You can tinker around with p/invoke if you just create a simple C# console application, where you try to load an arbitrary C++ library.

Once you understand how it works, the complexity will disappear and you can use it wherever you want. It even works in script components. It works with C#, IronPython, Python3 etc. I think its quite a common problem to run C/C++ code from a high-level language.

Hello to the community,

having made a lot of progress, but also some setbacks, I return with questions about the proper usage of the RhinoCommon Visual Studio Extension.

My first question:
Within the main C# document I am “declaring” the function like this:

    internal static class SfcInterop
    {
        [DllImport("sfc.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int Compute_surface_filling_curves(
            UIntPtr iterations,
            // Mesh data
            [In] double[] vertXyzArray,
            UIntPtr vertCount,
            [In] int[] faceIndexArray,
            UIntPtr facesCount,
            // Curve data
            [In] int[] polylines_vertices_flattened_array,
            [In] int[] polylines_nodes_counts,
            UIntPtr polylines_count,
            [In] int[] _FixedPoints,
            UIntPtr _FixedPointsCount,
            // Fields
            [In] double[] _vField,
            UIntPtr _vFieldCount,
            [In] double[] _smoothedFunction,
            UIntPtr _smoothedFunctionCount,
            // Parameters
            double p,
            double q,
            double w_fieldAlignedness,
            double w_curvatureAlignedness,
            double w_bilaplacian,
            [MarshalAs(UnmanagedType.I1)] bool useAnisotropicAlphaOnMesh,
            [MarshalAs(UnmanagedType.I1)] bool useGeodesicMedialAxis,
            double radius,
            double rmax
        );
    }

In the same file, but within the protected override void SolveInstance(IGH_DataAccess DA) section the function is called like this:

      if (run)
        {
            int result = SfcInterop.Compute_surface_filling_curves(
                (UIntPtr)iterations,
                vertXyzArray, (UIntPtr)(vertXyzArray.Length / 3),
                faceIndexArray, (UIntPtr)(faceIndexArray.Length / 3),
                polylines_vertices_flattened_array,
                polylines_nodes_counts,
                (UIntPtr)polylines_count,
                FixedPoints,
                (UIntPtr)FixedPointsCount,
                vField,
                (UIntPtr)_vFieldCount,
                alphaRatioMesh,
                (UIntPtr)alphaRatioMeshCount,
                p,
                q,
                w_fieldAlignedness,
                w_curvatureAlignedness,
                w_bilaplacian,
                useAnisotropicAlphaOnMesh,
                useGeodesicMedialAxis,
                Radius,
                MaxRadius
            );

            DA.SetData(0, result); // Optional: output the result
        }

Is this usage of the function correct, or would I do better to create a seperate C# “Wrapper” project?

My next question is:
I have placed the .dll in the same directory as my grasshopper assembly file (C:\Users\merli\AppData\Roaming\Grasshopper\Libraries\sfc.Grasshopper.Win). I built this .dll in Visual Studio, and it itself is reliant on other libraries:

`└── Documents\

        └── Projects\
            └── surface_filling_curves_cpp\
                └── build\
                    ├── bin\
                    │   └── Debug\
                    │       └── sfc.dll
                    └── lib\
                        └── Debug\
                            ├── geometry-central.lib
                            ├── modules.lib
                            ├── predicates.lib
                            ├── sfc.lib
                            ├── tetgen.lib
                            └── triangle.lib`

I am wondering as to wether these libraries need to be referenced aswell.

Thank you for the assistance,

Merlin