BoxedApp Blog

BoxedApp: Tips’n'Tricks, Examples, Use Cases etc.

.NET Runtime Embedding


Share this post Bookmark and Share

If you are into developing .NET applications, you are likely to know that it requires .NET Runtime to have the success running them. If the runtime suite is not installed, whenever the program attempts to launch, it shows an ugly messagebox notifying you that mscoree.dll could not be found.

So, we’ve got a .net application WindowsApplication1.exe, which displays a simple form with a progress bar and a couple of links. Now we are to create the environment where WindowsApplication1.exe would successfully launch on systems that don’t have .net runtime.

BoxedApp SDK creates the infrastructure of a virtual file system and a virtual registry; so once the corresponding virtual kes and virtual files are created, the .NET application can be launched successfully.

It is known that the hive HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework contains the key InstallRoot that points to the folder with subfolders with different versions of.net runtime. For instance, on my computer the key contains the value содержит значение “C:\WINDOWS\Microsoft.NET\Framework”. That folder includes the following runtime versions:

v1.0.3705
v1.1.4322
v2.0.50727
v3.0
v3.5

In this example, we assume that our runtime version is 2.0.50727. For the folder for storing our runtime files, let’s choose some path; e.g., C:\DotNet. Next, let’s create the virtual files within that folder, and the application will see it as if .net runtime was actually installed on the computer.

So, let’s create our virtual key:

// Initialize BoxedAppSDK
BoxedAppSDK_Init();

tstring strDotNetRoot = _T("C:\\DotNet");

DWORD dwDisposition;
HKEY hKey__DotNet;
LONG lRes = 
BoxedAppSDK_CreateVirtualRegKey(
	HKEY_LOCAL_MACHINE, 
	_T("SOFTWARE\\Microsoft\\.NETFramework"), 
	0, 
	NULL, 
	REG_OPTION_NON_VOLATILE, 
	KEY_ALL_ACCESS, 
	NULL, 
	&hKey__DotNet, 
	&dwDisposition);

LPCTSTR szValue = _T("C:\\DotNet\\");
RegSetValueEx(hKey__DotNet, _T("InstallRoot"), 0, REG_SZ, (CONST BYTE*)szValue, (lstrlen(szValue) + 1) * sizeof(TCHAR));

We will need paths to the system folders:

TCHAR szWinDir[MAX_PATH] = { 0 };
GetWindowsDirectory(szWinDir, MAX_PATH);
tstring strWinDir = szWinDir;

TCHAR szSystemDir[MAX_PATH] = { 0 };
GetSystemDirectory(szSystemDir, MAX_PATH);
tstring strSystemDir = szSystemDir;

In the resource file, specify which files are to be embedded in the application. We are going to embed both .net runtime and the WindowsApplication1.exe files, plus the AppLauncher.dll assembly, which we’re going to cover a bit further:

WindowsApplication1.exe BIN "WindowsApplication1.exe"
AppLauncher.dll BIN "AppLauncher.dll"

mscoree.dll BIN "system32\\mscoree.dll"
mscorjit.dll BIN "v2.0.50727\\mscorjit.dll"
mscorwks.dll BIN "v2.0.50727\\mscorwks.dll"
comctl32.dll BIN "WinSxS\\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2982_x-ww_ac3f9c03\\comctl32.dll"
msvcr80.dll BIN "WinSxS\\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.1433_x-ww_5cf844d2\\msvcr80.dll"
msvcr80d.dll BIN "WinSxS\\x86_Microsoft.VC80.DebugCRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_f75eb16c\\msvcr80d.dll"
System.Windows.Forms.dll BIN "assembly\\GAC_MSIL\\System.Windows.Forms\\2.0.0.0__b77a5c561934e089\\System.Windows.Forms.dll"
mscorlib.dll BIN "assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\mscorlib.dll"
mscorrc.dll BIN "v2.0.50727\\mscorrc.dll"
sorttbls.nlp BIN "assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\sorttbls.nlp"
sortkey.nlp BIN "assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\sortkey.nlp"
System.dll BIN "v2.0.50727\\System.dll"
System.Data.dll BIN "v2.0.50727\\System.Data.dll"
System.Drawing.dll BIN "v2.0.50727\\System.Drawing.dll"
Culture.dll BIN "v2.0.50727\\Culture.dll"
Accessibility.dll BIN "v2.0.50727\\Accessibility.dll"
GdiPlus.dll BIN "system32\\GdiPlus.dll"

