Click here to Skip to main content
16,021,687 members
Articles / Programming Languages / C#
Article

A Simple Way to Pack your .NET Code into a Single Executable

Rate me:
Please Sign up or sign in to vote.
4.69/5 (23 votes)
19 Feb 2008CPOL3 min read 142.9K   2.4K   104   52
C# and C++ source code for .NET application packer tool

Introduction

Applications built for .NET tend to be composed of a large number of assemblies and it's not a secret that .NET code can be easily decompiled using dissasemblers. It might be useful to pack all application assemblies into a single native executable, thus reducing application size and providing some degree of code protection. In this article, we'll look into a simple way of creating a .NET application packer tool. The complete source code and documentation of the tool is free to download at the Cellbi Web site.

Native Executable Template

First of all, we need to create a native executable, which we will use as a container for a target .NET application. We'll use C++ language for this purpose.

Since our template is a native application, we'll need to start .NET runtime manually from our code, then create a default AppDomain and then load our assemblies there. Only after that, it's OK to transfer execution to the managed code. Let's define the NativeLauncher class to handle this.

Here is the NativeLauncher definition:

C++
class NativeLauncher
{
public:
  NativeLauncher();
  int Launch(LPWSTR runtime, LPTSTR cmdLine);
};

NativeLauncher class contains only one method: Launch, it has two parameters runtime - to specify target .NET runtime version, and cmdLine - command line string passed.

Below is the code for the main method of our executable template:

C++
#define NET11 L"v1.1.4322";

HINSTANCE hInst;

int APIENTRY _tWinMain(HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
  hInst = hInstance;

  LPWSTR runtime = NET11;

  NativeLauncher* launcher = new NativeLauncher();
  int result = launcher->Launch(runtime, lpCmdLine);
  delete launcher;
  launcher = NULL;
  return result;
}

We just create an NativeLauncher instance and call its Launch method, passing .NET runtime version and command line string.

Starting .NET Runtime

To start .NET runtime from NativeLauncher, we will use the CorBindToRuntimeEx function available from the mscorlib.tlb library. This function will bind to the specified .NET runtime and return a pointer to the ICorRuntimeHost interface. ICorRuntimeHost interface exposes the Start method, which we will use to start the .NET runtime.

Below is the sample code:

C++
CComPtr<ICorRuntimeHost> pHost;
HRESULT hr = CorBindToRuntimeEx(runtime,
    NULL,
    NULL,
    CLSID_CorRuntimeHost,
    IID_ICorRuntimeHost,
    (void **)&pHost);
if (FAILED(hr))
  return hr;

Note that CLSID_CorRuntimeHost, IID_ICorRuntimeHost are defined in the mscorlib.tlb library.

In order to use the CorBindToRuntimeEx function, we need to import the mscorlib.tlb library. Here is how to do this:

C++
#import <mscorlib.tlb> raw_interfaces_only

using namespace mscorlib;

Loading .NET Assemblies

Once we started the .NET runtime host, it is fine to load any .NET assembly into the default AddDomain and then execute our managed code. ICorRuntimeHost interface has the GetDefaultDomain method which can be used to obtain a pointer to the default AppDomain.

Below is the code illustrating this:

C++
CComPtr<IUnknown> pUnk;
hr = pHost->GetDefaultDomain(&pUnk);
if (FAILED(hr))
  return hr;

CComPtr<_AppDomain> appDomain;
hr = pUnk->QueryInterface(&appDomain.p);
if (FAILED(hr))
  return hr;

Now we can load the .NET assembly into the default AppDomain using any of the Load methods exposed by _AppDomain.

Here is the code:

C++
CComSafeArray<unsigned char> *rawAssembly = new CComSafeArray<unsigned char>();
rawAssembly->Add(packerApiLibLength, packerApiLib);

CComPtr<_Assembly> assembly;
hr = appDomain->Load_3(*rawAssembly, &assembly);
if (FAILED(hr))
  return hr;

At the code above, we load our assembly from an array or bytes. The array is stored in the packerApiLib variable.

Executing Managed Code

The next step is to create a .NET class and obtain its pointer. This is easy to do via the CreateInstance method exposed by the _Assembly interface. We just pass the full name of the class to create a pointer to result value:

C++
CComVariant launcher;
hr = assembly->CreateInstance(_bstr_t("Cellbi.AppPacker.Api.NetLauncher"), &launcher);
if (FAILED(hr))
  return hr;

if (launcher.vt != VT_DISPATCH)
  return E_FAIL;

We use CComVariant to store the pointer to newly created managed instance.

Ok, it's now time to call methods defined on the created instance, but we need to find the right method first:

C++
CComPtr<IDispatch> disp = launcher.pdispVal;
DISPID dispid;
OLECHAR FAR* methodName = L"Launch";
hr = disp->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(hr))
  return hr;

And now we call the method found. The Invoke method defined on the IDispatch interface does this:

