Use C++ and no managed code to create a WPF form


In Use the power of Reflection to create and manipulate managed objects I showed how to create a WPF form with a StackPanel, Button and Textbox from a C# console app, using reflection. The code adds text to the textbox and sets some properties on it.

Using reflection works, but makes the code 5 time bigger. Yet, it’s instructive.

Because the sample code has a ButtonClick event handler and uses enums, implementing the code via reflection is even more complex.

Taking it a step further, we can use a C++ application to start the CLR and create and show a WPF application by using C++ code and pure interop reflection. Let me say this again: this is a WPF window with NO managed code! It uses reflection to load and instantiate types and hook them together. I couldn’t figure out how to use Enums to set properties. For example, the scrollbars on the Textbox have a ScrollBarVisibility property. I’d get various exceptions, like missing method exceptions, when I try to set the value. When I get an enum value, it comes back as an integer, but when I try to set a property that is an enum, passing an integer means that an overload of the setter using an integer parameter can’t be found.

I also couldn’t figure out how to hook up events. I think this exercise needs more time. Some of the attempts at enums and events are included in commented code.

See also: Create your own CLR Profiler

<code>

#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlsafe.h>
#include <metahost.h>
#include <functional>
// import, rename things that collide automatically by prepending with "__", exclude things that cause intellisense errors
#import "mscorlib.tlb" auto_rename exclude("ITrackingHandler") //raw_interfaces_only

/*
File->New Project->C++->Windows->Win32 Project
Tell the wizard to create an empty project. Call it CppWpf
Right click on the "Source Files" node in solution explorer, add a single C++ source file "Main.cpp".
paste in this content.
*/
using namespace mscorlib;

typedef HRESULT(STDAPICALLTYPE *fpCLRCreateInstance)(
	REFCLSID  rclsid,
	REFIID    riid,
	LPVOID*   ppv
	);

#define IfFailRet(expr)  { hr = (expr); if(FAILED(hr)) return (hr); }
#define IfNullFail(expr) { if (!expr) return (E_FAIL); }

class ClrUtility
{
public:
	static HRESULT StartClrAndGetAppDomain(_AppDomainPtr &srpDomain)
	{
		HRESULT hr = S_OK;

		CComPtr<ICLRMetaHost> spHost;
		HMODULE hMscoree = LoadLibrary(L"mscoree.dll"); //windows\system32
		fpCLRCreateInstance pCLRCreateInstance = 
			(fpCLRCreateInstance)GetProcAddress(hMscoree, "CLRCreateInstance");

		IfFailRet(pCLRCreateInstance(
			CLSID_CLRMetaHost, 
			IID_ICLRMetaHost, 
			(LPVOID *)&spHost));

		CComPtr<ICLRRuntimeInfo> spRuntimeInfo;
		CComPtr<IEnumUnknown> pRunTimes;
		IfFailRet(spHost->EnumerateInstalledRuntimes(&pRunTimes));
		CComPtr<IUnknown> pUnkRuntime;
		while (S_OK == pRunTimes->Next(1, &pUnkRuntime, 0))
		{
			CComQIPtr<ICLRRuntimeInfo> pp(pUnkRuntime);
			if (pUnkRuntime != nullptr)
			{
				spRuntimeInfo = pp;
				break;
			}
		}
		IfNullFail(spRuntimeInfo);

		BOOL bStarted;
		DWORD dwStartupFlags;
		hr = spRuntimeInfo->IsStarted(&bStarted, &dwStartupFlags);
		if (hr != S_OK) // sometimes 0x80004001  not implemented  
		{
			spRuntimeInfo = nullptr;
			IfFailRet(spHost->GetRuntime(
				L"v4.0.30319", 
				IID_PPV_ARGS(&spRuntimeInfo)));
			bStarted = false;
		}

		CComPtr<ICLRRuntimeHost> spRuntimeHost;
		IfFailRet(spRuntimeInfo->GetInterface(
			CLSID_CLRRuntimeHost, 
			IID_PPV_ARGS(&spRuntimeHost)));
		if (!bStarted)
		{
			hr = spRuntimeHost->Start();
			IfFailRet(hr);
		}

		// now the CLR has started
		CComPtr<ICorRuntimeHost> srpCorHost;
		IfFailRet(spRuntimeInfo->GetInterface(
			CLSID_CorRuntimeHost, 
			IID_PPV_ARGS(&srpCorHost)));
		CComPtr<IUnknown>       srpUnk;
		IfFailRet(srpCorHost->GetDefaultDomain(&srpUnk));
		IfFailRet(srpUnk->QueryInterface(IID_PPV_ARGS(&srpDomain)));
		return hr;
	}

