Use reflection from native C++ code to run managed code
In the prior post (Use Reflection to create instances of objects) I showed how to create a plain C# console application that has no special references (including none to any WinForms assembly) that can load and invoke a WinForm.Exe program.
Today, we’ll look at doing the same thing from a plain old C++ native program. We need to
1. Start the CLR
2. get the default app domain
3. get the MSCorlib assembly loaded in that app domain
4. get the type of System.Reflection.Assembly from MSCorLib
5. invoke Assembly.LoadFrom, passing in the name of the WinForm.exe
6. from the result, get the Type of the WinForm we want to instantaite
7. Create an instance of the Type.
8. Invoke the “ShowDialog” method on the type
9. (when the Winform is closed) Stop the CLR
The code to execute these steps in C++ uses COM to communicate with the CLR, and demonstrates some of the power, but also shows some of the complexity.
The “#import <mscorlib.tlb>” line imports a standard TypeLibrary and generates a file “mscorlib.tlh” for TypeLibrary Header, which as 16,000 lines of COM interface definitions.
It’s easier to see these in a tool called OleView.
Start the VS Command prompt (or a standard command prompt and run this file:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\vc\bin\vcvars32.bat
Then type OleView<enter> to start the typelib viewer. Choose Menu->View TypeLib, then point it to “c:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb”
Here OleView is showing some of the methods on _Assembly.
From this, we can see that the many managed types can be invoke via COM straight from plain old C++ code.
See also
Create and play your own Breakout game
Call managed code from your C++ code
Host the CLR and Generate IL to call a MessageBox
<code>
// Win32Project6.cpp : Defines the entry point for the application.
//
/*
Start Visual Studio
File->New Project->C++->Win32->Win32 Project. Just click Finish on the Win32 Application Wizard to accept all the defaults.
After the #include lines, paste in the code below:
In _tWinMain, replace the TODO: Place code here.
With a call to our code:
StartClrCode();
Make sure to adjust the names of the assembly and type:
(asmWinformFileName and typeNameToInstantiate)
Note: the default debugger is Native for this project, but because we start the CLR
managed debugging is useful too.
Project->Properties->Configuration Properties->Debugging->Debugger Type: Mixed
*/
#include "stdafx.h"
#include "Win32Project6.h"
#define MAX_LOADSTRING 100
#include "atlsafe.h"
#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent","ReportEventManaged")
#include <metahost.h>
#pragma comment(lib,"mscoree.lib")
#include <mscoree.h>
#include <comdef.h>
using namespace mscorlib;
// find a specified assembly from an AppDomain by enumerate all assemblies
HRESULT GetAssemblyFromAppDomain(_AppDomain* pAppDomain, LPCWSTR wszAssemblyName, _Deref_out_opt_ _Assembly **ppAssembly)
{
*ppAssembly = NULL;
// get the assemblies into a safearray
SAFEARRAY *pAssemblyArray = NULL;
HRESULT hr = pAppDomain->GetAssemblies(&pAssemblyArray);
if (FAILED(hr))
{
return hr;
}
// put the safearray into a smart ptr, so it gets released
CComSafeArray<IUnknown*> csaAssemblies;
csaAssemblies.Attach(pAssemblyArray);
size_t cchAssemblyName = wcslen(wszAssemblyName);
long cAssemblies = csaAssemblies.GetCount();
for (long i=0; i<cAssemblies; i++)
{
CComPtr<_Assembly> spAssembly;
spAssembly = csaAssemblies[i];
if (spAssembly == NULL)
continue;
CComBSTR cbstrAssemblyFullName;
hr = spAssembly->get_FullName(&cbstrAssemblyFullName);
if (FAILED(hr))
continue;
// is it the one we want?
if (cbstrAssemblyFullName != NULL &&
_wcsnicmp(cbstrAssemblyFullName,
wszAssemblyName,
cchAssemblyName) == 0)
{
*ppAssembly = spAssembly.Detach();
hr = S_OK;
break;
}
}
if (*ppAssembly == 0)
{
hr = E_FAIL;
}
return hr;
}
void StartClrCode()
{
CComPtr<ICLRMetaHost> spClrMetaHost;
// get a MetaHost
HRESULT hr = CLRCreateInstance(
CLSID_CLRMetaHost,
IID_PPV_ARGS(&spClrMetaHost)
);
_ASSERT(hr == S_OK);
// get a particular runtime version
CComPtr<ICLRRuntimeInfo> spCLRRuntimeInfo;
hr = spClrMetaHost->GetRuntime(L"v4.0.30319",
IID_PPV_ARGS(&spCLRRuntimeInfo)
);
_ASSERT(hr == S_OK);
// get the CorRuntimeHost
CComPtr<ICorRuntimeHost> spCorRuntimeHost;
hr = spCLRRuntimeInfo->GetInterface(
CLSID_CorRuntimeHost,
IID_PPV_ARGS(&spCorRuntimeHost)
);
_ASSERT(hr == S_OK);
// Start the CLR
hr = spCorRuntimeHost->Start();
_ASSERT(hr == S_OK);
// get the Default app domain as an IUnknown
CComPtr<IUnknown> spAppDomainThunk;
hr = spCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
_ASSERT(hr == S_OK);
// convert the Appdomain IUnknown to a _AppDomain
CComPtr<_AppDomain> spAppDomain;
hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spAppDomain));
_ASSERT(hr == S_OK);
// Get the mscorlib assembly
CComPtr<_Assembly> sp_mscorlib;
hr = GetAssemblyFromAppDomain(spAppDomain, L"mscorlib", &sp_mscorlib);
_ASSERT(hr == S_OK);
// the full file name on disk for the assembly
auto asmWinformFileName = CComBSTR(L"C:\\Users\\calvinh\\Documents\\Visual Studio 2012\\Projects\\Paddle\\Paddle\\bin\\Debug\\Paddle.exe");
// the name of the type to instantiate
auto typeNameToInstantiate = CComBSTR(L"WindowsFormsApplication1.Form1");;
//asmWinformFileName = CComBSTR(L"c:\\MemSpect\\Test\\csLife.exe");
//typeNameToInstantiate =C ComBSTR(L"Life.Form1");
// get the Type of "System.Reflection.Assembly"
CComPtr<_Type> _typeReflectionAssembly;
hr = sp_mscorlib->GetType_2(
CComBSTR(L"System.Reflection.Assembly"),
&_typeReflectionAssembly);
_ASSERT(hr == S_OK);
// create the array of args. only need 1 argument, array
auto psaLoadFromArgs = SafeArrayCreateVector(
VT_VARIANT,
0, //start array at 0
1); //# elems = 1
long index=0;
// set the array element
CComVariant arg1(asmWinformFileName); // the argument: the asm to load
SafeArrayPutElement(psaLoadFromArgs, &index, &arg1);
//invoke the "Assembly.LoadFrom" public static member to load the paddle.exe
CComVariant cvtEmptyTarget;
CComVariant cvtLoadFromReturnValue;
hr = _typeReflectionAssembly->InvokeMember_3(
CComBSTR(L"LoadFrom"),
static_cast<BindingFlags>(BindingFlags_InvokeMethod |
BindingFlags_Public |
BindingFlags_Static ),
nullptr, //Binder
cvtEmptyTarget, // target. Since the method is static, an empty variant
psaLoadFromArgs, //args
&cvtLoadFromReturnValue);
_ASSERT(hr == S_OK);
SafeArrayDestroy(psaLoadFromArgs); // don't need args any more
_ASSERT(cvtLoadFromReturnValue.vt == VT_DISPATCH);
// get the assembly from the return value
CComPtr<_Assembly> srpAssemblyWinForm;
srpAssemblyWinForm.Attach(
static_cast<_Assembly *>(cvtLoadFromReturnValue.pdispVal
));
// get the desired type from the assembly
CComPtr<_Type> _typeForm;
hr = srpAssemblyWinForm->GetType_2(
typeNameToInstantiate,
&_typeForm);
_ASSERT(hr == S_OK);
// create an instance of the WinForm
CComVariant resWinformInstance;
hr = srpAssemblyWinForm->CreateInstance(
typeNameToInstantiate,
&resWinformInstance);
_ASSERT(hr == S_OK);
// create an array var for return value of Type->GetMember
SAFEARRAY *psaMember = nullptr;
// Get the "ShowDialog" members to the array
// (there are 2 overloads)
// we use "ShowDialog" rather than the "Show"
// because we want the Invoke to return only
// when the user closes the form.
hr = _typeForm->GetMember(CComBSTR("ShowDialog"),
MemberTypes_Method,
(BindingFlags)( BindingFlags_Instance + BindingFlags_Public),
&psaMember);
_ASSERT(hr == S_OK);
// put into SafeArray so it gets released
CComSafeArray<IUnknown *> psaMem;
psaMem.Attach(psaMember);
// Get the Method Info for "ShowDialog" from the 1st type in the array
CComPtr<_MethodInfo> methodInfo;
hr = psaMem[0]->QueryInterface(
IID_PPV_ARGS(&methodInfo)
);
_ASSERT(hr == S_OK);
// invoke the ShowDialog method on the Form
hr = methodInfo->Invoke_3(
resWinformInstance,
nullptr, //parameters
nullptr // return value
);
_ASSERT(hr == S_OK);
// stop the runtime
hr = spCorRuntimeHost->Stop();
_ASSERT(hr == S_OK);
}
// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
StartClrCode();
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32PROJECT6, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32PROJECT6));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT6));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32PROJECT6);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
</code>
Comments
- Anonymous
May 08, 2015
Hi, Calvin, Very nice article about calling managed code from native C++. Manually writing such code is actually very time-consuming. If you are interested, you can take a look at our product of xInterop .NET Bridge which can be used to create native C/C++ DLL to call into manged .NET assembly, class, methods, etc. We create the C/C++ DLL via Reverse P/Invoke along with other PInvoke technologies. www.xinterop.com/.../introduction-to-the-xinterop-net-bridge www.xinterop.com/.../create-a-c-native-dll-bridging-to-c-managed-assembly-3 Thanks,