In the properties of the rc file (right-click on the rc file and then select Properties, Configuration Properties -> Resources -> General, Additional Include Directories), specify the additional folders to be searched for the files:


"$(FrameworkDir)";"$(SystemRoot)";"..\WindowsApplication1\bin\x86\$(ConfigurationName)";"..\AppLauncher\bin\x86\$(ConfigurationName)"

Here’s a light helper function for handling resources:

void LoadResourceHelper( /* in */ LPCTSTR lpszName, 
                         /* in */ LPCTSTR lpszType, 
                         /* out */ LPVOID& lpData, 
                         /* out */ DWORD& dwSize)
{
    HMODULE hModule = GetModuleHandle(NULL);
    HRSRC hResInfo = FindResource(hModule, lpszName, lpszType);
    HGLOBAL hResData = LoadResource(hModule, hResInfo);
    lpData = LockResource(hResData);
    dwSize = SizeofResource(hModule, hResInfo);
}

And here’s the function for creating a virtual file; the content will be taken from the specified resource:

void CreateVirtualFileFromResource(LPCTSTR szVirtualPath, LPCTSTR szResName, LPCTSTR szResType)
{
	DWORD temp;

	LPVOID pData;
	DWORD dwSize;

	LoadResourceHelper(szResName, szResType, pData, dwSize);

	HANDLE hFile = BoxedAppSDK_CreateVirtualFile(szVirtualPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, 0, NULL);
	WriteFile(hFile, pData, dwSize, &temp, NULL);
	CloseHandle(hFile);
}

Now we can create the virtual files, required for the successful performance of the .net runtime:

struct SFileResInfo
{
	tstring strVirtualPath;
	LPCTSTR szResName;
};

const SFileResInfo ResInfo[] = 
{
		{ strSystemDir + _T("\\mscoree.dll"), _T("mscoree.dll") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\mscorjit.dll"), _T("mscorjit.dll") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\mscorwks.dll"), _T("mscorwks.dll") }, 

		{ strWinDir + _T("\\WinSxS\\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2982_x-ww_ac3f9c03\\comctl32.dll"), _T("comctl32.dll") }, 
		{ strSystemDir + _T("\\comctl32.dll"), _T("comctl32.dll") }, 

		{ strWinDir + _T("\\WinSxS\\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.1433_x-ww_5cf844d2\\msvcr80.dll"), _T("msvcr80.dll") }, 
		{ strSystemDir + _T("\\msvcr80.dll"), _T("msvcr80.dll") }, 

		{ strWinDir + _T("\\assembly\\GAC_MSIL\\System.Windows.Forms\\2.0.0.0__b77a5c561934e089\\System.Windows.Forms.dll"), _T("System.Windows.Forms.dll") }, 

		{ strWinDir + _T("\\assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\mscorlib.dll"), _T("mscorlib.dll") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\mscorrc.dll"), _T("mscorrc.dll") }, 

		{ strWinDir + _T("\\assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\sorttbls.nlp"), _T("sorttbls.nlp") }, 
		{ strWinDir + _T("\\assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\sortkey.nlp"), _T("sortkey.nlp") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\System.dll"), _T("System.dll") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\System.Data.dll"), _T("System.Data.dll") }, 
		{ strDotNetRoot + _T("\\v2.0.50727\\System.Drawing.dll"), _T("System.Drawing.dll") }, 

		{ strDotNetRoot + _T("\\v2.0.50727\\Culture.dll"), _T("Culture.dll") }, 

		{ strWinDir + _T("\\assembly\\GAC_MSIL\\System\\2.0.0.0__b77a5c561934e089\\System.dll"), _T("System.dll") }, 
		{ strWinDir + _T("\\assembly\\GAC_MSIL\\System.Drawing\\2.0.0.0__b03f5f7f11d50a3a\\System.Drawing.dll"), _T("System.Drawing.dll") }, 

		{ strWinDir + _T("\\Microsoft.NET\\Framework\\v2.0.50727\\Accessibility.dll"), _T("Accessibility.dll") }, 
		{ strWinDir + _T("\\assembly\\GAC_MSIL\\Accessibility\\2.0.0.0__b03f5f7f11d50a3a\\Accessibility.dll"), _T("Accessibility.dll") }, 

		{ strSystemDir + _T("\\GdiPlus.dll"), _T("GdiPlus.dll") }, 


		{ _T("C:\\AppLauncher.dll"), _T("AppLauncher.dll") }, 
		{ _T("C:\\WindowsApplication1.exe"), _T("WindowsApplication1.exe") }
};

