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