	static HRESULT GetAssemblyFromAppDomain(
		_AppDomain* pAppDomain, 
		LPCWSTR wszTargetAssemblyName, 
		_Assembly **ppAssembly)
	{
		HRESULT hr = S_OK;
		SAFEARRAY                   *pAssemblyArray = NULL;
		CComSafeArray<IUnknown*>    csaAssemblies;
		long                        cAssemblies;

		IfFailRet(pAppDomain->raw_GetAssemblies(&pAssemblyArray));

		IfFailRet(csaAssemblies.Attach(pAssemblyArray));

		cAssemblies = csaAssemblies.GetCount();

		hr = E_FAIL;
		for (long i = 0; i < cAssemblies; i++)
		{
			CComQIPtr<_Assembly> srpqiAssembly;
			srpqiAssembly = csaAssemblies.GetAt(i);
			if (srpqiAssembly == nullptr)
			{
				continue;
			}
			CComBSTR bstrLocation;
			if (S_OK == srpqiAssembly->get_Location(&bstrLocation))
			{
				WCHAR drive[_MAX_DRIVE];
				WCHAR dir[_MAX_DIR];
				WCHAR fname[_MAX_FNAME];
				WCHAR ext[_MAX_EXT];
				_wsplitpath_s(bstrLocation, drive, dir, fname, ext);

				if (_wcsicmp(fname, wszTargetAssemblyName) == 0)
				{
					*ppAssembly = srpqiAssembly.Detach();
					hr = S_OK;
					break;
				}
			}
		}
		return hr;
	}

	static void BtnEventHandler(_ObjectPtr object, _ObjectPtr eventArgs)
	{
		MessageBox(0, L"Clicked!", L"yahoo!", 0);
	}

	static CComVariant CallMember(
		_TypePtr typePtr,
		LPUNKNOWN punkTarget,
		TCHAR *methName,
		BindingFlags bindingFlags,
		int nArgs,
		...
		)
	{
		CComSafeArray<VARIANT> args;
		va_list argptr;
		if (nArgs > 0)
		{
			va_start(argptr, nArgs);
			CComVariant pVar;
			for (int i = 0; i < nArgs; i++)
			{
				pVar = va_arg(argptr, CComVariant);
				args.Add(pVar);
			}
		}
		CComVariant cvtRetVal =
			typePtr->InvokeMember_3(
				_bstr_t(methName),
				bindingFlags,
				NULL,
				CComVariant(punkTarget),
				args
				);
		return cvtRetVal;
	}

