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