This code shows how to load a CLR in an unmanaged process, then load an assembly from memory (not from a file) and execute a method
/* | |
====== x86 (32 bits) EXE: | |
1/ Run the following bat file: | |
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" | |
2/ Then compile it using | |
c:\> cl.exe loadAssemblyFromMemory.cpp /o loadAssemblyFromMemory.exe | |
====== x64 (64 bits) EXE: | |
1/ Run the following bat file: | |
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" | |
2/ Then compile it using | |
c:\> cl.exe loadAssemblyFromMemory.cpp /o loadAssemblyFromMemory.exe | |
*/ | |
#pragma region Includes and Imports | |
#include <metahost.h> | |
#pragma comment(lib, "mscoree.lib") | |
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library). | |
#import <mscorlib.tlb> raw_interfaces_only \ | |
high_property_prefixes("_get","_put","_putref") \ | |
rename("ReportEvent", "InteropServices_ReportEvent") \ | |
rename("or", "InteropServices_or") | |
using namespace mscorlib; | |
#pragma endregion | |
// This is the TestAssembly.dll managed DLL represented as an array of bytes. | |
// Use the following PowerShell snippet to convert the dll file to the expect hex array and get the assembly size: | |
/* | |
$Bytes = Get-Content .\TestAssembly.dll -Encoding Byte | |
$Bytes.Length | |
$HexString = [System.Text.StringBuilder]::new($Bytes.Length * 4) | |
ForEach($byte in $Bytes) { $HexString.AppendFormat("\x{0:x2}", $byte) | Out-Null } | |
$HexString.ToString() | |
*/ | |
unsigned char assemblyDLL[] = "\x4d\x5a[...]"; | |
const unsigned int assemblyDLL_len = 3072; | |
int main() | |
{ | |
//------------------------------------ | |
// Code from https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0 | |
//------------------------------------ | |
HRESULT hr; | |
ICLRRuntimeInfo* pRuntimeInfo = NULL; | |
ICorRuntimeHost* pCorRuntimeHost = NULL; | |
//---------------------------------------------------------------------- | |
// Load the CLR runtime | |
ICLRMetaHost* pMetaHost = NULL; | |
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost)); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
// Get the ICLRRuntimeInfo corresponding to a particular CLR version. It | |
// supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE. | |
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo)); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
// Check if the specified runtime can be loaded into the process. This | |
// method will take into account other runtimes that may already be | |
// loaded into the process and set pbLoadable to TRUE if this runtime can | |
// be loaded in an in-process side-by-side fashion. | |
BOOL fLoadable; | |
hr = pRuntimeInfo->IsLoadable(&fLoadable); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
if (!fLoadable) { | |
return -1; | |
} | |
// Load the CLR into the current process and return a runtime interface | |
// pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting | |
// interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost | |
// interface that was provided in .NET v1.x, and is compatible with all | |
// .NET Frameworks. | |
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost)); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
//---------------------------------------------------------------------- | |
// Start the CLR | |
hr = pCorRuntimeHost->Start(); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
//---------------------------------------------------------------------- | |
// Get the default AppDomain for this Runtime host | |
IUnknownPtr spAppDomainThunk = NULL; | |
_AppDomainPtr spDefaultAppDomain = NULL; | |
// Get a pointer to the default AppDomain in the CLR. | |
hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain)); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
//---------------------------------------------------------------------- | |
// Load the assembly from memory, declared as an unsigned char array | |
SAFEARRAYBOUND bounds[1]; | |
bounds[0].cElements = assemblyDLL_len; | |
bounds[0].lLbound = 0; | |
SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds); | |
SafeArrayLock(arr); | |
memcpy(arr->pvData, assemblyDLL, assemblyDLL_len); | |
SafeArrayUnlock(arr); | |
_AssemblyPtr spAssembly = NULL; | |
hr = spDefaultAppDomain->Load_3(arr, &spAssembly); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
// Get the Type (ie: Namespace and Class type) to be instanciated from the assembly | |
bstr_t bstrClassName("TestNamespace.TestClass"); | |
_TypePtr spType = NULL; | |
hr = spAssembly->GetType_2(bstrClassName, &spType); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
//---------------------------------------------------------------------- | |
// Finally, invoke the method passing it some arguments as a single string | |
bstr_t bstrStaticMethodName(L"EntryPoint"); | |
SAFEARRAY* psaStaticMethodArgs = NULL; | |
variant_t vtStringArg(L"these|are|arguments|passed|to|the|dotNet|method"); | |
variant_t vtPSEntryPointReturnVal; | |
variant_t vtEmpty; | |
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1); | |
LONG index = 0; | |
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
// Invoke the method from the Type interface. | |
hr = spType->InvokeMember_3( | |
bstrStaticMethodName, | |
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public), | |
NULL, | |
vtEmpty, | |
psaStaticMethodArgs, | |
&vtPSEntryPointReturnVal); | |
if (FAILED(hr)) { | |
return -1; | |
} | |
SafeArrayDestroy(psaStaticMethodArgs); | |
psaStaticMethodArgs = NULL; | |
} |
/* | |
================================ Compile as a .Net DLL ============================== | |
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library /out:TestAssembly.dll TestAssembly.cs | |
*/ | |
using System.Windows.Forms; | |
namespace TestNamespace | |
{ | |
public class TestClass | |
{ | |
public static int EntryPoint (string arguments) | |
{ | |
MessageBox.Show(arguments); | |
return 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment