1

I am trying to load a managed C# dll into a managed C# process by following this tutorial. I have done a fair bit of coding in C/C++ and have working knowledge of MS COM, but C# and managed code is a completely new beast for me, so be forgiving if I am doing anything wrong. I have .NET 4.5 on my system and this is the same runtime being used by default (I think). The code so far (mostly copied from the above link):

Code - C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace InjectSample
{
    public class Program
    {
        int EntryPoint(String pwzArgument)
        {
            System.Media.SystemSounds.Beep.Play();

            MessageBox.Show(
                "I am a managed app.\n\n" +
                "I am running inside: [" +
                System.Diagnostics.Process.GetCurrentProcess().ProcessName +
                "]\n\n" + (String.IsNullOrEmpty(pwzArgument) ?
                "I was not given an argument" :
                "I was given this argument: [" + pwzArgument + "]"));

            return 0;
        }

        static void Main(string[] args)
        {
            Program prog = new Program();
            prog.EntryPoint("hello world");
        }
    }
}

Native Code

#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
#import "mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref") \
    rename("ReportEvent", "InteropServices_ReportEvent")


#include <strsafe.h>
void ErrorExit(LPCWSTR lpszFunction, DWORD dwLastError) 
{ 
    if(dwLastError == 0) return;
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dwFlag = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
    FormatMessage( dwFlag, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dwLastError, lpMsgBuf); 

    ::MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

int wmain(int argc, wchar_t* argv[])
{
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    ICLRRuntimeHost *pClrRuntimeHost = NULL;

    // build runtime
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
        IID_PPV_ARGS(&pClrRuntimeHost));

    // start runtime
    hr = pClrRuntimeHost->Start();

    // execute managed assembly
    DWORD pReturnValue;
    SetLastError(0);
    hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
        L"C:\\Temp\\InjectSample.exe", 
        L"InjectExample.Program", 
        L"int EntryPoint(String pwzArgument)", 
        L"hello .net runtime", 
        &pReturnValue);

    ErrorExit(L"ExecuteInDefaultAppDomain()", GetLastError());
    // free resources
    pMetaHost->Release();
    pRuntimeInfo->Release();
    pClrRuntimeHost->Release();

    return 0;
}

Problem

Now the problem is that when I execute the native code, GetLastError() returns 0. That is just after the call to ExecuteInDefaultAppDomain(). As per the Codeproject link, it should have shown the dialog box, but in my case it is not showing anything.

I am not sure about the problem, any suggestion/pointer will be helpful. Thanks.

Favonius
  • 13,959
  • 3
  • 55
  • 95

1 Answers1

3

The hosting api uses COM, it doesn't report errors through GetLastError(). The return value of the method is the error code. It is an HRESULT, the hosting api generally returns error codes like 0x8013xxxx. You'll find the xxxx values in the CorError.h SDK header.

You'll need something like this:

hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(...);
if (FAILED(hr)) ErrorExit(L"Execute", hr);

And add this check to every call you make. Not doing this is likely to make your program crash in a hard to diagnose way. And you need all help you can get, you no longer have the friendly .NET exceptions to tell you what went wrong.

Other survival strategies are using mixed-mode debugging so you have a shot at diagnosing managed exceptions before they turn into an HRESULT, writing an AppDomain.CurrentDomain.UnhandledException event handler in your managed code, using IErrorInfo so you can get the exception message in your host, using the proper method name, it is L"EntryPoint", and making it static as required.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks Hans, I have modified my native code to have `CoInitialize` and `CoUninitialize()`, after this I am getting `Error code - 127: the specified procedure could not be found` (this is for GetLastError()) also, interrogating HRESULT value gives `-2146233318 or CORSEC_E_INVALID_STRONGNAME`. Any idea what could be the reason, I have tried signing the assembly by right clicking->properties->signing->checking `sign the assembly` and `delay sign only`. Thanks. – Favonius Feb 02 '14 at 15:33
  • Also, I am getting the same error even after changing the method name to `L"EntryPoint"`. I think it is related to `CORSEC_E_INVALID_STRONGNAME` but I am not sure. – Favonius Feb 02 '14 at 15:36
  • 2
    Do stop using GetLastError(), you cannot rely on its value. No idea what could be wrong with the strong name. Btw, another mistake is that EntryPoint() must be a static method. Use samples you find before spinning your own. Good luck with it. – Hans Passant Feb 02 '14 at 15:46