I received a challenge from a friend (lost a bet...) regarding how to load a managed (C#) DLL in a native (C++) process by using the Common Language Runtime (CLR). This is confirmed to work with .NET Framework 4.0.
The trick is to host the CLR in the process and using it to load the managed DLL.
// This tutorial contains C/C++; you have been warned.
1) Making a C# DLL
This is the only part where we'll be using C# - so enjoy it as much as you can! We'll make a dummy DLL that will be later loaded into a native application.
I came up with this sophisticated code (feel free to improvise), but the function that you want to call must return
something (preferably int
).
using System.Windows.Forms;
namespace dllNamespace
{
public class dllClass
{
public static int ShowMsg(string msg)
{
MessageBox.Show(msg);
return 0;
}
}
}
2) Hosting the CLR in a Native Process
First things first, you'll need to include these 2 lines:
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
We have to call CLRCreateInstance()
in order to gain access to ICLRMetaHost
. This interface contains various methods that will provide general information about .NET Framework.
From there, it is required to focus on one version of the framework (in this tutorial, I'm working with v4.0.30319) - calling ICLRMetaHost::GetRuntime()
will return a pointer to another interface (ICLRRuntimeInfo
), which contains...more methods. (this is the upgraded version of ICorRuntimeHost
).
The next step is calling ICLRRuntimeInfo::GetInterface
which loads the CLR into the current process and allows us to use some runtime pointers.
Finally, start the runtime host (if it's not running already) using ICLRRuntimeHost::Start()
.
3) Calling a Method from the Managed DLL
This is done via the runtime host that we struggled to load earlier. Using ICLRRuntimeHost::ExecuteInDefaultAppDomain()
with a bunch of arguments does the job pretty well.
The method looks like this:
HRESULT ExecuteInDefaultAppDomain (
[in] LPCWSTR pwzAssemblyPath,
[in] LPCWSTR pwzTypeName,
[in] LPCWSTR pwzMethodName,
[in] LPCWSTR pwzArgument,
[out] DWORD *pReturnValue
);
Source Code
This is the whole source code:
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
int main()
{
ICLRMetaHost* metaHost = NULL;
ICLRRuntimeInfo* runtimeInfo = NULL;
ICLRRuntimeHost* runtimeHost = NULL;
if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost) == S_OK)
if (metaHost->GetRuntime(L"v4.0.30319",
IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo) == S_OK)
if (runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost,
(LPVOID*)&runtimeHost) == S_OK)
if (runtimeHost->Start() == S_OK)
{
DWORD pReturnValue;
runtimeHost->ExecuteInDefaultAppDomain(L"C:\\random.dll",
L"dllNamespace.dllClass", L"ShowMsg", L"It works!!", &pReturnValue);
runtimeInfo->Release();
metaHost->Release();
runtimeHost->Release();
}
return 0;
}
Short advice: Always check that each HRESULT
returned is equivalent to S_OK
.
P.S.: Due to some problems with my compiler, I couldn't test this code properly - last time, it worked pretty well... hope it still does so.