Monday, January 02, 2012

COM Interop Part 1: An Intro to Interop

For those of you who are unaware, I spend most of my time at my $DAYJOB developing software in C#. Managed code is awesome for oh so many reasons, which I won't get into here, but there is one serious drawback when it comes to writing Windows applications in C#: Windows itself is not written in managed code.

In other words, there's almost 20 years worth of existing Windows features, APIs, etc. just waiting for me to use them, and I can't talk to them from C#.

Of course, Microsoft knew this was going to be a problem.  Its a problem they have to deal with themselves, since large chunks of the .NET base class libraries are just exposing Windows features to managed code. So, built into the .NET Framework is the concept of unmanaged code interoperability, or "interop". This is the part of the Framework that allows existing, unmanaged code and new, managed code to cheerfully coexist, even in the same process.

There are a few different ways to accomplish crossing this managed/unmanaged boundary.
  • If you're a C++ person, Visual C++ 2003 and forward can produce what's called a mixed-mode assembly that contains managed and unmanaged code in the same module, and uses a C++-only type of interop. This is good, because it maintains type safety, but it only works in C++ applications, and only if you have the source code and can recompile it.  Not being a big C++ guy anymore, I'm not gonna talk about this any further.
  • If you need to call plain old native code (like the Windows API), you can use P/Invoke. This is a feature of the CLR that lets your managed code load an unmanaged PE file and call exported functions, just like an unmanaged application would. This works in a similar manner to the old-style Visual Basic imports that were used for the same purpose. I will probably talk about this more later, but we'll skip it for now.
  • If you need to use the more modern features of Windows, you will probably end up using COM Interop. As you can probably guess, this feature of the CLR allows managed code to interact with unmanaged COM objects (clients and servers) in essentially the same way that an unmanaged COM component would. The CLR takes care of the ugly details, and you can mostly ignore the fact that some of your objects are actually living outside of your managed app domain.
One thing to keep in mind is that P/Invoke and COM Interop, while they share the same purpose, and some of the same underlying code, behave very differently. For one thing, as the names imply, COM Interop is a bi-directional "interoperability" system that allows your managed code to both consume and provide COM objects, while P/Invoke is a one-way "invocation" system that lets you call native functions. (You sometimes here about a thing called "reverse P/Invoke", but this is actually just a creative use of mixed-mode assemblies, and does not actually use the P/Invoke subsystem.) Additionally, the default behavior of the interop system changes based on which type of interop you are using; for example, COM interop has different default string handling than P/Invoke.

Just to be clear, I'm not saying that either one of these is "better" or "worse" than the other. They are just different, and which you use is dictated mostly by the constraints of your environment. If you do have a choice, I'd probably go with COM Interop (since the development model is very similar), but that's typically not how things work. Usually, you already have an unmanaged system that you need to talk to, otherwise you'd just write everything in managed code! So, if you need to call into a native unmanaged Windows DLL, you need P/Invoke. The Windows Forms library in the BCL, for example, makes heavy use of P/Invoke to talk to the User Interface parts of the Windows API. If you need to talk to an unmanaged COM component, such as dealing with the Explorer Shell API, then you need COM Interop.

The rest of this series will focus only on the COM Interop style of interop, since that's what I'm dealing with myself. There will be a tiny bit of P/Invoke sprinkled in, since even a COM application has to get its first interface from somewhere, but it will be very sparse. Next time, I'll briefly explain the various automated ways to do COM Interop, before moving on to the fun and exciting world of writing your own interop code.