2 Attachment(s)
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:
Attachment 31185
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! :blush: 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... :o 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.