To kick off my AltDevBlogADay career, I figured I would start with an area I have a disturbing amount of experience: .NET interoperability with native code. Several years ago, I initiated a project called SlimDX, that exposes the entirety of the DirectX APIs (and several others) to managed code. It was a long trip down the rabbit hole from there, resulting in probably one of the biggest interop projects outside MS itself at over 200K lines. I’ve worked on a lot of interop projects in the meantime, and after attending tools sessions at two GDCs, it’s become evident that the knowledge for how to build bridges between native code and C# is not widespread or well documented.
Today, I’ll just cover the basic options available and their pros and cons.
Platform Invoke — P/Invoke for short — is the built in .NET mechanism for calling C APIs. Any exported function in any shared library can be invoked. The runtime also includes a large number of options for converting types back and forth, a process called marshaling. P/Invoke was primarily designed to support the Windows API, so it can tackle a wide range of C style calls but also includes a lot of mysterious names and types that only make sense from the perspective of a Windows developer. All the same, it’s a highly flexible system that is also infuriatingly subtle and awkward to use for complex cases. You can wrap practically any function a sane C API could throw at you, but getting the marshaling options correct for the data you’re transferring can be tricky. It’s often easier to simply write private functions to do it than to figure out how to trick the framework into doing the right thing.
Normally, you have to write P/Invoke wrapping code yourself, though the site SWIG which parses C and C++ code to generate a wrapper. It can even handle C++, by creating an intermediate C API.
The performance of P/Invoke functions is mediocre. The runtime goes to substantial lengths to prevent the native code from damaging the managed runtime, which includes all kinds of checks on stacks, memory, exceptions, and so on. That takes up a long time, so if you’re trying to use native code for performance, it’s critical that your API provides large batch processing functionality. It may be beneficial to rewrite smaller functions in .NET.
There’s also another problem, which is that the shared library to be loaded is named explicitly at compile time. This makes dynamic loading a lot more difficult, and breaks .NET’s normal x86/x64-agnostic arrangement.
COM Runtime Callable Wrapper
I won’t dwell on this, because most of you are in games and that means you’re probably not writing COM interfaces. COM exposes a nice restricted version of an interface-based API, along with a dedicated definition language to describe an API that is compatible with C++ without actively exposing its quirks to other languages. .NET can automatically generate wrappers of COM objects, which is moderately helpful if you already have a COM interface. The actual generated wrapper is not pleasant, but it does work.
This is the jackhammer. It’s Microsoft’s follow up to the now long deprecated Managed C++, and it allows you to compile a single binary containing both native and managed code. SlimDX and several other major interop projects use C++/CLI, since it allows you to express a .NET API while doing anything you want with native code underneath. Microsoft’s tag line when this monster was released was IJW, “It Just Works”, which is some kind of cruel joke.
C++/CLI stands in a limbo world blend of managed and native code, stirring the complexities of both together into a single pot. At the same time, the two worlds mix about as well as water and oil. You can compile nearly any native code perfectly well in C++/CLI, but it won’t help because they’re not managed interfaces. No, instead you have to write all new interfaces, structs, enums, and classes to mirror the native world, and there is absolutely no support whatsoever for letting the compiler do this automatically. In short, you’re left to create decorators for your entire project, write all of your marshaling manually, and be mindful of all the differing rules on both sides of the divide. (Memory and threading in particular can be dangerous.) Let your imagination go wild about what happens when the time comes to refactor the native interfaces.
By all rights, it should be possible to extend SWIG to generate C++/CLI code, assuming you want to do that. This is a pending area of research for me and I would be curious to hear if anyone else has looked into it. However, if you’re dealing with something like middleware that has a very stable public interface (DirectX in SlimDX’s case), then C++/CLI is a very powerful and effective (but dangerous) tool.
For the most part, C++/CLI suffers the same drawbacks of P/Invoke, except that the interfaces you can call are dramatically expanded. Anything C++ can interact with will work. Since you’re writing everything yourself, there’s also much more of an opportunity to exercise control over exactly how and when the expensive transitions between managed and native code occur. (Inadvertently giving you another thing to worry about.)
This is actually one a lot of people seem to forget about, and it’s probably the best choice for most game toolchains. There’s any number of possible ways to implement it, but it’s fairly straightforward to build a simple socket based messaging system, and you can share the message structure definitions by using SWIG to generate them. If you get ambitious, there are plenty of more sophisticated techniques: named pipes, remote procedure calls, shared memory, etc. The real drawback here is that it’s difficult to retrofit to an existing system. If you’re going to drive your interop with IPC, it’s generally best to design with that in mind from the ground up, especially since it’s typically a lot more expensive than in process function calls.
This probably won’t be a useful approach if you want to use specific libraries as part of a C# application. If your system is amenable to it though, it can be much less work to write a native code IPC server for a .NET client, which exposes a high level interface to your engine. There are dividends to be paid in terms of modularity, and frankly in terms of being somewhat independent from .NET when something new comes along.
So there you have it, four basic methods of getting native and managed code to talk to each other, and I’ve made all of them sound awful. This was only a high level overview though, and simply giving up now just isn’t the game developer way. I am curious to hear from you guys, though: what do you want to know? For my next post, I would like to dig into detail about the mechanics of using one of these interop methods cleanly and safely. I’ve alluded to a lot of rules, and a lot of experience (some of it bad!) in handling the problems inherent in this collision of worlds. My question is, where would you like me to start?