關於不同的程序入口,main(), _tmain(),WinMain(),wmain()?

今天終於有時間來研究一下一個很大很大的工程編譯成一個exe和若干dll後,程序是如果執行它的第一條指令的?操作系統以什麼規則來找到應該執行的第一條指令(或說如何找到第一個入口函數的)?

   我們以前寫windows程序時,都是先寫個main()函數,然後再寫自己的邏輯,然後編譯,然後點擊exe就能運行我們的程序了;如果我們用VS2005工具生成一個非空工程,工程會爲我們提供一個int_tmain(int argc, _TCHAR*argv[])或WinMain()函數的入口,然後我們在裏面添加程序等等。我們上學時這是這樣做了,但是很多人這時理所當然的,很少人會去問爲什麼會這樣?

   我讀了MSDN裏面的講解才弄出點眉目了,其實我們以前所寫的以main()函數開始的程序都是一個半成品,剩下的也是與系統息息相關的工作由編譯器幫我們代勞了。怎麼回事呢?編譯器是如何幫我們代勞的呢?那麼程序被系統加載時,準確的說是被系統中的加載器加載時又是如何知道編譯器在我們寫的程序上做了手腳呢?難道編譯器和加載器之間有什麼協定嗎?這一些列的問題,做爲剛入行的你是否在心裏問過自己沒有!?

   我們以前寫的程序在編譯器編譯成爲一個模塊(可能是obj文件或其他形式),然後鏈接器會將一些所需要的庫文件和剛纔編譯器生成的文件進行鏈接,最終生成一個exe文件,在所鏈接的庫文件中就包含CRT運行時庫,這就是我們今天談論的主角。在運行時庫裏面有好一個已經定義如下的函數函數:

(1)mainCRTStartup(或wmainCRTStartup)       //使用/SUBSYSTEM:CONSOLE 的應用程序

(2)WinMainCRTStartup(或 wWinMainCRTStartup) //使用/SUBSYSTEM:WINDOWS 的應用程序

(3)_DllMainCRTStartup                        //調用 DllMain(如果存在),DllMain 必須用 __stdcall 來定義

其中w開頭的函數時unicode版本的,分割符‘//’後面的是入口點函數匹配的subsystem(msdn中查看subsystem)屬性設置。

如果未指定 /DLL 或 /SUBSYSTEM (也就是subsystem選項)選項,則鏈接器將根據是否定義了 main 或WinMain 來選擇子系統和入口點。 函數 main、WinMain 和 DllMain 是三種用戶定義的入口點形式

在默認情況下,如果你的程序中使用的是main()或_main()函數,這鏈接器會將你的使用(1)中的函數連接到你的exe中;如果你的函數是以WinWain()函數開始的則連接器使用(2)中的函數連接進exe中;如果我們寫的是DLL程序這連接進DLL的是(3)中的函數。

   用我們寫的程序最終生成的exe執行時,一開始執行的就是上面的函數之一,而不是我們程序所寫的main或WinMain等。那麼鏈接器爲什麼要這樣做呢?這就是因爲我們寫的程序必須要使用到各種各樣的運行時庫函數才能正常工作,所有在執行我們自己寫程序之前必須要先準備好所需要的一切庫,噢,明白了吧,之所以要鏈接它們是因爲他們肩負着很重要的使命,就是初始化好運行時庫,準備在我們的程序執行時調用。

   那麼這些函數具體做了什麼呢?通過MSDN我們可以知道---它們會去進一步調用其他函數,使得C/C++運行時庫代碼在靜態非局部變量上調用構造函數和析構函數。

先摘錄一段msdn的解釋如下:

When you link your image, you eitherexplicitly or implicitly specify an entry point that the operatingsystem will call into after loading the image. For a DLL, thedefault entry point is DllMainCRTStartup. For anEXE, it is WinMainCRTStartup. You can override the default with the /ENTRY linkeroption. The CRT provides an implementation forDllMainCRTStartup,WinMainCRTStartup, andwWinMainCRTStartup (the Unicode entry point for anEXE). These CRT-provided entry points callconstructors on global objects and initialize other datastructures that are used by some CRTfunctions. This startup code adds about 25K to your image ifit is linked statically. If it is linked dynamically, most of thecode is in the DLL, so your image size stays small.

大家看到了吧,上面我用紅色標誌了嗎,我們可以使用鏈接器的鏈接選擇來設置我們的函數入口點,但是最好不要這樣做,原因就是我用藍色標誌的地方,如果我們重新設置入口點函數,則必須要在入口點函數中自己寫上有關的初始化工作,這樣豈不麻煩,所有我們最好用默認的入口點函數。

修改入口點方法:proerties->Linker->Advanced->EntryPoint

如果函數與鏈接器的SubSystem的屬性要一致的:

proerties->Linker->System->SubSystem

如果未指定 /DLL 或 /SUBSYSTEM 選項,則鏈接器將根據是否定義了 main 或 WinMain來選擇子系統和入口點。 函數 main、WinMain 和 DllMain 是三種用戶定義的入口點形式。

   通過上面的分析就知道,在微軟系統中原來操作系統中的加載器與鏈接器之間是有協議的,要不然在加載運行程序時不可能成功的,比如你將windows程序放到apple系統上運行,就會無法運行,因爲apple的加載程序根本不知道加載windows的exe的協議。 

給出一篇博文,該博文講的比較好:

http://tb.blog.csdn.net/TrackBack.aspx?PostId=455591

設有一個Win32下的可執行文件MyApp.exe,這是一個Win32應用程序,符合標準的PE格式。MyApp.exe的主要執行代碼都集中在其源文件MyApp.cpp中,該文件第一個被執行的函數是WinMain。初學者會認爲程序就是首先從這個WinMain函數開始執行,其實不然。

   在WinMain函數被執行之前,有一系列複雜的加載動作,還要執行一大段啓動代碼。運行程序MyApp.exe時,操作系統的加載程序首先爲進程分配一個4GB的虛擬地址空間,然後把程序MyApp.exe所佔用的磁盤空間作爲虛擬內存映射到這個4GB的虛擬地址空間中。一般情況下,會映射到虛擬地址空間中0X00400000的位置。加載一個應用程序的時間比一般人所設想的要少,因爲加載一個PE文件並不是把這個文件整個一次性的從磁盤讀到內存中,而是簡單的做一個內存映射,映射一個大文件和映射一個小文件所花費的時間相差無幾。當然,真正執行文件中的代碼時,操作系統還是要把存在於磁盤上的虛擬內存中的代碼交換到物理內存(RAM)中。但是,這種交換也不是把整個文件所佔用的虛擬地址空間一次性的全部從磁盤交換到物理內存中,操作系統會根據需要和內存佔用情況交換一頁或多頁。當然,這種交換是雙向的,即存在於物理內存中的一部分當前沒有被使用的頁也可能被交換到磁盤中。

   接着,系統在內核中創建進程對象和主線程對象以及其它內容。

   然後操作系統的加載程序搜索PE文件中的引入表,加載所有應用程序所使用的動態鏈接庫。對動態鏈接庫的加載與對應用程序的加載完全類似。

   再接着,操作系統執行PE文件首部所指定地址處的代碼,開始應用程序主線程的執行。首先被執行的代碼並不是MyApp中的WinMain函數,而是被稱爲CRuntime startupcode的WinMainCRTStartup函數,該函數是連接時由連接程序附加到文件MyApp.exe中的。該函數得到新進程的全部命令行指針和環境變量的指針,完成一些C運行時全局變量以及C運行時內存分配函數的初始化工作。如果使用C++編程,還要執行全局類對象的構造函數。最後,WinMainCRTStartup函數調用WinMain函數。

   WinMainCRTStartup函數傳給WinMain函數的4個參數分別爲:hInstance、hPrevInstance、lpCmdline、nCmdShow。

   hInstance:該進程所對應的應用程序當前實例的句柄。WinMainCRTStartup函數通過調用GetStartupInfo函數獲得該參數的值。該參數實際上是應用程序被加載到進程虛擬地址空間的地址,通常情況下,對於大多數進程,該參數總是0X00400000。

   hPrevInstance:應用程序前一實例的句柄。由於Win32應用程序的每一個實例總是運行在自己的獨立的進程地址空間中,因此,對於Win32應用程序,WinMainCRTStartup函數傳給該參數的值總是NULL。如果應用程序希望知道是否有另一個實例在運行,可以通過線程同步技術,創建一個具有唯一名稱的互斥量,通過檢測這個互斥量是否存在可以知道是否有另一個實例在運行。

   lpCmdline:命令行參數的指針。該指針指向一個以0結尾的字符串,該字符串不包括應用程序名。

   nCmdShow:指定如何顯示應用程序窗口。如果該程序通過在資源管理器中雙擊圖標運行,WinMainCRTStartup函數傳給該參數的值爲SW_SHOWNORMAL。如果通過在另一個應用程序中調用CreatProcess函數運行,該參數由CreatProcess函數的參數lpStartupInfo(STARTUPINFO.wShowWindow)指定。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章