CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 2 of 2
  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Post Native wrapper sample

    Here's a minimalistic sample of a native wrapper to make a .NET managed library accessible to native C++ code, posted in response to a thread in another forum section.

    The demo solution contains three projects, one representing each the managed library to be made accessible, the wrapper DLL and the native client app. Here are the relevant source files, ordered from low level upward:

    Code:
    // ManagedLib.h
    
    #pragma once
    
    namespace ManagedLib
    {
    
    public ref class ManagedClass
    {
    public:
      static void SayHello();
    };
    
    }
    Code:
    // ManagedLib.cpp
    
    #include "stdafx.h"
    
    #include "ManagedLib.h"
    
    using namespace System;
    
    using namespace ManagedLib;
    
    void ManagedClass::SayHello()
    {
      Console::WriteLine("Hello from the managed world!");
      Console::Out->Flush();
    }
    Code:
    // NativeWrapper.h
    
    #pragma once
    
    #include <Windows.h>
    
    #ifdef BUILDING_WRAPPER_DLL
    #define EXPORTS __declspec(dllexport)
    #else
    #define EXPORTS
    #endif
    
    extern "C"
    {
    
    EXPORTS void WINAPI NativeHelloWrapper();
    
    }
    Code:
    // NativeWrapper.cpp
    
    #include "stdafx.h"
    
    #define BUILDING_WRAPPER_DLL
    
    #include "NativeWrapper.h"
    
    // Must be compiled with CLR support.
    //
    // In the demo solution, this project has a project reference to ManagedLib. In a real-life
    // scenario with a 3rd-party .NET library DLL, that would either be an assembly reference to
    // that DLL or simply a #using directive (note the #).
    //
    // No need to #include ManagedLib.h: The prototype information is implicitly imported by
    // referencing the .NET library DLL.
    
    void WINAPI NativeHelloWrapper()
    {
      ManagedLib::ManagedClass::SayHello();
    }
    Code:
    // NativeWrapperDemo.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
    //
    
    #include "stdafx.h"
    
    #include <iostream>
    
    #include <Windows.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
      HMODULE hdll = LoadLibrary(_T("NativeWrapper.dll"));
      _ASSERT(hdll);
      FARPROC NativeHelloWrapper = GetProcAddress(hdll, "_NativeHelloWrapper@0");
      _ASSERT(NativeHelloWrapper);
      cout << "Have a nice native program run! :)" << endl;
      NativeHelloWrapper();
      cout << "Back in the native world." << endl;
      return 0;
    }
    Actually, the term "native wrapper" is somewhat inaccurate: The wrapper DLL isn't strictly native, it rather is a mixed-mode DLL containing both native and managed code. However, the same inaccuracy usually applies to the to the term "managed wrapper". The word before "wrapper" rather denotes the type of interface exposed.

    Of course there's plenty of room left for more in-depth discussions about performance considerations and such, I'm just too tired to start that right now. At any rate, I think, while I wouldn't really bet on that the wrapper approach beats a COM-based approach which bypasses the wrapper stage hands-down in terms of performance, I'd say at least it's more convenient. And it most probably isn't significantly inferior compared to the COM approach.
    Attached Files Attached Files
    Last edited by Eri523; March 19th, 2013 at 12:35 PM. Reason: Added a link back to the originating thread
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  2. #2
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: Native wrapper sample

    And here's an enhanced version of the wrapper demo, which now supports unloading the wrapped library DLL, achieved by loading that DLL into a separate application domain. This is in response to the concerns issued in post #8 of the other thread. Of course this version is considerably more complex, yet not monstrous...

    Here are the source files with the relevant changes:

    Code:
    // ManagedLib.h
    
    #pragma once
    
    namespace ManagedLib
    {
    using namespace System;
    
    // The [Serializable] attribute is required for the class' methods and properties to be
    // accessible across the app domain border. If some classes you intend to access in the
    // wrapped library don't feature this attribute, you may need to add another wrapper level
    // that exposes [Serializable] wrapper classes inside the managed lib app domain.
    
    [Serializable]
    public ref class ManagedClass
    {
    public:
      ManagedClass() : m_abyDummy(gcnew array<Byte>(16 * 1024 * 1024))
      {}
    
      void SayHello();
    
    private:
      // This member is merely here to bloat the object a bit, so its memory footprint is
      // clearly visible in the chart:
    
      array<Byte> ^m_abyDummy;
    };
    
    }
    The wrapper implementation file has gained quite some size and exports one additional function compared to the last version. This is of course reflected in the respective .h file, which is not shown here but part of the attached demo solution.

    Code:
    // NativeWrapper.cpp
    
    #include "stdafx.h"
    
    #define BUILDING_WRAPPER_DLL
    
    #include "NativeWrapper.h"
    
    #ifndef _MANAGED
    #error Must be compiled with CLR support
    #endif
    
    // In the demo solution, this project has a project reference to ManagedLib. In a real-life
    // scenario with a 3rd-party .NET library DLL, that would either be an assembly reference to
    // that DLL or simply a #using directive (note the #).
    //
    // No need to #include ManagedLib.h: The prototype information is implicitly imported by
    // referencing the .NET library DLL.
    
    using namespace System;
    
    private ref class ManagedClassProxy
    {
    public:
      static property ManagedLib::ManagedClass ^Instance
      {
        ManagedLib::ManagedClass ^get()
        {
          if (!s_instance) {
            s_apdManagedLibDomain = AppDomain::CreateDomain("Managed Lib Domain");
            s_instance = safe_cast<ManagedLib::ManagedClass ^>(s_apdManagedLibDomain->
              CreateInstanceAndUnwrap("ManagedLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
              "ManagedLib.ManagedClass"));
          }
          return s_instance;
        }
      }
    
    public:
      static void Unload()
      {
        AppDomain::Unload(s_apdManagedLibDomain);
      }
    
    private:
      static AppDomain ^s_apdManagedLibDomain;
      static ManagedLib::ManagedClass ^s_instance;
    };
    
    void WINAPI NativeHelloWrapper()
    {
      ManagedClassProxy::Instance->SayHello();
    }
    
    EXPORTS void WINAPI UnloadManagedLib()
    {
      ManagedClassProxy::Unload();
    }
    I have added some stop points to the demo client app where it waits for a key press, so the changes in process state can be conveniently observed using external tools:

    Code:
    // NativeWrapperDemo.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
    //
    
    #include "stdafx.h"
    
    #include <iostream>
    #include <conio.h>
    
    #include <Windows.h>
    
    using namespace std;
    
    void waitkey(char *prompt)
    {
    #ifndef NO_KEY_WAIT
      cout << "Hit any key to " << prompt << "..." << endl;
      _getch();
    #endif
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
      waitkey("load wrapper DLL");
      HMODULE hdll = LoadLibrary(_T("NativeWrapper.dll"));
      _ASSERT(hdll);
      FARPROC NativeHelloWrapper = GetProcAddress(hdll, "_NativeHelloWrapper@0");
      _ASSERT(NativeHelloWrapper);
      cout << "Have a nice native program run! :)" << endl;
      waitkey("call wrapped lib function");
      NativeHelloWrapper();
      cout << "Back in the native world." << endl;
      waitkey("unload wrapped lib");
      FARPROC UnloadManagedLib = GetProcAddress(hdll, "_UnloadManagedLib@0");
      _ASSERT(UnloadManagedLib);
      UnloadManagedLib();
      waitkey("unload wrapper lib");
      BOOL bFreeLibRes = FreeLibrary(hdll);
      _ASSERT(bFreeLibRes);
      waitkey("quit");
      return 0;
    }
    This diagram shows the achieved effect:

    Name:  NativeWrapperMemory.png
