How to pass C# object references to and from C++
Asked Answered
C

2

5

Is there any way to pass C# object references (class types, not structs) into and out of C++ via native interop?

Here's my use case:

I'm trying to write a game engine in C# but want to use a native (C++) physics library. The physics library maintains its own internal state of all the physical bodies and lets the user associate a small amount of data (a pointer) with each body. After a physics simulation tick, the library supplies a list of all physical bodies that moved. I want to iterate over this list and relay all the changes to the corresponding C# objects.

The question is: what's the best way to associate objects in C# with the corresponding physical bodies in C++?

I can think of a couple ways:

  1. Give each physical body an ID of some kind, associate the ID with the physical body in C++, and maintain a map of ID's to corresponding objects in C#. This seems unnecessarily indirect to me, even with an optimized mapping mechanism.
  2. Take advantage of the ability to marshal C# delegates (which can implicitly reference C# object instances) into C++ function pointers. This might be the best way to do it; I don't know how C# implements delegates and what type of overhead this would entail.
  3. Find some other way to refer to C# objects in C++ and associate that reference with each native physical body.

Is there any mechanism like option 3 I don't know of? C++/CLI isn't an option since I want to support platforms without it.

Chantilly answered 19/8, 2015 at 20:29 Comment(5)
if C++/CLI is no option, P/Invoke is the next thing to consider. refering to c# objects is not really possible though via P/invoke, and delegates might also be hard or impossible. but maybe you can get something out of it.Mceachern
Yeah, P/Invoke is what I meant when I said "native interop"; I didn't realize it had a name. Passing C# delegates into C++ as function pointers is totally doable with P/Invoke, I just don't know what kind of overhead it has. msdn.microsoft.com/en-us/library/367eeye0.aspxChantilly
Found another answer describing some of the (perceived) performance characteristics of delegates. stackoverflow.com/a/2082975 Maybe using them is the best way to implement this. While iterating over the list of changed bodies in C++, just call one corresponding delegate for each to get the new data into C#-land.Chantilly
Pinvoke is going the wrong way, helpful to call native code from C# but not the other way around. Giving native C++ any idea what a foreign type system looks like in a cross-platform way is just not in the cards. Microsoft does that pretty well with COM and C++/CX but the *nix people won't have anything to do with it. Not invented there.Moseley
The C++ code doesn't really need to know anything about the C# type system; an opaque pointer that it could return back to C# would do the job in this case. Just something that says "hey, you know that thing you told me about?" I imagine it's only not available because of garbage collection and relocation.Chantilly
T
8

I would suggest using the tool that was specifically designed for situations like that, i.e. System.Runtime.InteropServices.GCHandle.

Usage

In:

  1. Allocate a GCHandle for your CLR object with GCHandleType.Normal or GCHandleType.Weak.
  2. Convert the GCHandle to an IntPtr via GCHandle.ToIntPtr.
  3. Pass that IntPtr to the C++ side as the userdata pointer that you mentioned.

Out:

  1. Get IntPtr back from the C++ side.
  2. Convert the IntPtr back to a GCHandle via GCHandle.FromIntPtr
  3. Use the Target Property of the GCHandle to get to the original CLR object.

When the CLR object is no longer being referenced from the C++ side (either because the C++ object was destroyed or because you reset the userdata pointer to e.g. IntPtr.Zero), release the GCHandle by calling GCHandle.Free.

What type of GCHandle you should allocate depends on whether you want the GCHandle to keep the object alive (GCHandleType.Normal) or not (GCHandleType.Weak).

Terrellterrena answered 20/8, 2015 at 1:43 Comment(5)
In the delegate case, I could just call the delegate from C++ and use this on the C# side to get the object reference. But that's beside the point; the point of having an object reference in this case is so I can call a method on it. If I can just call the method directly, it gets the job done.Chantilly
I didn't know about the GCHandle stuff. I'll look into those. Thanks!Chantilly
GCHandle seems to be exactly what I was looking for. If you get rid of the delegate part of your answer, I'll accept this answer.Chantilly
@JoBates Done. I'm not sure that removing that part improves my answer, but since I'm a greedy bastard... :)Terrellterrena
Sorry for the conditional acceptance. I already had a way to use delegates that I know works; the real question was "are there any more direct alternatives?", and GCHandle alone answers that perfectly.Chantilly
N
2
  1. Mapping a unique identifier would allow you to isolate the managed from the unmanaged code. Its probably better if the unmanaged side isn't doing anything with your managed objects since you really can't control much of the object's lifecycle.
  2. Delegates as function pointers are possible. All you need to do is define your delegate type and create an instance. Then you can use Marshal.GetFunctionPointerForDelegate to get an address to pass to the unmanaged side. The only thing to note is that you have to make sure that the callback is using __stdcall.
  3. It's probably possible to access class members and functions directly, but without a C++/CLI wrapper you're playing with fire.
  4. Allocate a chunk of memory on the C# side and pass that to C++. In the classes on the managed side use properties to access this memory with appropriate offsets.
  5. Make the object COM visible.

Also, remember that AccessViolationExceptions are generally not catchable in .NET.

Norbertonorbie answered 19/8, 2015 at 21:22 Comment(4)
#4's pretty interesting. I had considered allocating a chunk of memory for each object, but quickly rejected the idea because there would be no way to have a "fixed" statement for each one. If they all shared one single chunk of memory, though...Chantilly
By allocate a chunk I was thinking something along the lines of Marshal.AllocCoTaskMem or Marshal.AllocHGlobal.Norbertonorbie
Whoa, I didn't know those methods existed. Thanks! I can think of a couple other places in my code where they'd be useful. But now that I think about it, even if they weren't available, I could have easily implemented AllocHGlobal and FreeHGlobal myself.Chantilly
If you're feeling really ambitious, you can also use any of the WinAPI Memory Management Functions using p/Invoke.Norbertonorbie

© 2022 - 2024 — McMap. All rights reserved.