C++
TCHAR szPath[MAX_PATH];
if (!GetModuleFileName(NULL, szPath, MAX_PATH))
  return E_FAIL;

CComVariant *path = new CComVariant(szPath);
CComVariant *cmdLineArg = new CComVariant(cmdLine);
CComVariant FAR args[] = {*cmdLineArg, *path};
DISPPARAMS noArgs = {args, NULL, 2, 0};
hr = disp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD,
  &noArgs, NULL, NULL, NULL);
if (FAILED(hr))
  return hr;

After the method is executed, we need to stop the .NET runtime:

C++
hr = pHost->Stop();
if (FAILED(hr))
  return hr;

That's all, I hope this article will be useful. Please let me know if there are any problems.

In this article, we didn't cover managed code used to pack .NET assemblies into the native executable template. The complete source code can be obtained at Cellbi.AppPacker.

History

  • December 5th, 2007 : Initial release
  • February 19th, 2008 : 1.0.0.20 build - Runtime version support added

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I'm excited about computers and programming, since my school days. I have master's degree in software development and at the moment I'm a software developer at Cellbi Software.

Comments and Discussions

 
QuestionPacker detected as virus/malware Pin
roland_h1-Apr-17 17:16
roland_h1-Apr-17 17:16 
QuestionParameter count mismatch. Pin
Member 439449320-Feb-16 22:47
Member 439449320-Feb-16 22:47 
Questionhelp Pin
arby774-Oct-13 8:05
arby774-Oct-13 8:05 
GeneralThanks , and one question Pin
_Armen19-Aug-09 22:16
_Armen19-Aug-09 22:16 
GeneralRe: Thanks , and one question Pin
_Armen20-Aug-09 1:45
_Armen20-Aug-09 1:45 
General.NET 3.5 not working Pin
battlemodetwo11-May-09 18:59
battlemodetwo11-May-09 18:59 
GeneralRe: .NET 3.5 not working Pin
cdengler18-May-09 12:16
cdengler18-May-09 12:16 
GeneralSmall Problem Pin
mk2028-Mar-08 11:33
mk2028-Mar-08 11:33 
GeneralRe: Small Problem Pin
SteveLi-Cellbi1-Apr-08 10:23
SteveLi-Cellbi1-Apr-08 10:23 
GeneralGood work, but... Pin
MFaithful12-Mar-08 6:20
MFaithful12-Mar-08 6:20 
GeneralRe: Good work, but... Pin
SteveLi-Cellbi12-Mar-08 7:51
SteveLi-Cellbi12-Mar-08 7:51 
GeneralRe: Good work, but... Pin
MFaithful12-Mar-08 22:44
MFaithful12-Mar-08 22:44 
GeneralRe: Good work, but... Pin
JohnDev6-Apr-09 7:50
JohnDev6-Apr-09 7:50 
GeneralCustomizing the Microsoft dot NET Framework CLR Pin
Bill Seddon25-Feb-08 13:37
Bill Seddon25-Feb-08 13:37 
GeneralGreat Idea Pin
merlin98120-Feb-08 4:29
professionalmerlin98120-Feb-08 4:29 
This is a great idea.

However, I think you should include documentation for the 99% of us who use 2.0 and 3.5. Also, the C++ source should be updated for 2.0/3.5.

Overall, this is a great idea, and with a little tweaking we should all be able to use it.



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rhabot - World of Warcraft Bot
Uber RPGE - Free Private World of Warcraft Server
Make long URLs short with NeatURL.net
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

GeneralRe: Great Idea Pin
SteveLi-Cellbi20-Feb-08 9:46
SteveLi-Cellbi20-Feb-08 9:46 
GeneralThe application Icon disappear Pin
ksboy19-Feb-08 16:49
ksboy19-Feb-08 16:49 
GeneralRe: The application Icon disappear Pin
SteveLi-Cellbi20-Feb-08 9:36
SteveLi-Cellbi20-Feb-08 9:36 
GeneralNot working Pin
tonyt18-Dec-07 9:57
tonyt18-Dec-07 9:57 
GeneralRe: Not working Pin
SteveLi-Cellbi19-Dec-07 6:19
SteveLi-Cellbi19-Dec-07 6:19 
GeneralTarget native DLL rather than EXE Pin
tonyt17-Dec-07 23:13
tonyt17-Dec-07 23:13 
GeneralRe: Target native DLL rather than EXE Pin
SteveLi-Cellbi19-Dec-07 6:21
SteveLi-Cellbi19-Dec-07 6:21 
GeneralAppPacker project does not build on VC2005 Pin
tonyt17-Dec-07 23:05
tonyt17-Dec-07 23:05 
QuestionIs this just for Desktop Apps? Pin
Dewey16-Dec-07 11:29
Dewey16-Dec-07 11:29 
AnswerRe: Is this just for Desktop Apps? Pin
SteveLi-Cellbi17-Dec-07 6:03
SteveLi-Cellbi17-Dec-07 6:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.