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
- Anonymous
December 04, 2016
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?