進程是當前操作系統(tǒng)下一個被加載到內存的、正在運行的應用程序的實例。每一個進程都是由內核對象和地址空間所組成的,內核對象可以讓系統(tǒng)在其內存放有關進程的統(tǒng)計信息并使系統(tǒng)能夠以此來管理進程,而地址空間則包括了所有程序模塊的代碼和數據以及線程堆棧、堆分配空間等動態(tài)分配的空間。進程僅僅是一個存在,是不能獨自完成任何操作的,必須擁有至少一個在其環(huán)境下運行的線程,并由其負責執(zhí)行在進程地址空間內的代碼。在進程啟動的同時即同時啟動了一個線程,該線程被稱作主線程或是執(zhí)行線程,由此線程可以繼續(xù)創(chuàng)建子線程。如果主線程退出,那么進程也就沒有存在的可能了,系統(tǒng)將自動撤消該進程并完成對其地址空間的釋放。

  加載到進程地址空間的每一個可執(zhí)行文件或動態(tài)鏈接庫文件的映象都會被分配一個與之相關聯(lián)的全局唯一的實例句柄(Hinstance)。該實例句柄實際是一個記錄有進程加載位置的基本內存地址。進程的實例句柄在程序入口函數WinMain()中通過第一個參數HINSTANCE hinstExe傳遞,其實際值即為進程所使用的基本地址空間的地址。對于VC++鏈接程序所鏈接產生的程序,其默認的基本地址空間地址為0x00400000,如沒有必要一般不要修改該值。在程序中,可以通過GetModuleHandle()函數得到指定模塊所使用的基本地址空間。
  子進程的創(chuàng)建

  進程的創(chuàng)建通過CreateProcess()函數來實現,CreateProcess()通過創(chuàng)建一個新的進程及在其地址空間內運行的主線程來啟動并運行一個新的程序。具體的,在執(zhí)行CreateProcess()函數時,首先由操作系統(tǒng)負責創(chuàng)建一個進程內核對象,初始化計數為1,并立即為新進程創(chuàng)建一塊虛擬地址空間。隨后將可執(zhí)行文件或其他任何必要的動態(tài)鏈接庫文件的代碼和數據裝載到該地址空間中。在創(chuàng)建主線程時,也是首先由系統(tǒng)負責創(chuàng)建一個線程內核對象,并初始化為1。最后啟動主線程并執(zhí)行進程的入口函數WinMain(),完成對進程和執(zhí)行線程的創(chuàng)建。

  CreateProcess()函數的原型聲明如下:

BOOL CreateProcess(
 LPCTSTR lpApplicationName, // 可執(zhí)行模塊名
 LPTSTR lpCommandLine, // 命令行字符串
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 進程的安全屬性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 線程的安全屬性
 BOOL bInheritHandles, // 句柄繼承標志
 DWORD dwCreationFlags, // 創(chuàng)建標志
 LPVOID lpEnvironment, // 指向新的環(huán)境塊的指針
 LPCTSTR lpCurrentDirectory, // 指向當前目錄名的指針
 LPSTARTUPINFO lpStartupInfo, // 指向啟動信息結構的指針
 LPPROCESS_INFORMATION lpProcessInformation // 指向進程信息結構的指針
);

  在程序設計時,某一個具體的功能模塊可以通過函數或是線程等不同的形式來實現。對于同一進程而言,這些函數、線程都是存在于同一個地址空間下的,而且在執(zhí)行時,大多只對與其相關的一些數據進行處理。如果算法存在某種錯誤,將有可能破壞與其同處一個地址空間的其他一些重要內容,這將造成比較嚴重的后果。為保護地址空間中的內容可以考慮將那些需要對地址空間中的數據進行訪問的操作部分放到另外一個進程的地址空間中運行,并且只允許其訪問原進程地址空間中的相關數據。具體的,可在進程中通過CreateProcess()函數去創(chuàng)建一個子進程,子進程在全部處理過程中只對父進程地址空間中的相關數據進行訪問,從而可以保護父進程地址空間中與當前子進程執(zhí)行任務無關的全部數據。對于這種情況,子進程所體現出來的作用同函數和線程比較相似,可以看成是父進程在運行期間的一個過程。為此,需要由父進程來掌握子進程的啟動、執(zhí)行和退出。下面這段代碼即展示了此過程:

// 臨時變量
CString sCommandLine;
char cWindowsDirectory[MAX_PATH];
char cCommandLine[MAX_PATH];
DWORD dwExitCode;
PROCESS_INFORMATION pi;
STARTUPINFO si = {sizeof(si)};
// 得到Windows目錄
GetWindowsDirectory(cWindowsDirectory, MAX_PATH);
// 啟動"記事本"程序的命令行
sCommandLine = CString(cWindowsDirectory) + "\\NotePad.exe";
::strcpy(cCommandLine, sCommandLine);
// 啟動"記事本"作為子進程
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret) {
 // 關閉子進程的主線程句柄
 CloseHandle(pi.hThread);
 // 等待子進程的退出
 WaitForSingleObject(pi.hProcess, INFINITE);
 // 獲取子進程的退出碼
 GetExitCodeProcess(pi.hProcess, &dwExitCode);
 // 關閉子進程句柄
 CloseHandle(pi.hProcess);
}

  此段代碼首先通過CreateProcess()創(chuàng)建Windows自帶的“記事本”程序為子進程,子進程啟動后父進程通過WaitForSingleObject()函數等待其執(zhí)行的結束,在子進程沒有退出前父進程是一直處于阻塞狀態(tài)的,這里子進程的作用同單線程中的函數類似。一旦子進程退出,WaitForSingleObject()函數所等待的pi.hProcess對象將得到通知,父進程將得以繼續(xù),如有必要可以通過GetExitCodeProcess()來獲取子進程的退出代碼。

  相比而言,更多的情況是父進程在啟動完子進程后就再不與其進行任何數據交換和通訊,由其創(chuàng)建的子進程的執(zhí)行成功與否均與父進程無關。許多大型軟件在設計時也多采用了這類思想,將某些功能完全通過獨立的應用程序來完成,當需要執(zhí)行某操作時只要通過主程序啟動相應的子進程即可,具體的處理工作均由子進程去完成。這類子進程的創(chuàng)建過程更為簡單,例如對于上面那段代碼只需去除對子進程句柄pi.hProcess的等待即可:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret) {
 // 關閉子進程的主線程句柄
 CloseHandle(pi.hThread);
 // 關閉子進程句柄
 CloseHandle(pi.hProcess);
}

  可以通過dwCreationFlags參數在創(chuàng)建進程時設置子進程的優(yōu)先級。前面的示例代碼在創(chuàng)建子進程時使用的均是默認的優(yōu)先級,如果要將優(yōu)先級設置為高,可以修改如下:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, HIGH_PRIORITY_CLASS, NULL, NULL, &si, &pi);

  如果在進程創(chuàng)建時沒有特別設置優(yōu)先級,可以通過SetPriorityClass()函數來動態(tài)設定,該函數需要待操作進程的句柄和優(yōu)先級標識符作為入口參數,函數原型為:

BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

  對于前面沒有設定優(yōu)先級的例子代碼,可以在子進程啟動后由父進程來動態(tài)改變其優(yōu)先級設置:

SetPriorityClass(pi.hProcess, HIGH_PRIORITY_CLASS);

  或是由子進程在其啟動后自行改變優(yōu)先級設置,需要注意的是這時進程句柄應設置為子進程自身的句柄,可通過GetCurrentProcess()函數來獲?。?br>
HANDLE hProcess = GetCurrentProcess();
SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS);

  進程的互斥運行

  正常情況下,一個進程的運行一般是不會影響到其他正在運行的進程的。但是對于某些有特殊要求的如以獨占方式使用串行口等硬件設備的程序就要求在其進程運行期間不允許其他試圖使用此端口設備的程序運行的,而且此類程序通常也不允許運行同一個程序的多個實例。這就引出了進程互斥的問題。

  實現進程互斥的核心思想比較簡單:進程在啟動時首先檢查當前系統(tǒng)是否已經存在有此進程的實例,如果沒有,進程將成功創(chuàng)建并設置標識實例已經存在的標記。此后再創(chuàng)建進程時將會通過該標記而知曉其實例已經存在,從而保證進程在系統(tǒng)中只能存在一個實例。具體可以采取內存映射文件、有名事件量、有名互斥量以及全局共享變量等多種方法來實現。下面就分別對其中具有代表性的有名互斥量和全局共享變量這兩種方法進行介紹:

// 創(chuàng)建互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
// 檢查錯誤代碼
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 // 如果已有互斥量存在則釋放句柄并復位互斥量
 CloseHandle(m_hMutex);
 m_hMutex = NULL;
 // 程序退出
 return FALSE;
}

  上面這段代碼演示了有名互斥量在進程互斥中的用法。代碼的核心是CreateMutex()對有名互斥量的創(chuàng)建。CreateMutex()函數可用來創(chuàng)建一個有名或無名的互斥量對象,其函數原型為:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全屬性的指針
 BOOL bInitialOwner, // 初始化互斥對象的所有者
 LPCTSTR lpName // 指向互斥對象名的指針
);

  如果函數成功執(zhí)行,將返回一個互斥量對象的句柄。如果在CreateMutex()執(zhí)行前已經存在有相同名字的互斥量,函數將返回這個已經存在互斥量的句柄,并且可以通過GetLastError()得到錯誤代碼ERROR_ALREADY_EXIST??梢?,通過對錯誤代碼ERROR_ALREADY_EXIST的檢測可以實現CreateMutex()對進程的互斥。

  使用全局共享變量的方法則主要是在MFC框架程序中通過編譯器來實現的。通過#pragma data_seg預編譯指令創(chuàng)建一個新節(jié),在此節(jié)中可用volatile關鍵字定義一個變量,而且必須對其進行初始化。Volatile關鍵字指定了變量可以為外部進程訪問。最后,為了使該變量能夠在進程互斥過程中發(fā)揮作用,還要將其設置為共享變量,同時允許具有讀、寫訪問權限。這可以通過#pragma comment預編譯指令來通知編譯器。下面給出使用了全局變量的進程互斥代碼清單:

#pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
……
if(++g_lAppInstance>1)
return FALSE;

  此段代碼的作用是在進程啟動時對全局共享變量g_nAppInstancd 加1 ,如果發(fā)現其值大于1,那么就返回FALSE以通知進程結束。這里需要特別指出的是,為了使以上兩段代碼能夠真正起到對進程互斥的作用,必須將其放置在應用程序的入口代碼處,即應用程序類的初始化實例函數InitInstance()的開始處。

  結束進程

  進程只是提供了一段地址空間和內核對象,其運行是通過在其地址空間內的主線程來體現的。當主線程的進入點函數返回時,進程也就隨之結束。這種進程的終止方式是進程的正常退出,進程中的所有線程資源都能夠得到正確的清除。除了這種進程的正常推出方式外,有時還需要在程序中通過代碼來強制結束本進程或其他進程的運行。ExitProcess()函數即可在進程中的某個線程中使用,并將立即終止本進程的運行。ExitProcess()函數原型為:

