一种将 .NET 代码打包到单个可执行文件中的简单方法






4.69/5 (22投票s)
.NET 应用程序打包工具的 C# 和 C++ 源代码
引言
为 .NET 构建的应用程序通常由大量程序集组成,并且 .NET 代码可以使用反汇编程序轻松地反编译,这已不是什么秘密。将所有应用程序程序集打包到单个本机可执行文件中可能很有用,从而减小应用程序大小并提供一定程度的代码保护。在本文中,我们将研究创建 .NET 应用程序打包工具的一种简单方法。该工具的完整源代码和文档可在 Cellbi 网站免费下载。
本机可执行文件模板
首先,我们需要创建一个本机可执行文件,我们将使用它作为目标 .NET 应用程序的容器。我们将为此目的使用 C++ 语言。
由于我们的模板是本机应用程序,因此我们需要从我们的代码手动启动 .NET 运行时,然后创建一个默认的 AppDomain
,然后将我们的程序集加载到那里。只有这样,才能将执行转移到托管代码。让我们定义 NativeLauncher
类来处理这个问题。
这是 NativeLauncher
的定义
class NativeLauncher
{
public:
NativeLauncher();
int Launch(LPWSTR runtime, LPTSTR cmdLine);
};
NativeLauncher
类仅包含一个方法:Launch
,它有两个参数 runtime
- 指定目标 .NET 运行时版本,以及 cmdLine
- 传递的命令行 string
。
下面是我们的可执行文件模板的 main
方法的代码
#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;
}
我们只是创建一个 NativeLauncher
实例并调用它的 Launch
方法,传递 .NET 运行时版本和命令行 string
。
启动 .NET 运行时
要从 NativeLauncher
启动 .NET 运行时,我们将使用 mscorlib.tlb 库中的 CorBindToRuntimeEx
函数。此函数将绑定到指定的 .NET 运行时,并返回一个指向 ICorRuntimeHost
接口的指针。ICorRuntimeHost
接口公开了 Start
方法,我们将使用它来启动 .NET 运行时。
下面是示例代码
CComPtr<ICorRuntimeHost> pHost;
HRESULT hr = CorBindToRuntimeEx(runtime,
NULL,
NULL,
CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(void **)&pHost);
if (FAILED(hr))
return hr;
请注意,CLSID_CorRuntimeHost
、IID_ICorRuntimeHost
在 mscorlib.tlb
库中定义。
为了使用 CorBindToRuntimeEx
函数,我们需要导入 mscorlib.tlb
库。下面是如何做到这一点
#import <mscorlib.tlb> raw_interfaces_only
using namespace mscorlib;
加载 .NET 程序集
一旦我们启动了 .NET 运行时主机,就可以将任何 .NET 程序集加载到默认的 AddDomain
中,然后执行我们的托管代码。ICorRuntimeHost
接口具有 GetDefaultDomain
方法,该方法可用于获取指向默认 AppDomain
的指针。
下面是说明此问题的代码
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;
现在我们可以使用 _AppDomain
公开的任何 Load
方法将 .NET 程序集加载到默认 AppDomain
中。
这是代码
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;
在上面的代码中,我们从字节数组加载我们的程序集。该数组存储在 packerApiLib
变量中。
执行托管代码
下一步是创建一个 .NET 类并获取其指针。这很容易通过 _Assembly
接口公开的 CreateInstance
方法来完成。我们只需传递要创建的类的全名,以获得指向结果值的指针
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;
我们使用 CComVariant
来存储指向新创建的托管实例的指针。
好的,现在是时候调用在创建的实例上定义的方法了,但我们需要首先找到正确的方法
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;
现在我们调用找到的方法。在 IDispatch
接口上定义的 Invoke
方法可以做到这一点
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;
方法执行后,我们需要停止 .NET 运行时
hr = pHost->Stop();
if (FAILED(hr))
return hr;
就这样,我希望这篇文章对您有所帮助。如果有任何问题,请告诉我。
在本文中,我们没有介绍用于将 .NET 程序集打包到本机可执行文件模板中的托管代码。完整源代码可在 Cellbi.AppPacker 上获得。
历史
- 2007 年 12 月 5日:初始版本
- 2008 年 2 月 19日:1.0.0.20 版本 - 添加了运行时版本支持