65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (22投票s)

2007年12月6日

CPOL

3分钟阅读

viewsIcon

147136

downloadIcon

2401

.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_CorRuntimeHostIID_ICorRuntimeHostmscorlib.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 版本 - 添加了运行时版本支持
© . All rights reserved.