VOID ExitProcess(UINT uExitCode);

  其參數uExitCode為進程設置了退出代碼。該函數具有強制性,在執(zhí)行完畢后進程即已經被結束,因此位于其后的任何代碼將不能被執(zhí)行。雖然ExitProcess()函數可以在結束進程的同時通知與其相關聯(lián)的動態(tài)鏈接庫,但是由于它的這種執(zhí)行的強制性,使得ExitProcess()函數在使用上將存在有安全隱患。例如,如果在程序調用ExitProcess()函數之前曾用new操作符申請過一段內存,那么將會由于ExitProcess()函數的強制性而無法通過delete操作符將其釋放,從而造成內存泄漏。有鑒于ExitProcess()函數的強制性和不安全性,在使用時一定要引起注意。

  ExitProcess()只能強制執(zhí)行本進程的退出,如果要在一個進程中強制結束其他的進程就要用TerminateProcess()來實現。與ExitProcess()不同,TerminateProcess()函數執(zhí)行后,被終止的進程是不會得到任何關于程序退出的通知的。也就是說,被終止的進程是無法在結束運行前進行退出前的收尾工作的。所以,通常只有在其他任何方法都無法迫使進程退出時才會考慮使用TerminateProcess()去強制結束進程的。下面給出TerminateProcess()的函數原型:

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);


  參數hProcess和uExitCode分別為進程句柄和退出代碼。如果被結束的是本進程,可以通過GetCurrentProcess()獲取到句柄。TerminateProcess()是異步執(zhí)行的,在調用返回后并不能確定被終止進程是否已經真的退出,如果調用TerminateProcess()的進程對此細節(jié)關心,可以通過WaitForSingleObject()來等待進程的真正結束。

  小結

  多進程是多任務管理中的重要內容,文中上述部分對其基本概念和主要的技術如子進程的創(chuàng)建與結束、進程間的互斥運行等做了較詳細的介紹。通過本文讀者應能對多進程管理有一個初步的認識。

進程控制簡單的說相當于在一個程序中執(zhí)行另一個程序,你可以把它想象成在 Dos 下用 int 21h/4bh 功能來執(zhí)行另外一個程序,如果單從執(zhí)行另一個程序的目的來講,在 Windows 中有不少方法,如使用 ShellExecute 等,但這些 Api 僅僅是“執(zhí)行”而已,進程控制的意義在于可以創(chuàng)建一個進程,并可以通過進程句柄結束進程,同樣你也可以通過進程句柄來跟蹤程序,還可以用 ReadProcessMemory 和 WriteProcessMemory 來讀寫子進程的內存空間。

進程控制要使用的相關 API 有下面這些:

創(chuàng)建進程的函數為CreateProcess,該函數比較復雜,共有十個參數,但有個好消息是使用時大部分可以用 NULL。

BOOL CreateProcess(
LPCTSTR lpApplicationName, // 執(zhí)行程序文件名
LPTSTR lpCommandLine, // 參數行
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 進程安全參數
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 線程安全參數
BOOL bInheritHandles, // 繼承標記
DWORD dwCreationFlags, // 創(chuàng)建標記
LPVOID lpEnvironment, // 環(huán)境變量
LPCTSTR lpCurrentDirectory, // 運行該子進程的初始目錄
LPSTARTUPINFO lpStartupInfo, // 創(chuàng)建該子進程的相關參數
LPPROCESS_INFORMATION lpProcessInformation // 創(chuàng)建后用于被創(chuàng)建子進程的信息
);


各個參數的說明如下:

  • lpApplicationName:為執(zhí)行程序的文件名,你也可以把執(zhí)行文件名包括在下一個參數 lpCommandLine 中,然后把該參數置為NULL。
  • lpCommandLine:為參數行,如果無參數可以為NULL,在有參數傳遞給進程時可以如下設置:lpApplicationName=文件名;lpCommandLine=參數,或者 lpApplicationName=NULL;lpCommandLine=文件名 + 參數。
  • lpProcessAttributes,lpThreadAttributes:分別描述了創(chuàng)建的進程和線程安全屬性,如果使用NULL表示使用默認的安全描述。
  • bInheritHandles:表示當前進程中的打開的句柄是否能夠被創(chuàng)建的子進程所繼承。
  • dwCreationFlags:表示創(chuàng)建標記,通過該標記可以設置進程的創(chuàng)建狀態(tài)和優(yōu)先級別。常用的有下面的標記:
    CREATE_NEW_CONSOLE:為子進程創(chuàng)建一個新的控制臺。
    CREATE_SUSPENDED:子進程在創(chuàng)建時為掛起狀態(tài)。如果指定了這個參數,那么執(zhí)行 CreateProcess 后進程只是被裝入內存,但不是馬上開始執(zhí)行,而是必須等主程序調用 ResumeThread 后才繼續(xù)執(zhí)行。
    HIGH_PRIORITY_CLASS/NORMAL_PRIORITY_CLASS:高/普通優(yōu)先級別。
  • lpEnvironment:表示子進程所使用的環(huán)境變量,如果為NULL,則表示與當前進程使用相同的環(huán)境變量。
  • lpCurrentDirectory:表示子進程運行的初始目錄。
  • lpStartupInfo:STARTUPINFO 結構,用于在創(chuàng)建子進程時設置各種屬性。
  • lpProcessInformation:PROCESS_INFORMATION 結構,用來在進程創(chuàng)建后接收相關信息,該結構由系統(tǒng)填寫。

調用 CreateProcess 函數有三個參數是必需的,一在 lpApplicationName 或 lpCommandLine 指定文件名,二是 lpStartupInfo 結構,三是 PROCESS_INFORMATION 結構,因為 PROCESS_INFORMATION 結構返回了進程建立后的句柄,以后的一切操作將要用到這些返回的句柄,它是由系統(tǒng)填寫的,結構說明如下:

typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; //進程句柄
HANDLE hThread; //進程的主線程句柄
DWORD dwProcessId; //進程ID
DWORD dwThreadId; //進程的主線程ID
} PROCESS_INFORMATION;

另外還有一個關鍵的結構 STARTUPINFO,該結構定義如下:

typedef struct STARTUPINFO {
DWORD cb; //結構長度
LPTSTR lpReserved; //保留
LPTSTR lpDesktop; //保留
LPTSTR lpTitle; //如果為控制臺進程則為顯示的標題
DWORD dwX; //窗口位置
DWORD dwY; //窗口位置
DWORD dwXSize; //窗口大小
DWORD dwYSize; //窗口大小
DWORD dwXCountChars; //控制臺窗口字符號寬度
DWORD dwYCountChars; //控制臺窗口字符號高度
DWORD dwFillAttribute; //控制臺窗口填充模式
DWORD dwFlags; //創(chuàng)建標記
WORD wShowWindow; //窗口顯示標記,如同ShowWindow中的標記
WORD cbReserved2; //
LPBYTE lpReserved2; //
HANDLE hStdInput; //標準輸入句柄
HANDLE hStdOutput; //標準輸出句柄
HANDLE hStdError; //標準錯誤句柄
} STARTUPINFO, *LPSTARTUPINFO;

結構中 dwFlags 指定了其它的一些字段是否有效,如:dwFlags包含 STARTF_USESIZE 表示dwXSize和dwYSize有效,包含STARTF_USEPOSITION表示dwX和dwY有效,等等。如果不是有特殊的要求,我們不用自己去填寫這個結構,只需用 GetStartupInfo 讓 Windows 為你填寫好了,這樣,建立一個進程的語句就是:

...
stStartUp STARTUPINFO stProcInfo PROCESS_INFORMATION <?>
stProcInfo PROCESS_INFORMATION <?>
...

invoke GetStartupInfo,addr stStartUp
invoke CreateProcess,NULL,addr szFileName,NULL,NULL,NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo

...

如果成功的話,eax 將返回非零值,注意返回在 PROCESS_INFORMATION 結構中的 hProcess,以后很多的操作都要用到它。

強制結束一個進程的 API 為 TerminateProcess

BOOL TerminateProcess(
HANDLE hProcess, // 進程句柄
UINT uExitCode // 退出代碼
);

你可以使用語句 invoke TerminateProcess,structProcInfo.hProcess,0 來結束進程,要注意的是如果可能的話,盡量不要在程序中強制結束別的進程,因為使用 TerminateProcess 結束的進程,它裝載的 dll 不能被正確卸載。這樣可能會引起系統(tǒng)資源的無效占用。最好的辦法在進程中自己使用 ExitProcess 退出。

查詢一個進程狀態(tài)的 API 為 GetExitCodeProcess。

BOOL GetExitCodeProcess(
HANDLE hProcess, // handle to the process
LPDWORD lpExitCode // address to receive termination status
);

如果進程尚未退出,函數將會返回STILL_ACTIVE。這個 API 是馬上返回的。

等待進程執(zhí)行可以用 WaitForSingleObject

