Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using managed code in an unmanaged application

0.00/5 (No votes)
4 Apr 2005 1  
How to use your managed class libraries in your unmanaged application, using IJW.

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#:

// MessageBoxShower.cs: a managed class that shows a message box.


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.

// IMessageBoxWrapper.h: interface to hide

// the managed implementation from the dll client


#pragma once

#include <string>


#ifdef MANAGEDWRAPPER_EXPORTS
#define DLLAPI  __declspec(dllexport)
#else
#define DLLAPI  __declspec(dllimport)
#pragma comment (lib, "ManagedWrapper.lib") // if importing, link also

#endif

class DLLAPI IMessageBoxWrapper
{
public:
    virtual void    Show(std::string message) = 0;

    // Class factory

    static IMessageBoxWrapper   *CreateInstance();
    static void                 Destroy(IMessageBoxWrapper *instance);
};

After that, we have to make the MessageBoxWrapper class itself:

// MessageBoxWrapper.h: actually wrapper to the managed class


#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:

// MessageBoxWrapper.cpp: implementation of MessageBoxWrapper and class factory


#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:

// main.cpp: a totaly unmanaged application


#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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here