CLR host doesn't execute WPF applications but it does WinForms

2

I made a code very similar to https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0. The difference between both is that I'm loading the executable from memory while he's doing it from file. The other difference is that he calls some random method while I want to call Main method.

My goal is to be something like .NET Reactor's native loader.

The code works fine with Console Apps and WinForms. The only issue is that it won't load WPF applications and more specifically it fails on this line: pMethodInfo->Invoke_3(v2, p2, &v); with hr = 0x80131604 (COR_E_TARGETINVOCATION). The WPF project was created in VS 2019 and the targeting architecture (x86) and .NET Framework (4.0) match with loader's.

In the image below the Main function of the WPF application is present. I have literally no idea why it might be causing TargetInvocation.

enter image description here

Code here:

int LoadAssembly(LPVOID bytes, DWORD size)
{
    HRESULT hr;

    ICLRMetaHost* pMetaHost = NULL;
    ICLRRuntimeInfo* pRuntimeInfo = NULL;
    ICorRuntimeHost* pCorRuntimeHost = NULL;

    IUnknownPtr UnkAppDomain = NULL;
    _AppDomainPtr AppDomain = NULL;

    _AssemblyPtr pAssembly = NULL;
    _MethodInfoPtr pMethodInfo = NULL;

    SAFEARRAY* params = NULL;
    SAFEARRAY* sa = NULL;

    bool bSuccess = false;

    while (!bSuccess)
    {
        // STAThread
        CoInitialize(NULL);

        // Load and start the .NET runtime.
        hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
        if (FAILED(hr))
            break;

        // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
        // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
        hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
        if (FAILED(hr))
            break;

        // Check if the specified runtime can be loaded into the process. This 
        // method will take into account other runtimes that may already be 
        // loaded into the process and set pbLoadable to TRUE if this runtime can 
        // be loaded in an in-process side-by-side fashion.
        BOOL fLoadable;
        hr = pRuntimeInfo->IsLoadable(&fLoadable);
        if (FAILED(hr) || !fLoadable)
            break;

        // Load the CLR into the current process and return a runtime interface 
        // pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting  
        // interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost 
        // interface that was provided in .NET v1.x, and is compatible with all 
          // .NET Frameworks. 
        hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
        if (FAILED(hr))
            break;

        // Start the CLR
        hr = pCorRuntimeHost->Start();
        if (FAILED(hr))
            break;

        // Get a pointer to the default AppDomain in the CLR
        hr = pCorRuntimeHost->GetDefaultDomain(&UnkAppDomain);
        if (FAILED(hr))
            break;

        hr = UnkAppDomain->QueryInterface(IID_PPV_ARGS(&AppDomain));
        if (FAILED(hr))
            break;

        SAFEARRAYBOUND sab[1];
        sab[0].lLbound = 0;
        sab[0].cElements = size;

        sa = SafeArrayCreate(VT_UI1, 1, sab);
        if (!sa)
            break;

        void* sa_raw;
        hr = SafeArrayAccessData(sa, &sa_raw);
        if (FAILED(hr))
            break;

        memcpy(sa_raw, bytes, size);

        SafeArrayUnaccessData(sa);

        hr = AppDomain->Load_3(sa, &pAssembly);
        if (FAILED(hr))
            break;

        hr = pAssembly->get_EntryPoint(&pMethodInfo);
        if (FAILED(hr))
            break;

        SAFEARRAY* mtd_params;
        hr = pMethodInfo->GetParameters(&mtd_params);
        if (FAILED(hr))
            break;

        SAFEARRAY* p2;

        if (mtd_params->rgsabound->cElements != 0)
        {
            INT argc;
            WCHAR** _argv = CommandLineToArgvW(GetCommandLineW(), &argc);

            params = SafeArrayCreateVector(VT_BSTR, 0, argc);
            if (!params)
                break;

            for (int i = 0; i < argc; i++) 
            {
                long lIndex = i;

                hr = SafeArrayPutElement(params, &lIndex, SysAllocString(_argv[i]));
                if (FAILED(hr))
                    break;
            }

            p2 = SafeArrayCreateVector(VT_VARIANT, 0, 1);
            LONG l2 = 0;
            VARIANT vp2;

            vp2.vt = VT_ARRAY | VT_BSTR;
            vp2.parray = params;
            hr = SafeArrayPutElement(p2, &l2, &vp2);
            if (FAILED(hr))
                break;
        }
        else
        {
            SAFEARRAYBOUND sabc[1];
            sabc[0].cElements = 0;
            sabc[0].lLbound = 0;

            p2 = SafeArrayCreate(VT_VARIANT, 1, sabc);
        }

        VARIANT v;
        VARIANT v2;
        VariantInit(&v);
        VariantInit(&v2);
        hr = pMethodInfo->Invoke_3(v2, p2, &v);
        VariantClear(&v);
        VariantClear(&v2);
        if (FAILED(hr))
            break;

        bSuccess = true;
    }

    if (pMetaHost)
        pMetaHost->Release();
    if (pRuntimeInfo)
        pRuntimeInfo->Release();
    if (pCorRuntimeHost)
    {
        // Please note that after a call to Stop, the CLR cannot be 
        // reinitialized into the same process. This step is usually not 
        // necessary. You can leave the .NET runtime loaded in your process.
        pCorRuntimeHost->Stop();
        pCorRuntimeHost->Release();
    }
    if (sa)
        SafeArrayDestroy(sa);
    if (params)
        SafeArrayDestroy(params);

    return hr;
}

Edit: I tried to load a WPF application from C# using System.Reflection and it didn't work. There is the code which nearly the same as my C++ one except that the C++ one fixes the arguments:

byte[] data = File.ReadAllBytes("Test2.exe");
Assembly assembly = Assembly.Load(data);
assembly.EntryPoint.Invoke(null, new object[0]);

This is the same error I received in C++'s code except that I'm not quite sure how to check for the Inner Exceptions there.

enter image description here

I did some research and I realized that I have to change ResourceAssembly as they did here: Load WPF application from the memory. The new code looked like this:

byte[] data = File.ReadAllBytes("Test2.exe");
Assembly assembly = Assembly.Load(data);

Type type = typeof(Application);

FieldInfo field = type.GetField("_resourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
field.SetValue(null, assembly);

Type helper = typeof(BaseUriHelper);
PropertyInfo property = helper.GetProperty("ResourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
property.SetValue(null, assembly, null);

assembly.EntryPoint.Invoke(null, new object[0]);

The result was that the code loaded the WPF application. Now, the question is how can I do that in my C++ code? Seems like nobody dealt with that issue. The only solution I found was by loading the file from disk and not memory - http://sbytestream.pythonanywhere.com/blog/clr-hosting.

c++
wpf
clr
asked on Stack Overflow Jun 9, 2019 by nop • edited Jun 11, 2019 by nop

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0