Click here to Skip to main content
11,928,078 members (38,856 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as


100 bookmarked

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

, 19 Feb 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
C# and C++ source code for .NET application packer tool


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:

class 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:

#define NET11 L"v1.1.4322";


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:

CComPtr<ICorRuntimeHost> pHost;
HRESULT hr = CorBindToRuntimeEx(runtime,
    (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:

#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:

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:

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:

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:

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:

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};
  &noArgs, NULL, NULL, NULL);
if (FAILED(hr))
  return hr;

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

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.


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


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


About the Author

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.

You may also be interested in...

Comments and Discussions

GeneralRe: .NET 3.0 / 3.5 Pin
SteveLi-Cellbi7-Jan-08 12:50
memberSteveLi-Cellbi7-Jan-08 12:50 
GeneralRe: .NET 3.0 / 3.5 Pin
DomiOh7-Jan-08 20:23
memberDomiOh7-Jan-08 20:23 
GeneralRe: .NET 3.0 / 3.5 Pin
SteveLi-Cellbi19-Feb-08 13:27
memberSteveLi-Cellbi19-Feb-08 13:27 
GeneralRe: .NET 3.0 / 3.5 Pin
Ri Qen-Sin22-Feb-09 15:21
memberRi Qen-Sin22-Feb-09 15:21 
Generalobservation Pin
merovingian1813-Dec-07 5:06
membermerovingian1813-Dec-07 5:06 
GeneralRe: observation Pin
SteveLi-Cellbi16-Dec-07 8:03
memberSteveLi-Cellbi16-Dec-07 8:03 
QuestionProblems? Pin
jung-kreidler12-Dec-07 23:40
memberjung-kreidler12-Dec-07 23:40 
AnswerRe: Problems? Pin
Member 435277813-Dec-07 0:39
memberMember 435277813-Dec-07 0:39 
same problem here ....

log says:

Number of streams read: 1
Packer class used: Cellbi.AppPacker.Api.DefaultPacker
Unpacking streams.
All streams are unpacked.
Loading assemblies.
AssemblyResolve, name: mscorlib.resources, Version=1.0.5000.0, Culture=el-GR, PublicKeyToken=b77a5c561934e089
mscorlib.resources, Version=1.0.5000.0, Culture=el-GR, PublicKeyToken=b77a5c561934e089 was not found.
AssemblyResolve, name: mscorlib.resources, Version=1.0.5000.0, Culture=el, PublicKeyToken=b77a5c561934e089
mscorlib.resources, Version=1.0.5000.0, Culture=el, PublicKeyToken=b77a5c561934e089 was not found.
Version 2.0 is not a compatible version.

at System.Reflection.Assembly.nLoadImage(Byte[] rawAssembly, Byte[] rawSymbolStore, Evidence evidence, StackCrawlMark& stackMark)
at System.Reflection.Assembly.Load(Byte[] rawAssembly)
at Cellbi.AppPacker.Api.NetLauncher.LoadAssembly(PackedDataStream data)
All assemblies are loaded.
<<< Loading data streams
Object reference not set to an instance of an object.
at Cellbi.AppPacker.Api.NetLauncher.LaunchApplication(String path, String cmdLine)
at Cellbi.AppPacker.Api.NetLauncher.Launch(String path, String cmdLine)
<<< Launch

GeneralRe: Problems? Pin
SteveLi-Cellbi16-Dec-07 8:07
memberSteveLi-Cellbi16-Dec-07 8:07 
AnswerRe: Problems? Pin
SteveLi-Cellbi16-Dec-07 8:06
memberSteveLi-Cellbi16-Dec-07 8:06 
AnswerRe: Problems? Pin
SteveLi-Cellbi19-Feb-08 13:24
memberSteveLi-Cellbi19-Feb-08 13:24 
GeneralRe: Problems? Pin
jung-kreidler20-Feb-08 5:44
memberjung-kreidler20-Feb-08 5:44 
GeneralRe: Problems? Pin
Tuakisan9-Mar-08 9:32
memberTuakisan9-Mar-08 9:32 
GeneralRe: Problems? Pin
SteveLi-Cellbi12-Mar-08 8:48
memberSteveLi-Cellbi12-Mar-08 8:48 
GeneralVery neat trick BUT link to Cellbi.AppPacker is Not working Pin
AnandChavali6-Dec-07 17:21
memberAnandChavali6-Dec-07 17:21 
GeneralRe: Very neat trick BUT link to Cellbi.AppPacker is Not working Pin
SteveLi-Cellbi6-Dec-07 18:42
memberSteveLi-Cellbi6-Dec-07 18:42 
GeneralVery cool Pin
Patrick Sears6-Dec-07 10:23
memberPatrick Sears6-Dec-07 10:23 
GeneralGood trick but.. Pin
Nathan Evans6-Dec-07 10:07
memberNathan Evans6-Dec-07 10:07 
GeneralRe: Good trick but.. Pin
The JZ7-Dec-07 6:43
memberThe JZ7-Dec-07 6:43 
GeneralRe: Good trick but.. Pin
pikipoki8-Dec-07 7:42
memberpikipoki8-Dec-07 7:42 
GeneralRe: Good trick but.. Pin
stixoffire12-Dec-07 21:19
memberstixoffire12-Dec-07 21:19 
GeneralNeat Trick Pin
Ri Qen-Sin6-Dec-07 8:32
memberRi Qen-Sin6-Dec-07 8:32 
GeneralRe: Neat Trick Pin
SteveLi-Cellbi6-Dec-07 8:41
memberSteveLi-Cellbi6-Dec-07 8:41 
GeneralRe: Neat Trick Pin
ti-oh21-Dec-07 18:17
memberti-oh21-Dec-07 18:17 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.151126.1 | Last Updated 19 Feb 2008
Article Copyright 2007 by SteveLi-Cellbi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid