Link to home
Start Free TrialLog in
Avatar of stevedell
stevedell

asked on

C++ - Loading Managed Assembly From Memory in Unmanaged Process

Hello!
I'm looking to load a managed assembly from memory (from a byte array) in an unmanaged process, similar to what applications like .NET Reactor, Enigma Virtual Box, and others do with their native stubs.

I am currently trying in C++- here's the code, based from https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0

void __stdcall three(LPCWSTR pszVersion)
{
	    HRESULT hr;

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

    // 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. 
    ICorRuntimeHost *pCorRuntimeHost = NULL;

    IUnknownPtr spAppDomainThunk = NULL;
    _AppDomainPtr spDefaultAppDomain = NULL;

    // The .NET assembly to load.

    _AssemblyPtr spAssembly = NULL;

    // The .NET class to instantiate.

    _TypePtr spType = NULL;
    variant_t vtObject;
    variant_t vtEmpty;

    // The static method in the .NET class to invoke.
    SAFEARRAY *psaStaticMethodArgs = NULL;
    variant_t vtStringArg(L"HelloWorld");
    variant_t vtLengthRet;
	bstr_t bstrClassName(L"Security.Startup"); 
	bstr_t bstrStaticMethodName(L"Startup.Main");
    // The instance method in the .NET class to invoke.
    SAFEARRAY *psaMethodArgs = NULL;
    variant_t vtStringRet;

    // 
    // Load and start the .NET runtime.
    // 

    wprintf(L"Load and start the .NET runtime %s \n", pszVersion);

    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if (FAILED(hr))
    {
        wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);

    }

    // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
    // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
    hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
    if (FAILED(hr))
    {
        wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);

    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);

    }

    if (!fLoadable)
    {
        wprintf(L".NET runtime %s cannot be loaded\n", pszVersion);

    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);

    }

    // Start the CLR.
    hr = pCorRuntimeHost->Start();
    if (FAILED(hr))
    {
        wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);

    }

    // 
    // Load the NET assembly. Call the static method GetStringLength of the 
    // class CSSimpleObject. Instantiate the class CSSimpleObject and call 
    // its instance method ToString.
    // 

    // The following C++ code does the same thing as this C# code:
    // 
    //   Assembly assembly = AppDomain.CurrentDomain.Load(pszAssemblyName);
    //   object length = type.InvokeMember("GetStringLength", 
    //       BindingFlags.InvokeMethod | BindingFlags.Static | 
    //       BindingFlags.Public, null, null, new object[] { "HelloWorld" });
    //   object obj = assembly.CreateInstance("CSClassLibrary.CSSimpleObject");
    //   object str = type.InvokeMember("ToString", 
    //       BindingFlags.InvokeMethod | BindingFlags.Instance | 
    //       BindingFlags.Public, null, obj, new object[] { });

    // Get a pointer to the default AppDomain in the CLR.
    hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
    if (FAILED(hr))
    {
        wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);

    }

    hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
    if (FAILED(hr))
    {
        wprintf(L"Failed to get default AppDomain w/hr 0x%08lx\n", hr);

    }

    // Load the .NET assembly.
	FILE * pFile;
  long lSize;
  char * buffer;
  size_t result;

  pFile = fopen ( "c:\\Security.exe" , "rb" );
  if (pFile==NULL) {fputs ("File error",stderr); exit (1);}

  // obtain file size:
  fseek (pFile , 0 , SEEK_END);
  lSize = ftell (pFile);
  rewind (pFile);

  // allocate memory to contain the whole file:
  buffer = (char*) malloc (sizeof(char)*lSize);
  if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}

  // copy the file into the buffer:
  result = fread (buffer,1,lSize,pFile);
  SAFEARRAYBOUND bounds = { lSize, 0 };
	
	SAFEARRAY *psa = SafeArrayCreate(VT_UI1, 1, &bounds);
	void* data;
	SafeArrayAccessData(psa, &data);
	CopyMemory(data, buffer, lSize);
	SafeArrayUnaccessData(psa);
    hr = spDefaultAppDomain->Load_3(psa, &spAssembly);
    if (FAILED(hr))
    {
        wprintf(L"Failed to load the assembly w/hr 0x%08lx\n", hr);

    }

    // Get the Type of CSSimpleObject.
    hr = spAssembly->GetType_2(bstrClassName, &spType);
    if (FAILED(hr))
    {
        wprintf(L"Failed to get the Type interface w/hr 0x%08lx\n", hr);

    }

    // Call the static method of the class: 
    //   public static int GetStringLength(string str);

    // Create a safe array to contain the arguments of the method. The safe 
    // array must be created with vt = VT_VARIANT because .NET reflection 
    // expects an array of Object - VT_VARIANT. There is only one argument, 
    // so cElements = 1.
    psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
    LONG index = 0;
    hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
    if (FAILED(hr))
    {
        wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr);

    }

    hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast<BindingFlags>(
        BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public), 
        NULL, vtEmpty, psaStaticMethodArgs, &vtLengthRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to invoke Main w/hr 0x%08lx\n", hr); //[b]hr = "Member not found"[/b]

    }

    // Print the call result of the static method.
    wprintf(L"Call %s.%s(\"%s\") => %d\n", 
        static_cast<PCWSTR>(bstrClassName), 
        static_cast<PCWSTR>(bstrStaticMethodName), 
        static_cast<PCWSTR>(vtStringArg.bstrVal), 
        vtLengthRet.lVal);

    // Instantiate the class.
    hr = spAssembly->CreateInstance(bstrClassName, &vtObject);
    if (FAILED(hr))
    {
        wprintf(L"Assembly::CreateInstance failed w/hr 0x%08lx\n", hr);

    }

    // Call the instance method of the class.
    //   public string ToString();







    if (psaStaticMethodArgs)
    {
        SafeArrayDestroy(psaStaticMethodArgs);
        psaStaticMethodArgs = NULL;
    }
    if (psaMethodArgs)
    {
        SafeArrayDestroy(psaMethodArgs);
        psaMethodArgs = NULL;
    }

    return ;
}

