.NET C++ Bridge Code


(Steve Baer) #1

Continuing the discussion from Hyphenated commands:

I used to write scripts to automatically generate “bridge” code, but it always ended up (putting it kindly) not very good to work with. I now write the code by hand.

It is much easier to write .NET code to access C++ than C++ code accessing .NET so I would recommend always going in that direction if at all possible. Much of RhinoCommon is written as a thin layer of C# that accesses C++ code through pinvoke. You can see how this is done by looking at the code on the github site

RhinoCommon is composed of a C# DLL and a C++ DLL (well, actually two C+ DLLs but who’s counting.) I first start by writing an exported C function so you don’t get C++ name mangling which you can see if you look at the .cpp files in the github site. I wrote a simple application called methodgen which parses the cpp files and generates a large set of C# pinvoke functions. methodgen is also available in the RhinoCommon open source code. The final step is to write C# classes that call the pinvoke functions, but are written in a clean .NET SDK style.

Hope that helps lay out the process for writing the bridge code.


RhinoCommon with native DLLs
(Dale Fugier) #2

Hi Dietrich,

Here is a sample solution that demonstrates how to share a common C++ library between a Rhino C++ plug-in and a RhinoCommon .NET plug-in written in C#.


#3

Hi Steve and Dale!

Thank you very much for these elaborated explanations! Together with the rhinocommon code and the Moose example this should be a big help when writing the lisp interface! Maybe I even can reuse some of your efforts, for example the c wrappers for the C++ code.

ECL allows to embed C or C++ code directly into the lisp code. So it is possible to write, for example, a ‘make-cone’ function in lisp which contains the C++ code necessary for creating and manipulating the Rhino objects:

(defun make-cone (height radius b-cap-bottom)
  (ffi:c-inline
   (height radius b-cap-bottom) (:double :double :bool) :void
    ON_Plane              plane      = ON_xy_plane;
    double                height     = #0;
    double                radius     = #1;
    BOOL                  bCapBottom = #2;
    const CRhinoCommandContext *context = RhinoLispContext::getRhinoCommandContext();

    ON_Cone cone(plane, height, radius);
    if(cone.IsValid()) {

      ON_Brep* cone_brep = ON_BrepCone(cone, bCapBottom);
      if(cone_brep) {

        CRhinoBrepObject* cone_object = new CRhinoBrepObject();
        cone_object->SetBrep(cone_brep);
        context->m_doc.AddObject(cone_object);
        context->m_doc.Redraw();
      }
    }
   "
   :one-liner nil
   :side-effects t))

I would suppose that the method name together with its signature / type of the parameters and return type should be enough to select the right method? Why are there still problems with name mangling?

So if I understand right, there are three steps? First the C wrappers, than the C# code generated from the wrappers by methodgen, and finally the C# classes which are based on the generated C# wrappers? And while the second step is automated, the C wrappers as well as the final C# classes are hand-crafted? That sounds like lot or work! No wonder that the API is as nice as it is if you put this amount of effort into it! Thanks a lot for the good work :slight_smile:

Concerning the access of the C++ code through pinvoke, is this necessary as well for the memory management? I had the impression that the C++ code itself dynamically manages its memory and that objects are garbage collected when not needed anymore? When I create, for example, some Rhino object in lisp and wrap the reference into a lisp object which immediatly after goes out of scope, do I have to care about the created Rhino object and its destruction myself?

There is still a lot to think about before writing my own wrappers. Should I base my interface on functions or classes? How to deal with the different constructors for Rhino objects? The best might be to just start with something and learn by experimenting with the different possibilities. So I probably will come back soon with more questions concerning your explanations :slight_smile:

Thanks a lot,
Dietrich


(Dale Fugier) #4

C++ does not have built-in garbage collection. I’m sure if you Google the topic, you will find lots of reasons why.

If you allocate memory on the stack:

double d = 3.14159;

Then the memory is recovered when the variable goes out of scope.

If you allocate memory on the heap:

double* d = new double;
*d = 3.14159;

Then the memory is not recovered, and you are responsible for deleting it later (or leak memory).

delete d;

#5

Hi Dale,

Sorry for my misunderstanding :). I started to use Microsoft and Visual C++ only recently in order to be able to use Rhino. For some reason I had the impression that Rhino relies on the common language infrastructure and its built-in memory management. Skimming through articles like this “C++: The Most Powerful Language for .NET Framework Programming” ( http://msdn.microsoft.com/en-us/library/ms379617.aspx ) under time pressure and without the necessary care might be the reason…

But what actually is the motivation to not rely on CLI’s memory management? Why do you restrict yourself to the standard C++ rather than using C++/CLI? Rhino relies on the .NET framework anyway. Wouldn’t it make the implementation of RhinoScript, RhinoPython etc. easier if you where using CLI’s extensions for the Rhino kernel and the OpenNURBS library as well? Are the reasons historical? Or do you want OpenNURBS to be compatible with standard C++? Is the state of Mono on other platforms the reason?

I thought I could implement the lisp API by simply wrapping pointers to Rhino objects - and leaving the rest to the garbage collector… Too bad, that makes everything more difficult :slight_smile:

Thanks for your friendly explanations, Dietrich


(Dale Fugier) #6

Core Rhino does not use the .NET Framework. Core Rhino is an unmanaged C++ application. There are, though, components used by Rhino that do use .NET.


(Steve Baer) #7

C++/CLI still does not do memory management for you if you are using “native” C++ classes. Only ref classes are managed by the .NET runtime. Our original .NET SDK was written in C++/CLI and I found it to be a major pain:

  1. C++/CLI is very Microsoft specific, so good luck getting it to compile anywhere else
  2. C++/CLI is pretty obscure which makes it somewhat hard to get other developers involved in the code
  3. Visual Studio 2010 messed up and couldn’t support intellisense for C++/CLI
    which made typing on the code pretty cumbersome

#8

Thanks! That is a clear statement. Just from reading the Microsoft documentation I had the impression that C++/CLI and .NET just work perfectly together.

How about .NET? Are you happy with this framework? Or would you choose something different after your experiences with porting Rhino to MAC? I suppose that there are quite a number of differences between .NET and Mono which make this task quite painful as well?

On the other side after developing RhinoScript .NET probably makes it easy to implement other scripting languages like Python and F#?


(Menno Deij - van Rijswijk) #9

Differences between .NET and Mono are listed here http://mono-project.com/Compatibility
Basically, the .NET 4 framework is almost completely supported. The main thing I guess for Rhino development is that there is and will be no WPF (windows GUI framework). But, if you employ the MVVM design pattern when creating WPF UI elements, you only need to replace the UI; all the business logic can be reused.


(Steve Baer) #10

I’m very happy with .NET and am happy with the progress we are making using it on OSX.

RhinoScript is actually COM based and doesn’t have much of anything to do with .NET


#11

Thanks for the details! Too bad that WPF is not supported under Mono…


#12

That sounds better. These explanations get more and more interesting and I would love to inquire further… But I better stop here and let you work :slight_smile: Thanks again, Dietrich