張晶瑜 陳僴璀
摘 要 本文介紹了一種在程序運(yùn)行過程當(dāng)中,暫時(shí)關(guān)閉Windows 系統(tǒng)中MessageBox()彈窗函數(shù)的方法。該方法能夠處理程序運(yùn)行過程中Windows彈窗函數(shù)彈出過多導(dǎo)致的程序運(yùn)行效率低下問題,進(jìn)而實(shí)現(xiàn)程序運(yùn)行過程中的無人值守。
關(guān)鍵詞 MessageBox 彈窗函數(shù) 程序
中圖分類號:TP311 文獻(xiàn)標(biāo)識(shí)碼:A
0引言
MessageBox()消息框函數(shù)是指user32.dll中MessageBox() API提供的彈出消息提示框,其作用主要為顯示文本消息。某些程度上,MessageBox()函數(shù)還明確了程序運(yùn)行的步驟,促進(jìn)了使用者對程序本身的了解,也使得使用者與程序開發(fā)者之間的交流變得更加順暢。然而,MessageBox()函數(shù)的使用,有時(shí)也會(huì)給程序的運(yùn)行效率帶來一些影響。
1研究背景和意義
計(jì)算機(jī)用戶經(jīng)常使用的應(yīng)用軟件(如辦公軟件、行業(yè)專用軟件)通常是軟件開發(fā)商針對某一類用戶的普遍需求所設(shè)計(jì)。如遇用戶有一些特殊需求,應(yīng)用軟件不能很好滿足時(shí),用戶自己通常會(huì)在原有軟件基礎(chǔ)上進(jìn)行二次開發(fā)。為了便于用戶進(jìn)行二次開發(fā),部分應(yīng)用會(huì)軟件設(shè)計(jì)一些API接口,供二次開發(fā)用戶調(diào)用。在二次開發(fā)過程中,往往會(huì)遇到MessageBox API所帶來的一些負(fù)面影響。
舉例如下,假設(shè)有一個(gè)運(yùn)行于Windows系統(tǒng)中的應(yīng)用軟件APP 1.0,開發(fā)商為其設(shè)計(jì)了一個(gè)API,可供用戶使用。API所屬模塊DLL文件為“C:\APP 1.0\abcapi.dll”,API對應(yīng)函數(shù)名稱為Function,該API的主要功能是對指定文件進(jìn)行特定操作(如讀取文件內(nèi)容、修改文件內(nèi)容)。該API聲明如下:
void Function(lpsz path);
其中path代表需要處理文件的完整路徑。
而用戶在進(jìn)行二次開發(fā)時(shí),所寫程序(用戶軟件)需要調(diào)用Function函數(shù)對一系列文件進(jìn)行逐個(gè)處理。當(dāng)用戶軟件使用者點(diǎn)擊了“開始工作”按鈕之后,在處理到第i個(gè)文件時(shí),F(xiàn)unction函數(shù)遇到了異常,會(huì)調(diào)用MessageBox()彈出一個(gè)模態(tài)對話框,對此異常情況進(jìn)行提示。此時(shí),使用者必須點(diǎn)擊彈出對話框的“確定”按鈕之后,整個(gè)程序才能繼續(xù)工作,程序才能繼續(xù)處理第i+1個(gè)文件。當(dāng)異常情況很多時(shí),程序運(yùn)行效率就會(huì)很低,而且這樣無法實(shí)現(xiàn)程序的無人值守運(yùn)行。Function函數(shù)的具體代碼是由APP 1.0的開發(fā)商提供,無論從技術(shù)層面還是法律層面上說,要想通過修改其編譯后的程序代碼達(dá)到消除彈窗的目的都不是明智之舉。
因此,在用戶軟件開發(fā)過程中,需要找到一種方法,實(shí)現(xiàn)在程序運(yùn)行過程中,暫時(shí)關(guān)閉MessageBox()的功能。
本文針對以上問題,提出了一種暫時(shí)關(guān)閉MessageBox()彈出對話框的方法,本文所述代碼示例均采用C++ .net所寫。
2研究方法
通過需求分析可知,進(jìn)行二次開發(fā)時(shí),可以在自己開發(fā)程序的進(jìn)程中找到MessageBox()函數(shù)的代碼,并對其進(jìn)行修改。這樣的優(yōu)點(diǎn)在于只會(huì)影響與MessageBox()函數(shù)相關(guān)的這1個(gè)進(jìn)程,不會(huì)影響其他程序,而且需要時(shí)也可以恢復(fù)。
首先要做的是,找到MessageBox()的地址。在user32.dll中,大部分API都有2個(gè)版本,對應(yīng)Ansi和Unicode字符集。MessageBox()也是如此,這2個(gè)版本分別是MessageBoxA和MessageBoxW。用kernel32.dll中的GetProcAddress API可以獲取到MessageBox()的地址。它的聲明如下:
[DllImport("kernel32.dll", CharSet = CharSet::Ansi)]
static IntPtr GetProcAddress(IntPtr hModule, String^ procName);
其中,hModule是目標(biāo)dll文件的指針,這里就是user32.dll的指針。
在我們獲取地址之后,需要先讀取地址處的代碼信息,將其保存起來,以便日后恢復(fù)。如果不打算恢復(fù)也可直接省略這一步。之后,把修改過的指令寫入之前獲取的地址當(dāng)中,這樣,就實(shí)現(xiàn)了MessageBox()的暫時(shí)關(guān)閉功能。代碼的讀、寫分別采用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; //本進(jìn)程的Handle
IntPtr hModule; //user32.dll的handle
IntPtr baseAddrA, baseAddrW; //MessageBox()方法的handle
int readOrWrite; //讀寫字節(jié)數(shù)
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類型數(shù)組。
讀取到MessageBox()代碼后,繼續(xù)研究如何更改代碼。下面以在Windows 7中讀取到的MessageBoxA()代碼為例,將其轉(zhuǎn)換為匯編語言后,代碼如下:
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采用的調(diào)用約定為stdcall。此種調(diào)用約定中,參數(shù)按照右至左的順序,返回值存放在EAX寄存器中,函數(shù)返回時(shí),由被調(diào)用函數(shù)負(fù)責(zé)清理堆棧。
通過對MessageBoxA()匯編代碼的分析,我們不難發(fā)現(xiàn),其最后一條有效指令為ret 0010{16}。這是一條返回指令,完成了堆棧清理的工作,其等效于以下2條指令:
POP EIP
ADD ESP,0X10
運(yùn)行該指令除了會(huì)改變EIP寄存器外,還會(huì)使得 ESP = ESP+0X10,也就是堆棧指針向棧頂移動(dòng)16個(gè)字節(jié)。
我們可以把這一條指令直接放到MessagBoxA代碼的最開始,這樣,程序運(yùn)行到MessagBoxA方法時(shí),會(huì)直接返回調(diào)用它的上一級代碼,于是,模態(tài)對話框就不會(huì)再彈出了。
將更改后指令替換掉原有MessageBox()指令的具體代碼如下:
array
WriteProcessMemory(thisProc, baseAddrA, codeNull, 3, readOrWrite);
WriteProcessMemory(thisProc, baseAddrW, codeNull, 3, readOrWrite);
其中{0xC2, 0x10, 0x00}這3個(gè)字節(jié)的來自于MessageBoxA()的最后一條指令:
C2 1000 - ret 0010 { 16 }
3結(jié)論
通過以上方法實(shí)現(xiàn)了在程序運(yùn)行過程當(dāng)中,暫時(shí)關(guān)閉Windows 系統(tǒng)中MessageBox()彈窗函數(shù)的功能。從某種程度上解決了程序運(yùn)行效率低下的問題,進(jìn)而實(shí)現(xiàn)了程序運(yùn)行過程中的無人值守。
參考文獻(xiàn)
[1] 李海雁.一個(gè)更為靈活的MessageBox()函數(shù)[J].電腦編程技巧與維護(hù),1996(01):34-35+38.