Open in new window


So basically I'm trying to invoke the Main method of the assembly.


Picture of assembly methods and classes:
User generated image
Avatar of Karrtik Iyer
Karrtik Iyer
Flag of India image

Hi Steve,
Where are you stuck currently, what problem are you facing? Sorry I was not able to get that information from your question,

Thanks,
Karrtik
Avatar of stevedell
stevedell

ASKER

Sorry I don't think I explained very well.

Basically at this line of code:
hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast<BindingFlags>(
        BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public), 
        NULL, vtEmpty, psaStaticMethodArgs, &vtLengthRet);

Open in new window

InvokeMember_3 returns "Member not found" (pertaining to the Main method, I assume).

So basically I think the ultimate problem is invoking the Main method. For some reason it cannot find it.
Hi Steve,
I could observe 2 differences as compared to example from the link that you had posted.
1> To load the .NET assembly you have used Load_3 instead of Load_2 as in example. however further HR checks are ensuring that this happening correctly.
2> The static Method name in your code is Startup.Main. Do you want to try with static method name as "Main" instead as in the example?
//your code
bstr_t bstrClassName(L"Security.Startup"); 
bstr_t bstrStaticMethodName(L"Startup.Main");

Open in new window

However in the example, the bstrStaticMethodName is only "GetStringLength" without the class name prefix.
//Example code
bstr_t bstrStaticMethodName(L"GetStringLength");

Open in new window

