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

來源:互聯網
上載者:User

今天終於有時間來研究一下一個很大很大的工程編譯成一個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)指定。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.