Click here to Skip to main content
Click here to Skip to main content

Using managed code in an unmanaged application

By , 4 Apr 2005
 

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

About the Author

Steeve Morin
Web Developer
France France
Member
Born in 1984. Got a computer at 12. Started programming at 14 with VB6.
Actually in third year at the Epitech school in Paris, France.
And part time working at the IMARA project at INRIA.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5groupplaster26 Mar '13 - 4:38 
Best example to manage to unmage code i have ever seen
Answerheap corruption errors [modified]groupplaster9 Oct '12 - 2:19 
Ok here a fix for the heap corruption error.
modify the "Show" function to accept a pointer
 
int main(int argc, char **argv)
{
      std::string *message;
      message = new std::string();
      *message = "hiya!";
      IMessageBoxWrapper *wrapper = IMessageBoxWrapper::CreateInstance();
      wrapper->Show(message);
      IMessageBoxWrapper::Destroy(wrapper);
      delete(message);
      return (0);
}
MessageBoxWrapper.h
void Show(std::string *message);
 
IMessageBoxWrapper.h
virtual void   Show(std::string *message) = 0;
 
MessageBoxWrapper.cpp
void MessageBoxWrapper::Show(std::string *message)
{
     _managedObject = gcnew MessageBoxShower(gcnew System::String(message->c_str()));
     _managedObject->Show();
}
 
I would also do the change posted previously about the gcroot.
This is the best example of the code i have seen for unmanaged to manage calling   Smile | :)

-- modified 9 Oct '12 - 10:18.
BugDOES NOT WORKmemberigotmymtv200520 Aug '12 - 6:41 
Sorry,
 
Code Project and or the author should go back and check projects like this and make sure they work. This fails with heap corruption errors when compiled and run under MSVS 2010 and MSVS 2012.
 
Nice idea and possibly useful if it actually worked.
GeneralRe: DOES NOT WORKmvpRichard MacCutchan21 Aug '12 - 22:46 
igotmymtv2005 wrote:
Code Project and or the author should go back and check projects like this and make sure they work.
Great idea, you can start the ball rolling by fixing this one.
 
Have you any idea how many articles get posted here on CodeProject every week? It would be a full time job for an army of developers to check all these articles, which, incidentally, are posted here for anyone to use, at no cost, so no guarantees as to whether they will work under all circumstances.
One of these days I'm going to think of a really clever signature.

GeneralMy vote of 5memberMember 4215073 Dec '10 - 9:05 
This was exactly what I was looking for. Thanks!!
GeneralTHANK YOU!!!memberthenutz7217 Mar '09 - 12:51 
God Bless You and Your Big Brain1
Generalgcroot is not necessarily needed in the examplemembergomoku@sina.com11 Nov '07 - 16:57 
Your article is an excellent example of using dll to expose managed world to un-managed clients.
 
However, since your native code block does not refer to the managed object, gcroot<T> is not needed. For example, MessageBoxWrapper dones not need to declare a gcroot<MessageBoxShower ^> _managedObject member. It can simply relay the call like:
 

void MessageBoxWrapper::Show(std::string message)
{
  //_managedObject = gcnew MessageBoxShower(gcnew ystem::String(message.c_str()));
  //_managedObject->Show();
  MessageBoxShower^ shower = gcnew MessageBoxShower(gcnew System::String(message.c_str()));
  shower->Show();
}


QuestionProblem when using a Network drivememberakiura18 Apr '07 - 3:20 
Hello,
 
i have used your project as example to build my wrapper. All work fine but:
 
if you put your project on a network drive, application crashes with comment:
 

System.IO.FileLoadException was unhandled
 
Message: Could not load file or assembly 'FileWatcherWrapper,
Version=1.0.2664.14826, Culture=neutral, PublicKeyToken=null' or
one of its dependencies. Failed to grant minimum permission requests.
(Exception from HRESULT: 0x80131417)

 
After search, it seems that we need full trust on the network drive. My question is: is it possible to bypass this security? I have searched on the net one day and no topic has a clear and simple solution. Is this security only on CLR? because i use amouts of unmanaged DLL and i have never had this problem.
 
Thant you very much
AnswerRe: Problem when using a Network drivememberhaddati7 Aug '07 - 22:39 
Yes it's a Code Access Security imposed by the .NET framework. What you can do is use Caspol to change the secuirty policy:
$ CasPol -q -m -cg LocalIntranet_Zone -zone Intranet FullTrust for example to give FullTrust to the localIntranet. You can also do it graghically from the control pannel (you will find a lot about this on google)
 
Best,
 
Said
GeneralRe: Problem when using a Network drivememberakiura8 Aug '07 - 20:30 
Thank you for your answer, that's indeed what i have done. I reply for follow up.
 
It's a pity that the graphical tool "Framework Configuration" is no longer present in the "redistributable package" but only in the "development package". Nethertheless, CASPOL is present in both versions.
 
Best Big Grin | :-D
 
François-Xavier
GeneralBuilding an MFC Application in Release ModememberMember #176587710 Feb '07 - 1:24 
Dear Friends,
I hav done an MFC.Exe application in visual 2005 studio and Im using the code
#using
using namespace System;
...........
..........
........
the problem is that im not able to properly configure the Property Pages(compiler settings) in Release mode though the generated .exe file runs properly in my pc but doesnt in other PC and gives an error message:application not able to run because application configuration is incorrect.Please help me
 