Yes I tried as both Startup.Main and Main :(!
Ok, let me create a C# exe and try loading the main method.
Thank you for your help !
Hi Steve,
Just for testing purposes I create another static public function in C# exe like below. And I was able to successfully invoke it. See screenshot below. I believe this works because the input argument to this function is string so creation of parameter VT_VARIANT suffices, however for main it is an array of string (string []), so I wonder if we need to do create a type VT_VECTOR of VT_VARIANTS to invoke main, I am giving it a try, shall let you know.
   public static void AnotherFunction(string args)
        {
            Console.WriteLine("Hello I am in MainTest");
        }

Open in new window

Output:
User generated imagethe c++ code I used is:
HRESULT RuntimeHostCSharpExe(PCWSTR pszVersion)
{
    HRESULT hr;

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

    // 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. 
    ICorRuntimeHost *pCorRuntimeHost = NULL;

    IUnknownPtr spAppDomainThunk = NULL;
    _AppDomainPtr spDefaultAppDomain = NULL;

    // The .NET assembly to load.
    //bstr_t bstrAssemblyName(pszAssemblyName);
    _AssemblyPtr spAssembly = NULL;

    // The .NET class to instantiate.
    bstr_t bstrClassName(L"MainTest.Program");
    _TypePtr spType = NULL;
    variant_t vtObject;
    variant_t vtEmpty;

    // The static method in the .NET class to invoke.
    bstr_t bstrStaticMethodName(L"AnotherFunction");
    SAFEARRAY *psaStaticMethodArgs = NULL;
    variant_t vtStringArg(L"HelloWorld");
    variant_t vtLengthRet;

    // The instance method in the .NET class to invoke.
    bstr_t bstrMethodName(L"ToString");
    SAFEARRAY *psaMethodArgs = NULL;
    variant_t vtStringRet;

    // 
    // Load and start the .NET runtime.
    // 

    wprintf(L"Load and start the .NET runtime %s \n", pszVersion);

    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if (FAILED(hr))
    {
        wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
    // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
    hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
    if (FAILED(hr))
    {
        wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    if (!fLoadable)
    {
        wprintf(L".NET runtime %s cannot be loaded\n", pszVersion);
        goto Cleanup;
    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Start the CLR.
    hr = pCorRuntimeHost->Start();
    if (FAILED(hr))
    {
        wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // 
    // Load the NET assembly. Call the static method GetStringLength of the 
    // class CSSimpleObject. Instantiate the class CSSimpleObject and call 
    // its instance method ToString.
    // 

    // The following C++ code does the same thing as this C# code:
    // 
    //   Assembly assembly = AppDomain.CurrentDomain.Load(pszAssemblyName);
    //   object length = type.InvokeMember("GetStringLength", 
    //       BindingFlags.InvokeMethod | BindingFlags.Static | 
    //       BindingFlags.Public, null, null, new object[] { "HelloWorld" });
    //   object obj = assembly.CreateInstance("CSClassLibrary.CSSimpleObject");
    //   object str = type.InvokeMember("ToString", 
    //       BindingFlags.InvokeMethod | BindingFlags.Instance | 
    //       BindingFlags.Public, null, obj, new object[] { });

    // Get a pointer to the default AppDomain in the CLR.
    hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
    if (FAILED(hr))
    {
        wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
    if (FAILED(hr))
    {
        wprintf(L"Failed to get default AppDomain w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Load the .NET assembly.
    wprintf(L"Load the assembly %s\n", L"MainTest.exe");
	 // Load the .NET assembly.
	FILE * pFile;
	long lSize;
	char * buffer;
	size_t result;

	pFile = fopen ( "E:\\tsip\\codeblocks_examples\\expertsexchange\\CppHostCLR\\C# and C++\\C#,C++\\MainTest\\bin\\Debug\\MainTest.exe" , "rb" );
	if (pFile==NULL) {fputs ("File error",stderr); exit (1);}

	// obtain file size:
	fseek (pFile , 0 , SEEK_END);
	lSize = ftell (pFile);
	rewind (pFile);

	// allocate memory to contain the whole file:
	buffer = (char*) malloc (sizeof(char)*lSize);
	if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}

	// copy the file into the buffer:
	result = fread (buffer,1,lSize,pFile);
	SAFEARRAYBOUND bounds = { lSize, 0 };
	
	SAFEARRAY *psa = SafeArrayCreate(VT_UI1, 1, &bounds);
	void* data;
	SafeArrayAccessData(psa, &data);
	CopyMemory(data, buffer, lSize);
	SafeArrayUnaccessData(psa);
	hr = spDefaultAppDomain->Load_3(psa, &spAssembly);
	if (FAILED(hr))
	{
		wprintf(L"Failed to load the assembly w/hr 0x%08lx\n", hr);
		goto Cleanup;
	}

   /* hr = spDefaultAppDomain->Load_2(bstrAssemblyName, &spAssembly);
    if (FAILED(hr))
    {
        wprintf(L"Failed to load the assembly w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }*/

    // Get the Type of CSSimpleObject.
    hr = spAssembly->GetType_2(bstrClassName, &spType);
    if (FAILED(hr))
    {
        wprintf(L"Failed to get the Type interface w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Call the static method of the class: 
    //   public static int GetStringLength(string str);

    // Create a safe array to contain the arguments of the method. The safe 
    // array must be created with vt = VT_VARIANT because .NET reflection 
    // expects an array of Object - VT_VARIANT. There is only one argument, 
    // so cElements = 1.
    psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
    LONG index = 0;
    hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
    if (FAILED(hr))
    {
        wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Invoke the "GetStringLength" method from the Type interface.
    hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast<BindingFlags>(
        BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public), 
        NULL, vtEmpty, psaStaticMethodArgs, &vtLengthRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to invoke GetStringLength w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Print the call result of the static method.
    wprintf(L"Call %s.%s(\"%s\") => %d\n", 
        static_cast<PCWSTR>(bstrClassName), 
        static_cast<PCWSTR>(bstrStaticMethodName), 
        static_cast<PCWSTR>(vtStringArg.bstrVal), 
        vtLengthRet.lVal);

    // Instantiate the class.
    hr = spAssembly->CreateInstance(bstrClassName, &vtObject);
    if (FAILED(hr))
    {
        wprintf(L"Assembly::CreateInstance failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Call the instance method of the class.
    //   public string ToString();

    // Create a safe array to contain the arguments of the method.
    psaMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);

    // Invoke the "ToString" method from the Type interface.
    hr = spType->InvokeMember_3(bstrMethodName, static_cast<BindingFlags>(
        BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
        NULL, vtObject, psaMethodArgs, &vtStringRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to invoke ToString w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Print the call result of the method.
    wprintf(L"Call %s.%s() => %s\n", 
        static_cast<PCWSTR>(bstrClassName), 
        static_cast<PCWSTR>(bstrMethodName), 
        static_cast<PCWSTR>(vtStringRet.bstrVal));

Cleanup:

    if (pMetaHost)
    {
        pMetaHost->Release();
        pMetaHost = NULL;
    }
    if (pRuntimeInfo)
    {
        pRuntimeInfo->Release();
        pRuntimeInfo = NULL;
    }
    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.
        //wprintf(L"Stop the .NET runtime\n");
        //pCorRuntimeHost->Stop();

        pCorRuntimeHost->Release();
        pCorRuntimeHost = NULL;
    }

    if (psaStaticMethodArgs)
    {
        SafeArrayDestroy(psaStaticMethodArgs);
        psaStaticMethodArgs = NULL;
    }
    if (psaMethodArgs)
    {
        SafeArrayDestroy(psaMethodArgs);
        psaMethodArgs = NULL;
    }

    return hr;
}

Open in new window

Hi Steve,
I am still not there, but I have found that to invoke an C# .NET exe from CPP, we need to call ExecuteAssembly method of Default App domain, instead of doing load as in case of DLL. (
spDefaultAppDomain->ExecuteAssembly_2(bstrExeName,&retval);

Open in new window

)
I am almost there, but I am getting Invalid Arg error, but I think Execute Assembly is the correct method to call which would automatically invoke the main of a C# program. Something like below,
HRESULT RuntimeHostCSharpExe(PCWSTR pszVersion)
{
    HRESULT hr;

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

    // 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. 
    ICorRuntimeHost *pCorRuntimeHost = NULL;

    IUnknownPtr spAppDomainThunk = NULL;
    _AppDomainPtr spDefaultAppDomain = NULL;

    // The .NET assembly to load.
    //bstr_t bstrAssemblyName(pszAssemblyName);
    _AssemblyPtr spAssembly = NULL;

    // The .NET class to instantiate.
    bstr_t bstrClassName(L"MainTest.Program");
    _TypePtr spType = NULL;
    variant_t vtObject;
    variant_t vtEmpty;

    // The static method in the .NET class to invoke.
	bstr_t bstrExeName(L"E:\\tsip\\codeblocks_examples\\expertsexchange\\CppHostCLR\\C# and C++\\C#,C++\\CppHostCLR\\CppHostCLR\Debug\\MainTest.exe");
    bstr_t bstrStaticMethodName(L"Main");
    SAFEARRAY *psaStaticMethodArgs = NULL;
    variant_t vtStringArg(L"HelloWorld");
    variant_t vtLengthRet;

    // The instance method in the .NET class to invoke.
    bstr_t bstrMethodName(L"ToString");
    SAFEARRAY *psaMethodArgs = NULL;
    variant_t vtStringRet;

    // 
    // Load and start the .NET runtime.
    // 

    wprintf(L"Load and start the .NET runtime %s \n", pszVersion);

    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if (FAILED(hr))
    {
        wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
    // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
    hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
    if (FAILED(hr))
    {
        wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    if (!fLoadable)
    {
        wprintf(L".NET runtime %s cannot be loaded\n", pszVersion);
        goto Cleanup;
    }

    // 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))
    {
        wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Start the CLR.
    hr = pCorRuntimeHost->Start();
    if (FAILED(hr))
    {
        wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // 
    // Load the NET assembly. Call the static method GetStringLength of the 
    // class CSSimpleObject. Instantiate the class CSSimpleObject and call 
    // its instance method ToString.
    // 

   
    // Get a pointer to the default AppDomain in the CLR.
    hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
    if (FAILED(hr))
    {
        wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
    if (FAILED(hr))
    {
        wprintf(L"Failed to get default AppDomain w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }

    // Load the .NET assembly.
    wprintf(L"Load the assembly %s\n", L"MainTest.exe");
	//
    // Load an Exe Assembly and call Main()
    //
    
  /*   psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
	 LONG index = 0;
	 hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
	 if (FAILED(hr))
    {
        wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr);
        goto Cleanup;
    }*/
	
	long retval = 0;
	
    hr = spDefaultAppDomain->ExecuteAssembly_2(bstrExeName,&retval);//,nullptr,psaStaticMethodArgs,&retval);
    if (FAILED(hr))
	{
		wprintf(L"Failed to excute the assembly w/hr 0x%08lx\n", hr);		
		goto Cleanup;
	}

	
Cleanup:

    if (pMetaHost)
    {
        pMetaHost->Release();
        pMetaHost = NULL;
    }
    if (pRuntimeInfo)
    {
        pRuntimeInfo->Release();
        pRuntimeInfo = NULL;
    }
    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.
        //wprintf(L"Stop the .NET runtime\n");
        //pCorRuntimeHost->Stop();

        pCorRuntimeHost->Release();
        pCorRuntimeHost = NULL;
    }

    if (psaStaticMethodArgs)
    {
        SafeArrayDestroy(psaStaticMethodArgs);
        psaStaticMethodArgs = NULL;
    }
    if (psaMethodArgs)
    {
        SafeArrayDestroy(psaMethodArgs);
        psaMethodArgs = NULL;
    }

    return hr;
}

Open in new window

Hi Karrtik,
I really appreciate your help.

The problem with using ExecuteAssembly is that it requires that the assembly on disk. I need to load it from memory, like programs such as .NET Reactor does. I'm trying to see how they do it in their native stub, but it's very confusing since apparently Microsoft didn't include symbols in their .NET libraries.

Very cool that you got it to work with another static function. I wonder why it wouldn't work with Main...
Thanks Steve, I shall continuing investigating further to see if we can load Main from memory in some way.
Thanks Karrtik :d hugely appreciated. wish there was a way for me to compensate you more than giving you points
Hey,
I found this function in the assembly...
	mscorlib::_MethodInfo *pVal = NULL;
	spAssembly->get_EntryPoint(&pVal);

Open in new window

Then pVal has 3 interesting functions... Invoke, Invoke_2 and Invoke_3

All of them call for data I don't know how to generate however.

This might be the answer
That's fine Steve :-) Your kind comments are good enough. I shall look into your last comment about get_entrypoint.
Hi Steve, have you considered building a c++/cli layer which would act as bridge between unmanaged and managed world?
Hi Steve, have you considered building a c++/cli layer which would act as bridge between unmanaged and managed world?
Not 100% sure what that means. Essentially what I want to do is to make a native stub to load a managed assembly from a byte array.

I know that .NET Reactor, Enigma Virtual Box  are doing something similar to this method, but I don't know exactly how it's working
Ok, no problems, I understand what you wish to do, give me some more time to think and try out couple of things before proposing any solution for you.
Btw, just to update you. another static function which I was able to call in the exe, stopped working as soon as I changed the function signature of that function from a normal c# string to c# string array (string [] args) as in case of main function. This tells me that we need to send some different datatype other VT Variant for string [] to work.
Oh ok great to know. Thanks again! :d
I think that there are no parameters for the function Main actually. I made a test app and purposely made the argument a string array, and look at the difference
User generated image
I think the get_Entrypoint is likely the way to go on this. I don't know how to properly call those Invoke functions though
ASKER CERTIFIED SOLUTION
Avatar of Karrtik Iyer
Karrtik Iyer
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ahh yes it worked :D

I'm going to credit the solution to you now

And I'm going to reopen another question related to doing this with get_Entrypoint. I'd love your help with that too!

Thank you so much for your help. unbelievably helpful and its much appreciated
Very helpful expert
You are welcome Steve.
Where is byte array [] ??? Can u put all Source code ?