一种暂时关闭Windows弹窗函数程序的设计实现
张晶瑜 陈僴璀
摘 要 本文介绍了一种在程序运行过程当中,暂时关闭Windows 系统中MessageBox()弹窗函数的方法。该方法能够处理程序运行过程中Windows弹窗函数弹出过多导致的程序运行效率低下问题,进而实现程序运行过程中的无人值守。
关键词 MessageBox 弹窗函数 程序
中图分类号:TP311 文献标识码:A
0引言
MessageBox()消息框函数是指user32.dll中MessageBox() API提供的弹出消息提示框,其作用主要为显示文本消息。某些程度上,MessageBox()函数还明确了程序运行的步骤,促进了使用者对程序本身的了解,也使得使用者与程序开发者之间的交流变得更加顺畅。然而,MessageBox()函数的使用,有时也会给程序的运行效率带来一些影响。
1研究背景和意义
计算机用户经常使用的应用软件(如办公软件、行业专用软件)通常是软件开发商针对某一类用户的普遍需求所设计。如遇用户有一些特殊需求,应用软件不能很好满足时,用户自己通常会在原有软件基础上进行二次开发。为了便于用户进行二次开发,部分应用会软件设计一些API接口,供二次开发用户调用。在二次开发过程中,往往会遇到MessageBox API所带来的一些负面影响。
举例如下,假设有一个运行于Windows系统中的应用软件APP 1.0,开发商为其设计了一个API,可供用户使用。API所属模块DLL文件为“C:\APP 1.0\abcapi.dll”,API对应函数名称为Function,该API的主要功能是对指定文件进行特定操作(如读取文件内容、修改文件内容)。该API声明如下:
void Function(lpsz path);
其中path代表需要处理文件的完整路径。
而用户在进行二次开发时,所写程序(用户软件)需要调用Function函数对一系列文件进行逐个处理。当用户软件使用者点击了“开始工作”按钮之后,在处理到第i个文件时,Function函数遇到了异常,会调用MessageBox()弹出一个模态对话框,对此异常情况进行提示。此时,使用者必须点击弹出对话框的“确定”按钮之后,整个程序才能继续工作,程序才能继续处理第i+1个文件。当异常情况很多时,程序运行效率就会很低,而且这样无法实现程序的无人值守运行。Function函数的具体代码是由APP 1.0的开发商提供,无论从技术层面还是法律层面上说,要想通过修改其编译后的程序代码达到消除弹窗的目的都不是明智之举。
因此,在用户软件开发过程中,需要找到一种方法,实现在程序运行过程中,暂时关闭MessageBox()的功能。
本文针对以上问题,提出了一种暂时关闭MessageBox()弹出对话框的方法,本文所述代码示例均采用C++ .net所寫。
2研究方法
通过需求分析可知,进行二次开发时,可以在自己开发程序的进程中找到MessageBox()函数的代码,并对其进行修改。这样的优点在于只会影响与MessageBox()函数相关的这1个进程,不会影响其他程序,而且需要时也可以恢复。
首先要做的是,找到MessageBox()的地址。在user32.dll中,大部分API都有2个版本,对应Ansi和Unicode字符集。MessageBox()也是如此,这2个版本分别是MessageBoxA和MessageBoxW。用kernel32.dll中的GetProcAddress API可以获取到MessageBox()的地址。它的声明如下:
[DllImport("kernel32.dll", CharSet = CharSet::Ansi)]
static IntPtr GetProcAddress(IntPtr hModule, String^ procName);
其中,hModule是目标dll文件的指针,这里就是user32.dll的指针。
在我们获取地址之后,需要先读取地址处的代码信息,将其保存起来,以便日后恢复。如果不打算恢复也可直接省略这一步。之后,把修改过的指令写入之前获取的地址当中,这样,就实现了MessageBox()的暂时关闭功能。代码的读、写分别采用kernel32.dll中的ReadProcessMemory和WriteProcessMemory API。它们声明如下:
[DllImport("kernel32.dll", CharSet = CharSet::Auto)]
static bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, array
[DllImport("kernel32.dll", CharSet = CharSet::Auto)]
static bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, array
读取MessageBox()指令的具体代码如下:
IntPtr thisProc; //本进程的Handle
IntPtr hModule; //user32.dll的handle
IntPtr baseAddrA, baseAddrW; //MessageBox()方法的handle
int readOrWrite; //读写字节数
thisProc = Diagnostics::Process::GetCurrentProcess()->Handle;
ProcessModuleCollection^ modules = Process::GetCurrentProcess()->Modules;
for each(ProcessModule^ m in modules){
if(m->ModuleName->ToLower() == "user32.dll"){
hModule = m->BaseAddress;
}
}
baseAddrA = GetProcAddress(hModule, "MessageBoxA" );
baseAddrW = GetProcAddress(hModule, "MessageBoxW" );
ReadProcessMemory(thisProc, baseAddrA, CodeMBA, 32, readOrWrite);
ReadProcessMemory(thisProc, baseAddrW, CodeMBW, 32, readOrWrite);
上述代码中CodeMBA与CodeMBW是事先声明过的全局Byte类型数组。
读取到MessageBox()代码后,继续研究如何更改代码。下面以在Windows 7中读取到的MessageBoxA()代码为例,将其转换为汇编语言后,代码如下:
8B FF - mov edi,edi
55 - push ebp
8B EC - mov ebp,esp
6A 00 - push 00 { 0 }
FF 75 14 - push [ebp+14]
FF 75 10 - push [ebp+10]
FF 75 0C - push [ebp+0C]
FF 75 08 - push [ebp+08]
E8 A0FFFFFF - call USER32.MessageBoxExA
5D - pop ebp
C2 1000 - ret 0010 { 16 }
90 - nop
90 - nop
90 - nop
90 - nop
90 - nop
Win32 API采用的调用约定为stdcall。此种调用约定中,参数按照右至左的顺序,返回值存放在EAX寄存器中,函数返回时,由被调用函数负责清理堆栈。
通过对MessageBoxA()汇编代码的分析,我们不难发现,其最后一条有效指令为ret 0010{16}。这是一条返回指令,完成了堆栈清理的工作,其等效于以下2条指令:
POP EIP
ADD ESP,0X10
運行该指令除了会改变EIP寄存器外,还会使得 ESP = ESP+0X10,也就是堆栈指针向栈顶移动16个字节。
我们可以把这一条指令直接放到MessagBoxA代码的最开始,这样,程序运行到MessagBoxA方法时,会直接返回调用它的上一级代码,于是,模态对话框就不会再弹出了。
将更改后指令替换掉原有MessageBox()指令的具体代码如下:
array
WriteProcessMemory(thisProc, baseAddrA, codeNull, 3, readOrWrite);
WriteProcessMemory(thisProc, baseAddrW, codeNull, 3, readOrWrite);
其中{0xC2, 0x10, 0x00}这3个字节的来自于MessageBoxA()的最后一条指令:
C2 1000 - ret 0010 { 16 }
3结论
通过以上方法实现了在程序运行过程当中,暂时关闭Windows 系统中MessageBox()弹窗函数的功能。从某种程度上解决了程序运行效率低下的问题,进而实现了程序运行过程中的无人值守。
参考文献
[1] 李海雁.一个更为灵活的MessageBox()函数[J].电脑编程技巧与维护,1996(01):34-35+38.