chandrashekhar
Generaldeploymentmembernadeemahmad19 Sep '06 - 2:07 
Hi
How can the unmanaged dll that references the .net dll be deployed to client pc without registering in GAC?
Application works fine when I registered .net dll in GAc by using regasm tool.
But I want a solution without GAC registration.
Is it possible? What should I have to do?
 
Regards
Generalmodifying the string...memberd.rikel23 Jun '06 - 4:06 
Hello,
 
This article is a jewel ! Big thanks.
 
But I do have a question. How to pass a string to the managed class and actually modifying this string inside the managed class? In other words, is it possible to pass a string as output parameter from unmanaged to managed ?
I need to do this to pass a buffer allocate by a legacy application and this buffer would be filled inside a managed dll written in c#.
 
Thank you in advance,
D
GeneralManaged code threadsmemberrobee07 Mar '06 - 4:08 
Hi Steeve!
 
I wrote a wrapper based on your article. My program links the managed library dinamically (using LoadLibrary), not statically, as you described. I did it because my program uses the managed library only in some situations and I don't want to have the managed code loaded and running always. Loadind the DLL and calling managed code works fine, but after the managed code finishes and my code releases the DLL, 2 threads created by the managed DLL remains attached to my process. The threads run mscorwks.dll or something similiar. What are these threads? How can I totally unload the managed environment? I would like to have my single thread after finishing the managed code.
 
Thanks in advance!
 
Robert
GeneralRe: Managed code threadsmemberSteeve Morin7 Mar '06 - 21:15 
Well, I do have to admit I was not aware of that Smile | :)
 
I am not aware of any way to unload the CLR once it's loaded. And if you think about this, it kinda makes sense, since loading the CLR is quite long, I think they decided to leave it in memory to speed up further DLL loadings. Since the chances you re-use a managed DLL in your application is quite high if you loaded one once. But of course they are cases when you won't load another one.
 
I suggest you to mail the IJW guys at microsoft, or post on the MS newsgroups.
 
Or maybe there is another way:
- When you load the mixed DLL, the CLR will load itself, and will load the managed code inside what's call an AppDomain. Maybe it's that very same AppDomain that stays loaded. What I suggest you to try, is to make a function pointer out of a delegate, then call it at the DLL_PROCESS_DETACH.
This managed function will simply call Application.Exit(), or maybe AppDomain.Unload(AppDomain.CurrentDomain). If it doesn't work, well, I don't know Wink | ;)
Generalgcnewmemberimsodul24 Aug '05 - 4:26 
gcnew do not exists in NET 1.1 either...
Generalsuggestionsmemberscott_mccaskill2 May '05 - 11:36 
Good article; I found it useful as a basic introduction. I do have a couple of suggestions:
 
If MessageBoxWrapper had a destructor, it would not be called in IMessageBoxWrapper::Destroy() since IMessageBoxWrapper does not have a virtual destructor and the object is deleted via an IMessageBoxWrapper pointer. In C++, any class that is meant to be inherited from should have a virtual destructor (pure virtual in this case, since IMessageBoxWrapper is an interface class).
 
Also, I found it useful to modify the CreateInstance() function to return std::auto_ptr<IMessageBoxWrapper>, which simultaneously removes the need for Destroy() and makes it clear that the caller owns the created object.

GeneralRe: suggestionsmemberSteeve Morin2 May '05 - 12:44 
Oh yes, I forgot the virtual destructor Smile | :)
 
About the auto_ptr, that would be a cool idea, since auto_ptr would delete the pointer when it goes out of scope. But you see, the problem is you can't delete a pointer that has been allocated by another module, in this case the class factory in the dll. You would end up having a CRT error, hence the Destroy() method.
 
Thanks again for your suggestions, i'll update the article soon.
 
Steeve
GeneralRe: suggestionsmemberscott_mccaskill3 May '05 - 4:03 
Ah, I forgot about the CRT issues.   I assume this is because a client of the dll might link with the static CRT, in which case it would have its own heap?   If so, what if the dll and all its clients were using the same dll version of the CRT?

GeneralCheck out this artical on MSDNmemberPeter Tewkesbury4 Apr '05 - 23:29 
Check out this artical on MSDN
 
http://msdn.microsoft.com/msdnmag/issues/05/04/C/default.aspx[^]
 
by Paul DiLascia
 
Peter Tewkesbury
Lead Developer

GeneralRe: Check out this artical on MSDNmemberSteeve Morin5 Apr '05 - 2:05 
Man, if I had this url before, it would have saved me a lot of time Smile | :)
 
Thanks Smile | :)
 
Steeve
GeneralRe: Check out this artical on MSDNmemberGarth J Lancaster5 Apr '05 - 2:47 
Steeve Morin wrote:
saved me a lot of time
 
that may be Steeve, but your article is still pretty good in its own right
 
'G'
GeneralRe: Check out this artical on MSDNmemberMichael Bergman30 Jan '09 - 9:28 
Yeah, but you saved me a lot of time so it all works out. Smile | :)
Thanks.
 
m.bergman
-- For Bruce Schneier, quanta only have one state : afraid.
 

GeneralRe: Check out this artical on MSDNmemberAl_S30 Sep '09 - 4:15 
Great article though it doesn't seem to build.
 
Jer 29:11

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 5 Apr 2005
Article Copyright 2005 by Steeve Morin
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid