views:

448

answers:

6

Have a moderate size (40-odd function) C API that needs to be called from a C# project. The functions logically break up to form a few classes that will be API presented to the rest of the project.

Are there any objective reasons to prefer P/Invoke or C++/CLI for the interoperability underneath that API, in terms of robustness, maintainability, deployment, ...?

The issues I could think of that might be, but aren't problematic are:

  • C++/CLI will require an separate assembly, the P/Invoke classes can be in the main assembly. (We've already got multiple assemblies and there'll be the C dlls anyway so not a major issue).
  • Performance doesn't seem differ noticeable between the two methods.

Issues that I'm not sure about are:

  • My feeling is C++/CLI will be easier to debug if there's inter-op problem, is this true?
  • Language familiarity enough people know C# and C++ but knowledge of details of C++/CLI are rarer here.

Anything else?

+4  A: 

In the case where I am working with an existing C library I prefer PInvoke. PInvoke, while a bit tedious and troublesome at times, is a fairly well understood technology that has a ever growing set of tools and internet documentation avaliable. Generally speaking whatever problem you run into, there is already a sample available on the web or a quick check on stack overflow will provide a solution.

C++/CLI is a great technology but IMHO it's documentation is limited as compared to PInvoke for interop specific scenarios. It also doesn't have the tooling infrastructure for interop solutions that PInvoke has. Adding a C++/CLI assembly for a scenario that can be solved with PInvoke just seems too costly to me.

On the other hand if I'm working with a large C++ library I consider C++/CLI a bit more. PInvoke does not work with C++ and hence I must end up adding some kind of intermediate layer. Either a small C layer to wrap all of the C++ function calls or a C++/CLI library to bridge the gap. C++/CLI feels a bit more natural to me in this case.

JaredPar
+1  A: 

C++/CLI will be easier to debug if you have personnel who know how to debug C++. If you don't, it could potentially be much harder.

I would suggest that the tradeoff here is between ease of use for the consumers of your interop assembly versus ease of maintainability for the assembly itself. If you have a solid core of senior engineers who are familiar with C++ and who can reliably maintain the assembly, it will be much easier on the rest of your team who are unfamiliar with native code to give them a fully managed interface that takes care of everything for them.

On the other hand, if you're the only person in the shop with C++ experience, I would be very leery of embedding a C++ module into the project, in case someone else has to maintain it later.

Dan Story
The majority of the current developers are familiar with C++ though only two or three have written any quantity of C++/CLI.
Ian G
+2  A: 

Think of P/Invoke as platform invocation, are you calling into something like the Win32 API which is very P/Invoke friendly or do you need to provide .NET bindings for an unmanaged library?

Since the wrapper typically is very thin a C++/CLI wrapper doesn't necessitate that you know C++/CLI specifically. What you need to know can be found in the language specification, it's an extensive documentation with lots of examples. P/Invoke is more of an nice to have feature for smaller well established existing libraries, but if the interface for calling into that library changes you'll run into a problem. With C++/CLI you can still have a public managed interface in your C++/CLI project that's exposed for managed code and handle changes to the C API more easily that way.

If you wanna get rid of the extra DLL you can always try ILMerge, but I'm not sure if it's capable of handling mixed assemblies, (apparently not), but it looks like it's possible to link both managed and unmanaged *.obj files with the PlatformSDK linker like this:

cl.exe /MD /c /clr Unmanaged.cpp
csc.exe /target:module /addmodule:*.obj Managed.cs
link.exe /DLL /LTCG /NOENTRY /CLRIMAGETYPE:IJW *.obj Managed.netmodule
John Leidegren
Interesting, I didn't know you could link C# and C++/CLI obj files into the same assembly. Though in this case adding one assembly to the 15+ we already have won't be a problem. Thanks.
Ian G
A: 

Lots of good answers here - another perspective is the plan for the existing C API. Arguments for using PInvoke include:

  • You need to keep a C API around for compatibility with other C consumers
  • You need to keep the code in C as it's large and migration is too expensive

Arguments for using C++/CLI include:

  • you want to move as much of the code as possible to the CLR

In this case you can start with C++/CLI and then move more and more over to C#

Scott Weinstein
The C API is indeed used elsewhere and so will be staying around regardless of the choice made for this project.
Ian G
A: 

For an API of this size (~40 total entry points), I would draw the dividing line between C++/CLI and P/Invoke based on how much "header file gunk" you have to duplicate in C#. If it's a small (to modest) amount, P/Invoke is fine. Once you start duplicating a lot of .H files in C#--especially for things that aren't exposed in your .NET API--you might be better off using C++/CLI.

Dan
+1  A: 

It depends in large part how memory ownership is handled. P/invoke can marshal pointers only when memory management is one of a couple particular ways, usually caller-allocated buffers. If your API returns pointers (via return value or out parameter, doesn't matter) and expects them to be handed back to a destruction function later... p/invoke will either do the automatic marshaling or give you direct access to the pointer value you need to send back later, never both. So C++/CLI becomes a very desirable approach in that case.

Ben Voigt
I'd have to double check but I think in this case the C library uses caller-allocated buffers everywhere.
Ian G