for (int i = 0; i < sizeof(ResInfo) / sizeof(ResInfo[0]); i++)
	CreateVirtualFileFromResource(ResInfo[i].strVirtualPath.c_str(), ResInfo[i].szResName, _T("BIN"));

To get along with .net runtime, we’re going to use the mscoree.dll library (please note that mscoree.dll is embedded in the application). mscoree.dll exports the function CorBindToRuntimeEx, which will give us the interface ICLRRuntimeHost. The method ICLRRuntimeHost::ExecuteInDefaultAppDomain loads the assembly and calls the specified method. However, that method is to have a very specific signature; you cannot call just any method, but we’re going to cover this later. So, here’s our ICLRRuntimeHost:

HMODULE hMSCoree = LoadLibrary((strSystemDir + _T("\\mscoree.dll")).c_str());

typedef HRESULT (__stdcall *P_CorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
P_CorBindToRuntimeEx pCorBindToRuntimeEx = (P_CorBindToRuntimeEx)GetProcAddress(hMCoree, "CorBindToRuntimeEx");

ICLRRuntimeHost* pCLRRuntimeHost = NULL;
pCorBindToRuntimeEx(L"v2.0.50727", NULL, 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**)&pCLRRuntimeHost);

ICLRRuntimeHost::ExecuteInDefaultAppDomain can be called only by a method with the following signature:

int SomeMethod (string Argument);

Now, our job is to load the assembly of the .net application and call its point of entry. However, the point of entry has a different signature:

static void Main();

Therefore, we’re going to create another assembly, which will be a class with a static method, which in its turn will load the assembly of the main application and call its point of entry:

using System;
using System.Reflection;
using System.Text;

namespace AppLauncher
{
    public class Launcher
    {
        public static int Launch(string strPath)
        {
            Assembly assembly = Assembly.LoadFile(strPath);
            assembly.EntryPoint.Invoke(null, null);

            return 0;
        }
    }
}

Now we’ve got everything ready for running the application:

pCLRRuntimeHost->Start();

DWORD nRetValue;
pCLRRuntimeHost->ExecuteInDefaultAppDomain(
	L"C:\\AppLauncher.dll", 
	L"AppLauncher.Launcher", 
	L"Launch", 
	L"C:\\WindowsApplication1.exe", 
	&nRetValue);

pCLRRuntimeHost->Stop();

pCLRRuntimeHost->Release();

FreeLibrary(hMSCoree);

Related links:

Share this post Bookmark and Share




Comments



1
Author:  Interesting Finds: 2008.06.18 - gOODiDEA.NET | Date:  June 18, 2008 | Time:  3:07 am

[...] .NET Runtime Embedding [...]

2
Author:  Portable PowerShell | keyongtech | Date:  January 18, 2009 | Time:  8:10 pm

[...] Portable PowerShell Also there is a good idea here: http://boxedapp.com/blog/2008/06/11/net-runtime-embedding/ Thanks. On 3 ���, 13:19, "Martin Zugec" <martin.zu…@gmail.com> wrote: > Have [...]



Write a Comment

You must be logged in to post a comment. Log in