AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

用自删除dll实现应用程序的安装/卸载代码

51自学网 2015-08-30 http://www.wanshiok.com

*摘要
  当我在编写“What To Do”程序(这是作者编写的一个应用程序,小巧玲珑,很实用——译者注)时,就想写一个自己的安装和卸载代码,主要目的是想随心所欲地控制整个安装/卸载过程中用户所看到的画面。本文我们就来讨论如何利用自删除的动态链接库(DLL)实现自删除的可执行程序,从而实现程序的安装/卸载。相信很多朋友在编写 Windows 程序时都想这么做,本文还将展示一些非常有用的相关技术,一定让你大开眼界......

*实现自删除卸载程序的难点
  编写卸载程序最具挑战性的部分是如何让卸载程序在删除完目标程序文件和相关目录之后自己删除自己。此外,卸载程序还必须能在所有 Windows 操作系统平台(Windows 9x、Windows NT、Windows 2000、Windows XP.....)上运行,不需要用户下载任何附加组件。我在网上搜索了一番,找到一些相关的资料介绍如何自删除可执行程序文件,但是大多数所建议的解决方案都存在一个问题,那就是只能在某个版本的 Windows 上工作。有些方法通过修改线程属性来实现,这样做一般都会导致定时问题。还有一些方法运行时出现严重错误,根本就不能用。我琢磨着寻求一种更好的解决方法来实现可执行程序的自删除功能:用自删除的 DLL 实现自删除的可执行程序,从而突破上述诸方法的局限。

*实用程序 rundll32.exe 介绍
  从所周知,DLL的代码通常需要先加载到内存之后才能执行,那么如何执行某个DLL导出的代码而不用创建加载和调用该 DLL 的 EXE 文件呢?方法如下:从 Windows 95 开始的每个 Windows 操作系统版本都附带一个系统实用程序:rundll32.exe。利用它可以象下面这样执行某些 DLL(但不是所有)输出的任何函数:    rundll32.exe DllName,ExportedfnName args   
ExportedfnName 是DLL输出的函数名。在编写供 rundll32 使用的 DLL时,可以象下面这样来声明输出函数:extern "C" __declspec(dllexport) void CALLBACK FunctionName (
HWND hwnd,
HINSTANCE hInstance,
LPTSTR lpCmdLine,
int nCmdShow
)
{ ... }  
rundll32.exe 根据函数参数列表对函数进行调用,但根据经验,实际上用得上的参数值只有一个,那就是 lpCmdLine,该参数接收运行 rundll32.exe 时传入的参数值;__declspec(dllexport)的目的是输出函数;extern "C" 使输出的函数名有修饰符,如:_FunctionName@16 (函数名中被强制包含函数参数的大小,详细信息请参见 MSDN 中有关DLL输出函数调用规范说明)。rundll32.exe 加载指定的 DLL 并调用通过 args 参数传入的 lpCmdLine 的值指定的输出函数。有关 rundll32.exe 的正式文档参见 MSDN 库相关资料(Q164787): http://support.microsoft.com/default.aspx?scid=kb;en-us;164787

*实现能自删除的 DLL
下面是实现自删除DLL的示范代码:

#include <windows.h>
HMODULE g_hmodDLL;

extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
g_hmodDLL = hinstDLL;
return TRUE;
}

extern "C" __declspec(dllexport) void CALLBACK MagicDel(HWND,
HINSTANCE,
LPTSTR lpCmdLine,
int)
{
// 延时2秒
Sleep(2000);
// 删除创建该进程的可执行文件
DeleteFile(lpCmdLine);

// 删除DLL自己
char filenameDLL[MAX_PATH];
GetModuleFileName(g_hmodDLL, filenameDLL, sizeof(filenameDLL));

__asm
{
lea eax, filenameDLL
push 0
push 0
push eax
push ExitProcess
push g_hmodDLL
push DeleteFile
push FreeLibrary
ret
}
}   
  上面这段代码首先删除某个文件,然后自删除。DllMain 是DLL的入口函数,当首次加载动态链接库时该函数被调用,此时将模块句柄赋值给全局变量 g_hmodDLL,以便梢后使用它来获取 DLL 本身的文件名。在 MagicDel 函数中,lpCmdLine 是DLL要删除的可执行文件的名称(如:卸载程序的文件名)。要删除它很容易——用 Sleep 做一个延时,以便可执行程序的进程有时间退出并调用 DeleteFile。为了掌握 MagicDel 的实现细节,你可以将可执行程序的进程句柄传给MagicDel并在调用 DeleteFile 之前做一个等待,看看会发生什么?
  要让 DLL 进行自删除需要一点诀窍。rundll32 调用 LoadModule 将 DLL 加载到它的地址空间。如果 DLL 函数可以返回的话,rundll32 将会退出,从而导致 DLL 被释放(不是被删除)。为了解决这个问题,我们可以执行下面的代码:    FreeLibrary(DLL module handle);
   DeleteFile(DLL filename);
   ExitProcess(0);   
  MagicDel 函数是不能按这样的顺序进行直接调用的,因为 FreeLibary 会使代码页无效。为此, MagicDel 采用将等效的汇编指令压入堆栈,然后执行它们,后跟一个 ret 指令,最后调用 ExitProccess 以防止进程继续往下执行。我参考 Gary Nebbit 在 Windows 开发杂志(WDJ)“Tech Tips”栏目发表的文章编写了一个汇编代码块。如果你用 Visual Studio 以默认选项生成DLL,最终的二进制文件大约为 40K。由于我们打算将 DLL 作为可执行程序的资源,它的体积越小越好,为此,我们必须对它进行瘦身处理。思路是将无用的 C 运行时代码从DLL中删除掉,具体方法如下:
本文例子使用 Visual Studio.NET 2003 中文版编译生成 DLL,先设置项目的编译/链接选项:
项目(P)| [项目名称] 属性(P)... | 链接器 | 输入 | 忽略所有默认库:是(/NODEFAULTLIB),此设置将 /NODEFAULTLIB 选项传给链接器以便过滤掉运行时代码。

由于 DLL 入口点(Entry Point)通常是由运行时库提供(默认为 DllMain),所以完成上述第一步设置之后,还必须显式地将 DLL入口点设置为 DllMain:
项目(P)| [项目名称] 属性(P)... | 链接器 | 高级 | 入口点:DllMain。
如果此时编译生成 DLL,编译器会报如下两个 无法解析的外部符号( unresolved externals ) 错误: error LNK2019: 无法解析的外部符号 ___security_cookie ,该符号在函数 _MagicDel@16 中被引用
error LNK2019: 无法解析的外部符号 @__security_check_cookie@4 ,该符号在函数 _MagicDel@16 中被引用
解决方法是进行下一步设置。


项目(P)| [项目名称] 属性(P)... | C/C++ | 代码生成 | 缓冲区安全检查:否,
该设置不会将 /GS 标志传给编译器,从而摆脱 unresolved externals 错误。
好了,现在编译生成 DLL,最终的 DLL 大小为 3K,实际的文件大小只有 2.5K。

*实现能自删除的可执行程序
  这里所用的主要思路是将一个能自删除的 DLL 作为资源保存在拟实现自删除的可执行程序中,然后在需要时重新创建它,同时,启动一个 rundll32.exe 进程实现删除行为。
  下面是用于将DLL存储为资源的头文件和资源文件。资源类型值只要大于 256 都可以,这是为用户定义类型预留的。此外还有一种可选方法是将 DLL 二进制文件以字节数组的形式直接存储在源中:
在资源中包含一个文件

<

 

 

 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
上一篇:MFC应用程序框架入门  下一篇:数据库综合开发实践