views:

117

answers:

2

I am trying to wrap an unmanaged c++ interface composed of several abstract structs (with all pure virtual methods) and a small factory namespace which returns handles (shared_ptrs) to these structs.

It seems that, because of this, I might be able to get away with merely marshalling pointers through as System.IntPtr types (although them being of type boost::shared_ptr, would that be ok or do i need additional handling?) and passing them around to simple managed wrappers and then back into the native code without the need to every worry what they point to. Is this on the right track?

I would appreciate any help or references to data marshalling with pinvoke for STL types or shared_ptr types (all i can find is very little on MSDN and other sites on strings and structs of primitives.)

Thank you,

+1  A: 

I recommend defining a C API to be consumed by the .NET layer. Use void * or const void * for the "handle" types to your structures, and define an extern "C" function for each of the methods (including factory methods and the destructor). You can't marshal a shared_ptr<T> argument directly, so you need to use the void * handles.

In the .NET layer, first define a type derived from SafeHandle whose sole purpose is to dispose the handle correctly (i.e., call the C API representing the object destructor). Next, define a wrapper type that represents the actual object, and has methods for each of the C API functions that represent member functions of the object. Finally, define a factory type that wraps the C API factory methods.

It's a lot of work, but this is the correct way to do it.

Stephen Cleary
I have been following your suggestion Stephen, thank you for that. I have come across a difficulty and thought I'd follow up with it here...some of the functions in the interface return shared_ptr types, with the C API i have been retrieving the raw pointer from within and returning that instead to get it on the .NET side as a System.IntPtr. Problem is, some functions in the native interface take a shared_ptr as an argument, and i don't think i can "rewrap a raw pointer into it's previously shared form" - I'm not quite sure how to handle this..any suggestions?Thank you
Palace
An idea i have would be, rather than return the shared_ptr::get() raw pointers...maybe i should return the address of the shared_ptr object as a raw pointer..because i'm wrapping an interface the only way ill actually use that is by passing it back to some "method call" which when it arrives back to the C Style API would use the raw pointer to a smart pointer ptr as: (*ptr)->methodName()Does this seem like a reasonable approach?
Palace
Yes, I'd return a pointer to a heap-allocated `shared_ptr<T>`. The C API constructors would create a *copy* of the `shared_ptr<T>` returned by the factory method: `return new shared_ptr<T>(factory.Create());` - note that we're purposely "leaking" to the C# code. Other methods would use it as `shared_ptr<T> * const ptr = (shared_ptr<T> *)(handle); (*ptr)->method()` as you suggested. The C API destructor would just delete the pointer: `delete ptr;`, closing the "leak". The "leak" is necessary because C# can't directly use the reference count in the `shared_ptr`.
Stephen Cleary
Ok phew, i'm glad you think this approach is ok because it does feel pretty sketchy memory-management wise. Thank you for your help.
Palace
So far I've been getting away by returning pointers (even to shared_ptr objects) and using them only as handles in c# and passing them back to native code...but say a c++ function returns a std::vector of stuff, ideally i'd like to translate this to a collection of pointers and not a pointer to the native vector...how can i go about doing that? Thanks
Palace
When you return a pointer to a `shared_ptr<T>`, you're treating it as an opaque handle; the C# code doesn't know it's point to a `shared_ptr<T>` and doesn't care. `vector` is different; the C# code needs to be aware of the structure. Unfortunately, the only "collections" supported by p/Invoke are arrays. To have a C API return an array, it's usually best to have the caller pass a pointer (to the beginning of the memory for the array) and the number of elements in the array. The C API then copies the `vector` data into that memory buffer (or fails and requests more room if there isn't enough).
Stephen Cleary
That's the common pattern. When you have a C API that takes a pointer/length combo, then you can have the C# p/Invoke just pass the pointer argument as `LPArray` and have it refer to the size argument, like this: `static extern int GetArrayData(int count, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] myData[] data, ref int requestedCount);`. This works for a C API that is like this: `extern "C" int GetArrayData(int count, MyData * const data, int * const requestedCount)`, using the return value as an error/status and - if the status is NotEnoughRoom - setting `*requestedCount` too.
Stephen Cleary
A: 

With COM you can only marshal/unmarshal COM data types -> you interface should be COM convertible (see this for an example of building a COM object in C++).

This does not mean that in the implementation of the interface you cannot use stl or boost types, it's just that your interface definition should only consist of COM convertible types. See this for a list of C++ data types and their COM equivalent.

If you will be using a COM object you will not need to PInvoke from .net, as you can directly import a COM object in .net from Visual Studio (Add reference-> COM).

You need to use PInvoke if you are doing direct calls to unmanaged code. See this for a starter on PInvoke. See this question for a discussion about calling unmanaged code that uses STL containers.

Ando