	static HRESULT DoWork()
	{
		HRESULT hr = S_OK;
		_AppDomainPtr srpDomain;
		IfFailRet(StartClrAndGetAppDomain(srpDomain));
		_AssemblyPtr asmMsCorlib;
		IfFailRet(GetAssemblyFromAppDomain(srpDomain, L"mscorlib", &asmMsCorlib));
		// get the type of the System Activator
		_TypePtr ptypActivator = asmMsCorlib->GetType_2(_bstr_t(L"System.Activator"));

		auto mscorlibLoc = asmMsCorlib->Location;
		WCHAR _FullPath[_MAX_FNAME];
		WCHAR _Drive[_MAX_DRIVE];
		WCHAR	_Dir[_MAX_DIR];
		WCHAR _Filename[_MAX_FNAME];
		WCHAR _Ext[_MAX_EXT];
		_wsplitpath_s(mscorlibLoc, _Drive, _Dir, _Filename, _Ext);
		wcscpy_s(_Filename, L"WPF\\PresentationFramework");
		//"C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll"
		_wmakepath_s(_FullPath, _Drive, _Dir, _Filename, _Ext);
		_TypePtr ptypAssembly = asmMsCorlib->GetType_2(
			_bstr_t(L"System.Reflection.Assembly"));

		auto cvtPresFwk = CallMember(
			ptypAssembly,
			nullptr, // static method uses null IUnknown target
			L"LoadFrom",
			(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
			1,
			CComVariant(_FullPath)
			);

		_AssemblyPtr  asmPresFwk(cvtPresFwk.punkVal);

		_TypePtr ptypWindow = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Window"));
		auto cvtWindow = CallMember(
			ptypWindow,
			nullptr, // CreateInstance uses null IUnknown target
			nullptr, // method name not needed for CreateInstance
			(BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
			0
			);

		_ObjectPtr oWindow(cvtWindow.punkVal);
		CallMember(
			ptypWindow,
			oWindow.GetInterfacePtr(),
			L"Title",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(L"Window Title")
			);
		_TypePtr ptypSPanel = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Controls.StackPanel"));
		auto cvtStackPanel = CallMember(
			ptypSPanel,
			nullptr, // CreateInstance uses null IUnknown target
			nullptr, // method name not needed for CreateInstance
			(BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
			0
			);

		_TypePtr ptypButton = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Controls.Button"));
		auto btnType = CallMember(
			ptypButton,
			ptypButton.GetInterfacePtr(),
			L"GetType",
			(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
			0
			);
		_TypePtr ptypButton2 = V_UNKNOWN(&btnType);
		auto cvtbtnClick = CallMember(
			V_UNKNOWN(&btnType),
			ptypButton.GetInterfacePtr(),
			L"GetEvent",
			(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
			1,
			CComVariant(L"Click")
			);
		auto cvtButton = CallMember(
			ptypButton,
			nullptr, // CreateInstance uses null IUnknown target
			nullptr, // method name not needed for CreateInstance
			(BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
			0
			);
		auto typEventInfo = asmMsCorlib->GetType_2(_bstr_t(L"System.Reflection.EventInfo"));
		auto typMarshal = asmMsCorlib->GetType_2(_bstr_t(L"System.Runtime.InteropServices.Marshal"));
		_AssemblyPtr asmPresentationCore;
		IfFailRet(GetAssemblyFromAppDomain(srpDomain, L"PresentationCore", &asmPresentationCore));
		auto typRoutedEventHandler = asmPresentationCore->GetType_2(
			_bstr_t(L"System.Windows.RoutedEventHandler"));
		auto typeoftypRoutedEventHandler = CallMember(
			typRoutedEventHandler,
			typRoutedEventHandler.GetInterfacePtr(),
			L"GetType",
			(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
			0
			);
		long addr = (long)&ClrUtility::BtnEventHandler;
		//auto delBtnClick = CallMember(
		//    typMarshal,
		//    nullptr,
		//    L"GetDelegateForFunctionPointer",
		//    (BindingFlags)(BindingFlags_Static | BindingFlags_Public | BindingFlags_InvokeMethod),
		//    2,
		//    CComVariant(addr),
		//    typeoftypRoutedEventHandler
		//    );

		//auto typIntPtr = asmMsCorlib->GetType_2(_bstr_t(L"System.IntPtr"));
		//auto intptrBtnEventHandler = CallMember(
		//    typIntPtr,
		//    nullptr, // CreateInstance uses null IUnknown target
		//    nullptr, // method name not needed for CreateInstance
		//    (BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
		//    1,
		//    CComVariant(addr)
		//    );

		//CallMember(
		//    typMarshal,
		//    nullptr,
		//    L"GetDelegateForFunctionPointer",
		//    (BindingFlags)(BindingFlags_Static | BindingFlags_Public | BindingFlags_InvokeMethod),
		//    2,
		//    CComVariant(addr),
		//    typeoftypRoutedEventHandler
		//    );
		//auto cvtBtnEventHandler = CallMember(
		//    typRoutedEventHandler,
		//    nullptr, // CreateInstance uses null IUnknown target
		//    nullptr, // method name not needed for CreateInstance
		//    (BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
		//    2,
		//    CComVariant(intptrBtnEventHandler),
		//    CComVariant(intptrBtnEventHandler)
		//    );

		//auto x = CallMember(
		//    typEventInfo,
		//    V_UNKNOWN(&cvtbtnClick),
		//    L"AddEventHandler",
		//    (BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
		//    2,
		//    cvtButton,
		//    CComVariant(addr)
		//    );
		//CallMember(
		//    V_UNKNOWN(&cvtNtnClickEventType),
		//    V_UNKNOWN(&cvtButton),
		//    L"sAddEventHandler",
		//    (BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
		//    2,
		//    CComVariant(),
		//    CComVariant()
		//    );

		CallMember(
			ptypButton,
			V_UNKNOWN(&cvtButton),
			L"Height",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(20)
			);
		CallMember(
			ptypButton,
			V_UNKNOWN(&cvtButton),
			L"Content",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(L"_Button")
			);
		CallMember(
			ptypButton,
			V_UNKNOWN(&cvtButton),
			L"Width",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(200)
			);
		auto cvtspChildren = CallMember(
			ptypSPanel,
			V_UNKNOWN(&cvtStackPanel),
			L"Children",
			(BindingFlags)(BindingFlags_GetProperty | BindingFlags_Instance | BindingFlags_Public),
			0,
			nullptr
			);
		_TypePtr ptypUICollection = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Controls.UIElementCollection"));
		CallMember(
			ptypUICollection,
			V_UNKNOWN(&cvtspChildren),
			L"Add",
			(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
			1,
			CComVariant(cvtButton.punkVal)
			);
		auto ptypTxtBox = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Controls.TextBox"));
		auto cvtTxtBox = CallMember(
			ptypTxtBox,
			nullptr, // CreateInstance uses null IUnknown target
			nullptr, // method name not needed for CreateInstance
			(BindingFlags)(BindingFlags_CreateInstance | BindingFlags_Instance | BindingFlags_Public),
			0,
			nullptr
			);
		CallMember(
			ptypTxtBox,
			V_UNKNOWN(&cvtTxtBox),
			L"Height",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(500)
			);

		auto typOftypTxtBox = CallMember(
			ptypTxtBox,
			ptypTxtBox, // target is the type
			L"GetType",
			(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
			0
			);

		auto typScrollBarVisibility = asmPresFwk->GetType_2(
			_bstr_t(L"System.Windows.Controls.ScrollBarVisibility"));
		// now we need the type of the type of ScrollBarVisibility on which to invoke the "GetEnumValues"
		auto typOftypScrollBarVisibility =
			CallMember(
				typScrollBarVisibility,
				typScrollBarVisibility, // target is the type
				L"GetType",
				(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
				0
				);

		auto vscrollberVisibility = CallMember(
			V_UNKNOWN(&typOftypTxtBox),
			V_UNKNOWN(&typOftypTxtBox),
			L"GetProperty",
			(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(L"VerticalScrollBarVisibility")
			);

		auto sauto = CallMember(
			V_UNKNOWN(&typOftypScrollBarVisibility),
			typScrollBarVisibility,
			L"GetField",
			(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant(L"Auto")
			);


		auto cvtEnumScrollBarVisibility =
			CallMember(
				V_UNKNOWN(&typOftypScrollBarVisibility),
				typScrollBarVisibility,
				L"GetEnumValues",
				(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
				0
				);
		CComSafeArray<int> psa = V_ARRAY(&cvtEnumScrollBarVisibility);
		auto val = psa.GetAt(1);
		//auto t1 = CallMember(
		//	ptypTxtBox,
		//	V_UNKNOWN(&cvtTxtBox),
		//	L"get_VerticalScrollBarVisibility",
		//	(BindingFlags)(BindingFlags_InvokeMethod| BindingFlags_Instance | BindingFlags_Public),
		//	1,
		//	sauto
		//	//            CComVariant(L"System.Windows.Controls.ScrollBarVisibility.Auto")
		//	);

		CallMember(
			ptypTxtBox,
			V_UNKNOWN(&cvtTxtBox),
			L"AcceptsReturn",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			CComVariant((bool)1)
			);
		for (int i = 0; i < 40; i++)
		{
			TCHAR buf[100];
			swprintf_s(buf, L"Some Text %d\r\n", i);
			CallMember(
				ptypTxtBox,
				V_UNKNOWN(&cvtTxtBox),
				L"AppendText",
				(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
				1,
				CComVariant(buf)
				);
		}
		auto ptypScrollBarVisibility = asmPresFwk->GetType_2(_bstr_t(L"System.Windows.Controls.ScrollBarVisbility"));
		//CallMember(
		//    ptypTxtBox,
		//    V_UNKNOWN(&cvtTxtBox),
		//    L"VerticalScrollBarVisibility",
		//    (BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
		//    1,
		//    CComVariant(1)
		//);

		CallMember(
			ptypUICollection,
			V_UNKNOWN(&cvtspChildren),
			L"Add",
			(BindingFlags)(BindingFlags_Instance | BindingFlags_Public | BindingFlags_InvokeMethod),
			1,
			CComVariant(cvtTxtBox.punkVal)
			);

		CallMember(
			ptypWindow,
			V_UNKNOWN(&cvtWindow),
			L"Content",
			(BindingFlags)(BindingFlags_SetProperty | BindingFlags_Instance | BindingFlags_Public),
			1,
			cvtStackPanel
			);
		CallMember(
			ptypWindow,
			oWindow.GetInterfacePtr(),
			L"ShowDialog",
			(BindingFlags)(BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
			0,
			nullptr
			);
		return hr;
	}
};


int __stdcall WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd
	)
{
	CoInitializeEx(0, COINIT_APARTMENTTHREADED);
	try
	{
		ClrUtility::DoWork();
	}
	catch (_com_error& cerr)
	{
		MessageBoxW(0, cerr.Description().GetBSTR(), cerr.ErrorMessage(), 0);
	}

	return 0;
}

</code>

Comments (1)

  1. qtxie says:

    Hi, It’s very interesting. I tried the code and want to hook the button event, but it’s failed as you said.

    I did some research and test, seems it’s not possible as we cann’t create IntPtr type in COM side. (https://msdn.microsoft.com/en-us/library/2x07fbw8(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#Anchor_3)

    So do you know any solution now?

Skip to main content