[7/20/05: Added an additional comment regarding setting up for Win32]

I've mentioned in my earlier posts just how nice Managed C++  in 2005 (C++/CLI) is when it comes to interoping with unmanaged code.  It makes P/Invoke downright painful by comparison. I'd rather toss 10,000 lines of P/Invoke code and restart in C++ than maintain it (and yes, I've done just that).  Those legions out there that are using C# (or even VB.Net) shouldn't be afraid of C++ now, the managed syntax is so much better, it's really quite easy to work with.  In this post I'm going to go through some examples of just how easy it is, specifically looking at accessing the Win32 API.

An example in C# using P/Invoke:

Lets say we want to write a wrapper base class for a window object in Win32.  Lets say we want that class to do something relatively simple, like find a specific window by title and stash the handle (HWND) to it, and grab the window's coordinates.  Well, first we'd have to define the necessary structs before we could get to defining the P/Invokes:

public struct RECT
   public Int32 left;
   public Int32 top;
   public Int32 right;
   public Int32 bottom;
public struct WINDOWINFO
   public UInt32 cbSize;
   public RECT rcWindow;
   public RECT rcClient;
   public UInt32 dwStyle;
   public UInt32 dwExStyle;
   public UInt32 dwWindowStatus;
   public UInt32 cxWindowBorders;
   public UInt32 cyWindowBorders;
   public UInt16 atomWindowType;
   public UInt16 wCreatorVersion;

Hmm, ok.  Now the P/Invokes:

[DllImport("user32.dll", SetLastError = true)]
public static extern Boolean GetWindowInfo(
   IntPtr hwnd;
   out WINDOWINFO pwi);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(
   [MarshalAs(UnmanagedType.LPTStr)] string lpClassName;
   [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName);

Ugh. Now we can finally call the appropriate APIs.  This is a simple example too.  It can quickly balloon into a real pain in the ass.  Say, when you have an API that takes a #define value for one or more of it's arguments, such as the SendMessage api.  You have to find said values, define an enum or constant, and hope desperately you manually copied the value correctly.  (When you start doing hundreds or thousands this isn't too difficult to mess up.)

A few initial humps on the way to the C++ example:

Allright, I may lose some people here, but there are a few things you'll have to do on the C++ side.  Native and .NET Interoperability describes in detail what you have to do.  Usually you don't have to do much of anything.  Strings, however, are a little different.  (See How to: Marshal ANSI Strings Using C++ Interop for complete details--there is a link to Unicode & COM strings from there).  I'll give you a small class here that you can use to make this easier (note that I haven't split this up into a header/declaration and a definition for clarity, but I highly recommend it):

public ref class Convert
   // Handle for marshalling the unmanged string pointer.
   IntPtr unmanagedStringPointer;
   // Constructor for the managed to native conversion.    Convert(String^ managedString)    {       this->unmanagedStringPointer = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(managedString);    }
   // Converts the marshalled pointer to a native string.    char* ToNativeString()    {       return static_cast<char*>(this->unmanagedStringPointer.ToPointer());    }
public:    // Converts a managed string to a native string.    static char* ToNativeString(String^ managedString)    {       return (gcnew Convert(managedString))->ToNativeString();    }
   // Converts a native string to a managed string.    static String^ ToString(char* nativeString)    {       return System::Runtime::InteropServices::Marshal::PtrToStringAnsi(static_cast<IntPtr>(nativeString));    }
   // Destructor (implicitly implements IDisposable)    ~Convert()    {       if(this.unmanagedStringPointer != IntPtr::Zero)       {          System::Runtime::InteropServices::Marshal::FreeHGlobal(this.unmanagedStringPointer);       }    } };

This little helper will make things much easier.  You should be able to add Unicode and COM string support by following the previous SDK link.  The other thing you'll probably want to do is start adding helpers to convert from common Win32 datatypes to .Net datatypes (say RECT to Rectangle, etc.).  You would  have to do this in C# as well, of course.

Now for the C++ example:

Again, I won't break this up into a .h and a .cpp file, but this is only for clarity.  I'm only using the helper above to convert strings.  The rest comes straight from including the Win32 headers.  (See the next section for details on how to set that up.)

public ref class Window
   // The native HWND.
   HWND windowHwnd;
public:    // Constructor that takes a window title.    Window(String^ windowTitle)    {       HWND foundWindow = FindWindow(NULL, (LPCSTR)Convert::ToNativeString(windowTitle));       if (foundWindow == NULL)       {          // didn't find the window, barf here...       }       else       {          this->windowHwnd = foundWindow;       }       }
   // Returns the bounds of the window in screen coordinates.    property Drawing::Rectangle Bounds    {       Drawing::Rectangle get()       {          RECT windowRect;          if (GetWindowRect(this->windowHwnd, &windowRect== FALSE)          {             // Failed somehow. Deal with it... (Another post.)          }          else          {             return Drawing::Rectangle::FromLTRB(windowRect.left, windowRect.top, windowRect.right, windowRect.bottom);          }       }    } };

And that's it.  One small price in setting up a string helper class and you're up and running.  I could create the above in a DLL and use it in all my C# projects.  (Hey, I do just that.)  You can read the Platform SDK and pretty much use it directly.  You don't have to set up P/Invokes or redefine structs or #defines.  You get intellisense...  It's much more freeform to be able to see another useful API and relatively directly be able to use it.  For example, when I was constructing my Window class from a specified HWND I noticed there was an API for verifying that a HWND was valid--ok, add one line of code...

Now, with everything there are always caveats.  I have found two circumstances where things don't work seamlessly.  If the API has a struct that has a union or bitfields I can't figure out how to access them directly from managed code.  (SendInput is an example.)   There is a relatively easy workaround and that is to create an unmanaged helper class.  That's another post if anyone is interested.

Setting up a Managed C++ project for interop:

Dll, exe, lib, doesn't matter.  Here are the key things you need:

  • /clr option set for the project.  (One of the general settings, see my last post.)
  • Include the appropriate Win32 headers.  (#include <windows.h> is the main one)
  • Appropriate target OS #defines. (see Using the Windows Headers for the right values)
  • [7/20/05] Note that if you have a C++ forms app you'll need to remove $(NoInherit) from the Additional Dependencies property under Linker:Input.

It's probably a good idea to put the Win32 #includes in a precompiled header for speedier compilation (stdafx.h typically, see Creating Precompiled Header Files for more info).