Views: 2121
Size:  20.4 KB

    It displays the demo process' memory consumption (working set size and private bytes) as well as the application domain count (in red). The processing steps are clearly visible:

    • loading the wrapper DLL
    • calling the wrapped function over the wrapper, which implicitly loads the wrapped library DLL and instantiates the lib object
    • unloading the wrapped library by unloading the application domain created for it

    I don't see any way of getting rid of the default (i.e. root) application domain containing the wrapper, as well as the .NET framework stuff accompanying it, which is quite some as well, as can be seen. But I think this still is a step ahead.

    I refrained from messing with the wrapper DLL's DllMain() by now, although that may allow to handle the unloading process in a more transparent and elegant manner. But that function plays an important role in starting up the .NET runtime when the DLL is loaded, and I've seen warnings that it must be treated with extreme caution in a mixed-mode DLL (though, IIRC, they in fact only referred to start-up of the DLL, not its shutdown).

    EDIT: Oops! Of course it's not really a good idea to wrap a function call that is meant to be executed in either build (here: FreeLibrary(hdll)) into an _ASSERT(). How embarrassing... The fix is highlighted in red in the listing. It's not yet reflected in the attached demo solution, so downloaders please apply it themself. This has no effect on the correctness of the diagram (that was recorded from the debug build anyway), since this step doesn't have any observable effect on the recorded parameters.
    Attached Files Attached Files
    Last edited by Eri523; March 19th, 2013 at 09:10 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured