VC++中程式與多程式管理的方法(轉)

ba發表於2007-08-15
VC++中程式與多程式管理的方法(轉)[@more@]程式是當前作業系統下一個被載入到記憶體的、正在執行的應用程式的例項。每一個程式都是由核心物件和地址空間所組成的,核心物件可以讓系統在其記憶體放有關程式的統計資訊並使系統能夠以此來管理程式,而地址空間則包括了所有程式模組的程式碼和資料以及執行緒堆疊、堆分配空間等動態分配的空間。程式僅僅是一個存在,是不能獨自完成任何操作的,必須擁有至少一個在其環境下執行的執行緒,並由其負責執行在程式地址空間內的程式碼。在程式啟動的同時即同時啟動了一個執行緒,該執行緒被稱作主執行緒或是執行執行緒,由此執行緒可以繼續建立子執行緒。如果主執行緒退出,那麼程式也就沒有存在的可能了,系統將自動撤消該程式並完成對其地址空間的釋放。

  載入到程式地址空間的每一個可執行檔案或動態連結庫檔案的映象都會被分配一個與之相關聯的全域性唯一的例項控制程式碼(Hinstance)。該例項控制程式碼實際是一個記錄有程式載入位置的基本記憶體地址。程式的例項控制程式碼在程式入口函式WinMain()中透過第一個引數 HINSTANCE hinstExe傳遞,其實際值即為程式所使用的基本地址空間的地址。對於VC++連結程式所連結產生的程式,其預設的基本地址空間地址為 0x00400000,如沒有必要一般不要修改該值。在程式中,可以透過GetModuleHandle()函式得到指定模組所使用的基本地址空間。
  子程式的建立

  程式的建立透過CreateProcess()函式來實現,CreateProcess()透過建立一個新的程式及在其地址空間內執行的主執行緒來啟動並執行一個新的程式。具體的,在執行CreateProcess()函式時,首先由作業系統負責建立一個程式核心物件,初始化計數為1,並立即為新程式建立一塊虛擬地址空間。隨後將可執行檔案或其他任何必要的動態連結庫檔案的程式碼和資料裝載到該地址空間中。在建立主執行緒時,也是首先由系統負責建立一個執行緒核心物件,並初始化為1。最後啟動主執行緒並執行程式的入口函式WinMain(),完成對程式和執行執行緒的建立。

  CreateProcess()函式的原型宣告如下:

BOOL CreateProcess(
 LPCTSTR lpApplicationName, // 可執行模組名
 LPTSTR lpCommandLine, // 命令列字串
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 程式的安全屬性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 執行緒的安全屬性
 BOOL bInheritHandles, // 控制程式碼繼承標誌
 DWORD dwCreationFlags, // 建立標誌
 LPVOID lpEnvironment, // 指向新的環境塊的指標
 LPCTSTR lpCurrentDirectory, // 指向當前目錄名的指標
 LPSTARTUPINFO lpStartupInfo, // 指向啟動資訊結構的指標
 LPPROCESS_INFORMATION lpProcessInformation // 指向程式資訊結構的指標
);

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

// 臨時變數
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, π);
if (ret) {
 // 關閉子程式的主執行緒控制程式碼
 CloseHandle(pi.hThread);
 // 等待子程式的退出
 WaitForSingleObject(pi.hProcess, INFINITE);
 // 獲取子程式的退出碼
 GetExitCodeProcess(pi.hProcess, &dwExitCode);
 // 關閉子程式控制程式碼
 CloseHandle(pi.hProcess);
}

  此段程式碼首先透過CreateProcess()建立Windows自帶的“記事本”程式為子程式,子程式啟動後父程式透過 WaitForSingleObject()函式等待其執行的結束,在子程式沒有退出前父程式是一直處於阻塞狀態的,這裡子程式的作用同單執行緒中的函式類似。一旦子程式退出,WaitForSingleObject()函式所等待的pi.hProcess物件將得到通知,父程式將得以繼續,如有必要可以透過GetExitCodeProcess()來獲取子程式的退出程式碼。

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

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, π);
if (ret) {
 // 關閉子程式的主執行緒控制程式碼
 CloseHandle(pi.hThread);
 // 關閉子程式控制程式碼
 CloseHandle(pi.hProcess);
}

  可以透過dwCreationFlags引數在建立程式時設定子程式的優先順序。前面的示例程式碼在建立子程式時使用的均是預設的優先順序,如果要將優先順序設定為高,可以修改如下:

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

  如果在程式建立時沒有特別設定優先順序,可以透過SetPriorityClass()函式來動態設定,該函式需要待操作程式的控制程式碼和優先順序識別符號作為入口引數,函式原型為:

BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

  對於前面沒有設定優先順序的例子程式碼,可以在子程式啟動後由父程式來動態改變其優先順序設定:

SetPriorityClass(pi.hProcess, HIGH_PRIORITY_CLASS);

  或是由子程式在其啟動後自行改變優先順序設定,需要注意的是這時程式控制程式碼應設定為子程式自身的控制程式碼,可透過GetCurrentProcess()函式來獲取:

HANDLE hProcess = GetCurrentProcess();
SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS);

  程式的互斥執行

  正常情況下,一個程式的執行一般是不會影響到其他正在執行的程式的。但是對於某些有特殊要求的如以獨佔方式使用序列口等硬體裝置的程式就要求在其程式執行期間不允許其他試圖使用此埠裝置的程式執行的,而且此類程式通常也不允許執行同一個程式的多個例項。這就引出了程式互斥的問題。

實現程式互斥的核心思想比較簡單:程式在啟動時首先檢查當前系統是否已經存在有此程式的例項,如果沒有,程式將成功建立並設定標識例項已經存在的標記。此後再建立程式時將會透過該標記而知曉其例項已經存在,從而保證程式在系統中只能存在一個例項。具體可以採取記憶體對映檔案、有名事件量、有名互斥量以及全域性共享變數等多種方法來實現。下面就分別對其中具有代表性的有名互斥量和全域性共享變數這兩種方法進行介紹:

// 建立互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
// 檢查錯誤程式碼
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 // 如果已有互斥量存在則釋放控制程式碼並復位互斥量
 CloseHandle(m_hMutex);
 m_hMutex = NULL;
 // 程式退出
 return FALSE;
}

  上面這段程式碼演示了有名互斥量在程式互斥中的用法。程式碼的核心是CreateMutex()對有名互斥量的建立。CreateMutex()函式可用來建立一個有名或無名的互斥量物件,其函式原型為:

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

  如果函式成功執行,將返回一個互斥量物件的控制程式碼。如果在CreateMutex()執行前已經存在有相同名字的互斥量,函式將返回這個已經存在互斥量的控制程式碼,並且可以透過GetLastError()得到錯誤程式碼ERROR_ALREADY_EXIST。可見,透過對錯誤程式碼 ERROR_ALREADY_EXIST的檢測可以實現CreateMutex()對程式的互斥。

  使用全域性共享變數的方法則主要是在 MFC框架程式中透過編譯器來實現的。透過#pragma data_seg預編譯指令建立一個新節,在此節中可用volatile關鍵字定義一個變數,而且必須對其進行初始化。Volatile關鍵字指定了變數可以為外部程式訪問。最後,為了使該變數能夠在程式互斥過程中發揮作用,還要將其設定為共享變數,同時允許具有讀、寫訪問許可權。這可以透過#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 ,如果發現其值大於1,那麼就返回FALSE以通知程式結束。這裡需要特別指出的是,為了使以上兩段程式碼能夠真正起到對程式互斥的作用,必須將其放置在應用程式的入口程式碼處,即應用程式類的初始化例項函式InitInstance()的開始處。

  結束程式

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

VOID ExitProcess(UINT uExitCode);

  其引數uExitCode為程式設定了退出程式碼。該函式具有強制性,在執行完畢後程式即已經被結束,因此位於其後的任何程式碼將不能被執行。雖然 ExitProcess()函式可以在結束程式的同時通知與其相關聯的動態連結庫,但是由於它的這種執行的強制性,使得ExitProcess()函式在使用上將存在有安全隱患。例如,如果在程式呼叫ExitProcess()函式之前曾用new運算子申請過一段記憶體,那麼將會由於ExitProcess ()函式的強制性而無法透過delete運算子將其釋放,從而造成記憶體洩漏。有鑑於ExitProcess()函式的強制性和不安全性,在使用時一定要引起注意。

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

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);


  引數hProcess和uExitCode分別為程式控制程式碼和退出程式碼。如果被結束的是本程式,可以透過GetCurrentProcess()獲取到控制程式碼。TerminateProcess()是非同步執行的,在呼叫返回後並不能確定被終止程式是否已經真的退出,如果呼叫TerminateProcess ()的程式對此細節關心,可以透過WaitForSingleObject()來等待程式的真正結束。

  小結

  多程式是多工管理中的重要內容,文中上述部分對其基本概念和主要的技術如子程式的建立與結束、程式間的互斥執行等做了較詳細的介紹。透過本文讀者應能對多程式管理有一個初步的認識。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-957348/,如需轉載,請註明出處,否則將追究法律責任。

相關文章