Please keep in mind that the code of this article is for the .NET Framework 2.0 Beta 1 (C++/CLI). To make this sample work under .NET 1.1, basically use *
instead of ^
for the managed types.
Introduction
When we talk about the .NET platform, people often complain about the same things:
- Speed
- Garbage collection
- No backward compatibility with C or C++
While the first two topics are very well discussed, today I will talk about a relatively unknown feature of the .NET platform: IJW.
What is IJW?
First, IJW stands for "It Just Works", and, at the very first level, it enables you to compile existing C/C++ projects in MSIL, and "It Just Works" ;). To use it, simply add the /clr
compiler option, or set the "Common Language Runtime support" option of your project to "Common Language Runtime Support (/clr)". It's as simple as that.
Of course, why recompile an entire C++ project into MSIL when it's working already? Well, the force of IJW is more in the managed/unmanaged interoperability. Let's take a look at what it can offer you with a simple case: mine.
A sample problem
At work, we heavily use remoting to abstract the network transportation layer, and we were very happy with it. Then one problem came: we use some software to handle our sensors and processing dataflow. This software basically allows you to make "components" that you load in it, and then use them. The problem is, the format for a component is very specific and has to be made in C++ (using the provided source generator). So, to overcome this problem, I designed a simple .NET 2.0 library (using generics) and component that use global shared memory and Win32 events to allow IPC between the software process and the .NET process. While this technique works very well, the drawbacks were huge because you couldn't use the remoting functions you needed when you wanted to and were forced to adopt a certain communication convention. Really, it was a pain sometimes, especially when dealing with pointers. Then I came to see IJW.
What IJW can do
Well, with IJW, you can make an executable image (.exe or .dll) with both unmanaged and managed code inside, and allows interoperability between them. For instance, you make an IJW DLL with managed code, and export an unmanaged class or function that uses the managed code. When you will load this DLL in your unmanaged process (via __declspec(dllimport)
or LoadLibrary()
), the CLR will load the managed image inside and will allow you to use the managed code from your unmanaged process. So, to use your managed code from the unmanaged process, you just have to make the proper wrapper. But then one problem comes: you can't have managed type members in an unmanaged class.
And then I already see some of you saying: "Okay great, but how can I make a wrapper around a managed type if I can't store the managed instance in my wrapper? After all, unmanaged classes are not aware of the .NET Platform". And you would be right. However (I bet you suspected), there is a workaround for this.
Storing managed type members in an unmanaged class
.NET provides a way to overcome this via the System.Runtime.InteropServices.GCHandle
class. We won't go into GCHandle
deeply because, fortunately for us, Microsoft has designed a templated class that wraps GCHandle
: gcroot<T>
, declared in vcclr.h. So, to use a managed type in an unmanaged class, just declare it as the typed gcroot<T>
template. You can then access all its members using the ->
symbol.
Note also that you have to use the gcnew
keyword to instantiate the managed type into the managed heap, this type will then be marked collectable at the gcroot<T>
destructor. Here's a sample:
#include <vcclr.h>
#using <mscorlib.dll>
using namespace System;
class Unmanaged
{
private:
gcroot<String ^> _myString;
public:
Unmanaged()
{
_myString = gcnew String();
}
int GetHashCode()
{
return (_myString->GetHashCode());
}
};
Wrapper instantiation
But then another problem comes: the unmanaged client of the DLL wrapper mustn't see any managed stuff (like gcroot<>
). So, we'll have to make an interface to the wrapper, and a class factory for it. Let's make a class that wraps a managed class System.Windows.Forms.MessageBox
.
First, let's make a managed class called MessageBoxShower
in C#:
using System;
using System.Collections.Generic;
using System.Text;
namespace ManagedClasses
{
public class MessageBoxShower
{
private string _message = "";
public MessageBoxShower(string message)
{
_message = message;
}
public void Show()
{
System.Windows.Forms.MessageBox.Show(_message,
"System.Windows.Forms.MessageBox");
}
}
}
Then, let's make the interface to the C++ wrapper: IMessageBoxWrapper
. Of course, we don't forget to add a reference to the DLL containing MessageBoxShower
.
#pragma once
#include <string>
#ifdef MANAGEDWRAPPER_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#pragma comment (lib, "ManagedWrapper.lib")
#endif
class DLLAPI IMessageBoxWrapper
{
public:
virtual void Show(std::string message) = 0;
static IMessageBoxWrapper *CreateInstance();
static void Destroy(IMessageBoxWrapper *instance);
};
After that, we have to make the MessageBoxWrapper
class itself:
#pragma once
#include <vcclr.h>
#include <string>
#include "IMessageBoxWrapper.h"
using namespace ManagedClasses;
class DLLAPI MessageBoxWrapper : IMessageBoxWrapper
{
private:
gcroot<MessageBoxShower ^> _managedObject;
public:
MessageBoxWrapper() { }
void Show(std::string message);
};
And finally, implement the class factory code, and the MessageBoxWrapper
class:
#include "IMessageBoxWrapper.h"
#include "MessageBoxWrapper.h"
void MessageBoxWrapper::Show(std::string message)
{
_managedObject = gcnew MessageBoxShower(gcnew System::String(message.c_str()));
_managedObject->Show();
}
IMessageBoxWrapper *IMessageBoxWrapper::CreateInstance()
{
return ((IMessageBoxWrapper *)new MessageBoxWrapper());
}
void IMessageBoxWrapper::Destroy(IMessageBoxWrapper *instance)
{
delete instance;
}
And here we are! We can now use our managed class directly in our unmanaged code without the need of any assembly referencing or .NET stuff! Here's a sample file, remember linking is done via __declspec(dllimport)
in IMessageBoxWrapper.h:
#include "IMessageBoxWrapper.h"
int main(int argc, char **argv)
{
IMessageBoxWrapper *wrapper = IMessageBoxWrapper::CreateInstance();
wrapper->Show("hey!");
IMessageBoxWrapper::Destroy(wrapper);
return (0);
}
Data conversion
Data conversion between managed world and unmanaged world is your job; this means you'll have to make a wrapper for each managed type you use between the two worlds. And that's a big drawback of IJW since this can't be automated, because obviously, it depends on your code. However, it can be done anywhere you want. Also, I suggest you to take a look at the System.Runtime.InteropServices.Marshal
class, it can be really useful.
Talking about collections, the conversion is also up to you: for instance ArrayList
or List<>
to std::vector<>
or Hashtable
to std::map<>
, and back.
Conclusion
With this article, we demonstrated that managed code can interoperate very well with unmanaged code, in both directions. And well, I hope, for once, it will help you decide to switch to managed code.
Oh, and don't forget, a common mistake is to forget to put the .NET references needed by your wrapper DLL in the same directory as the executable (not the project!).
History
03/30/2005 - Initial release.