這個 API 并不是單用于進程的等待,其它還可以用在線程等操作,但我們一般用它來等待進程的執(zhí)行,它的申明是:

DWORD WaitForSingleObject(
HANDLE hHandle, // handle of object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);

如果我們要等待進程執(zhí)行 1 秒鐘,可以 invoke WaitForSingleObject,stProcInfo.hProcess,1000 如果要等到進程結束,可以用 WaitForSingleObject,stProcInfo.hProcess,INFINITE ,參數 2 中的 INFINITE 在 Windows.inc 中有定義,意思是無窮等待。

最后,當不再使用進程句柄的時候,不要忘了使用 CloseHandle 關閉 hProcess 和 hThread,否則會浪費系統(tǒng)句柄的資源。

源程序 - 匯編源文件


	.386
	.model flat, stdcall
	option casemap :none   ; case sensitive
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	Include 數據
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include		windows.inc
include		user32.inc
include		kernel32.inc
include		comctl32.inc
include		comdlg32.inc
include		gdi32.inc

includelib	user32.lib
includelib	kernel32.lib
includelib	comctl32.lib
includelib	comdlg32.lib
includelib	gdi32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	Equ 數據
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN	equ	3000
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ID_BROWSE	equ	3001
ID_RUN		equ	3002
ID_EXIT		equ	3003
ID_TEXT		equ	3004

F_RUNNING	equ	0001h	;進程在運行中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	數據段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.data?

stStartUp	STARTUPINFO		<?>
stProcInfo	PROCESS_INFORMATION	<?>
stOpenFileName	OPENFILENAME	<?>

hRunThread	dd	?
hInstance	dd	?
hWinMain	dd	?
hIcon		dd	?
szBuffer	db	512 dup	(?)

dwFlag		dd	?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		.data

szExcute	db	'執(zhí)行(&E)',0		;按鈕文字
szKill		db	'終止(&E)',0
szExcuteError	db	'啟動應用程序錯誤!',0

szTitleOpen	db	"Open executable file...",0
szExt		db	'*.exe',0
szFilter	db	'Excutable Files',0,'*.exe;*.com',0
		db	0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.code

if		DEBUG
		include		Debug.asm
endif
include		Win.asm

;********************************************************************
; 執(zhí)行程序用的線程
; 1. 用 CreateProcess 建立進程
; 2. 用 WaitForSingleOject 等待進程結束
;********************************************************************
_RunThread	proc	uses ebx ecx edx esi edi,		dwParam:DWORD

		or	dwFlag,F_RUNNING
;********************************************************************
; 取消“退出”按鈕并把“執(zhí)行”按鈕改為“中止”
;********************************************************************
		invoke	GetDlgItem,hWinMain,ID_EXIT
		invoke	EnableWindow,eax,FALSE
		invoke	SendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szKill

;********************************************************************
; 執(zhí)行文件,如果成功則等待程序結束
;********************************************************************
		invoke	GetStartupInfo,addr stStartUp
		invoke	CreateProcess,NULL,addr szBuffer,NULL,NULL,			NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo
		.if	eax !=	0
			invoke	WaitForSingleObject,stProcInfo.hProcess,INFINITE
			invoke	CloseHandle,stProcInfo.hProcess
			invoke	CloseHandle,stProcInfo.hThread
		.else
			invoke	MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR
		.endif
;********************************************************************
; Enable “退出”按鈕并把“中止”按鈕改為“執(zhí)行”
;********************************************************************
		invoke	GetDlgItem,hWinMain,ID_EXIT
		invoke	EnableWindow,eax,TRUE
		invoke	SendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szExcute
		and	dwFlag,not F_RUNNING
		ret

_RunThread	endp

;********************************************************************
;	窗口程序
;********************************************************************
DialogMainProc	proc	uses ebx edi esi, 		hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

		mov	eax,wMsg
;********************************************************************
		.if	eax ==	WM_INITDIALOG
			mov	eax,hWnd
			mov	hWinMain,eax
			call	_Init
;********************************************************************
		.elseif	eax ==	WM_CLOSE
			invoke	EndDialog,hWinMain,NULL
;********************************************************************
		.elseif	eax ==	WM_COMMAND
			mov	eax,wParam
			.if	ax ==	ID_BROWSE
				call	_BrowseFile
				call	_CheckText
			.elseif	ax ==	ID_TEXT
				invoke	GetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512
				call	_CheckText
			.elseif	ax ==	ID_RUN
;********************************************************************
; 如果沒有在執(zhí)行中(dwFlag 沒有置位) 則建立線程,在線程中執(zhí)行程序
; 如果已經在執(zhí)行中,則用 TerminateProcess 終止執(zhí)行
;********************************************************************
				test	dwFlag,F_RUNNING
				.if	ZERO?
					invoke	CreateThread,NULL,NULL,offset _RunThread,					NULL,NULL,offset hRunThread
				.else
					invoke	TerminateProcess,stProcInfo.hProcess,-1
				.endif
			.elseif	ax ==	ID_EXIT
				invoke	EndDialog,hWinMain,NULL
			.endif
		.else
;********************************************************************
;	注意:對話框的消息處理后,要返回 TRUE,對沒有處理的消息
;	要返回 FALSE
;********************************************************************
			mov	eax,FALSE
			ret
		.endif
		mov	eax,TRUE
		ret

DialogMainProc	endp
;********************************************************************
; 程序入口
;********************************************************************
start:
		invoke	InitCommonControls
		invoke	GetModuleHandle,NULL
		mov	hInstance,eax
		invoke	DialogBoxParam,hInstance,DLG_MAIN,NULL,offset DialogMainProc,0
		invoke	ExitProcess,NULL
;********************************************************************
_Init		proc

		invoke	_CenterWindow,hWinMain

		invoke	SendDlgItemMessage,hWinMain,ID_TEXT,EM_LIMITTEXT,512,NULL
		invoke	GetDlgItem,hWinMain,ID_RUN
		invoke	EnableWindow,eax,FALSE

		ret

_Init		endp
;********************************************************************
; 根據 text control 中有無字符決定是否將“執(zhí)行”按鈕 Disable 掉
;********************************************************************
_CheckText	proc

		invoke	GetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512
		invoke	lstrlen,addr szBuffer
		.if	eax != 0 || (dwFlag & F_RUNNING)
			invoke	GetDlgItem,hWinMain,ID_RUN
			invoke	EnableWindow,eax,TRUE
		.else
			invoke	GetDlgItem,hWinMain,ID_RUN
			invoke	EnableWindow,eax,FALSE
		.endif
		ret

_CheckText	endp
;********************************************************************
_BrowseFile	proc

		mov	stOpenFileName.Flags,OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
		mov	stOpenFileName.lStructSize,SIZEOF stOpenFileName
		mov	eax,hWinMain
		mov	stOpenFileName.hwndOwner,eax
		mov	stOpenFileName.lpstrFilter,offset szFilter	;擴展名
		mov	stOpenFileName.lpstrFile,offset szBuffer	;文件名緩沖
		mov	stOpenFileName.nMaxFile,512			;文件名緩沖長度
		mov	stOpenFileName.lpstrInitialDir,0
		mov	stOpenFileName.lpstrTitle,offset szTitleOpen
		mov	stOpenFileName.lpstrDefExt,offset szExt
		invoke	GetOpenFileName,offset stOpenFileName
		.if	eax == FALSE
			ret
		.endif
		invoke	SetDlgItemText,hWinMain,ID_TEXT,addr szBuffer
		ret

_BrowseFile	endp
;********************************************************************
		end	start


程序的分析和要點

    本程序在使用調用 GetOpenFileName 或者自己在文本框中輸入執(zhí)行文件名,然后通過 CreateProcess 建立進程,最后用 WaitForSingleObject 等待進程結束,如果在對話框的處理過程中等待會導致程序在進程返回前無法響應,所以程序中用 CreateThread 建立一個線程來實現這個過程,當子過程返回的時候,線程結束。dwFlag 中的 0 位作為標志位,表示是否子過程在運行中,如果這一位置 1 的話,按下“終止”按鈕會用 TerminateProcess 來強制終止子進程。