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>