在學習第一個C++程序的時候發現控制台程序的入口函數是int _tmain而不是main,查了資料才發現_tmain()是為了支持unicode所使用的main一個別名,宏定義在<stdafx.h>,有這么兩行
#include <stdio.h>
#include <tchar.h>
可以在頭文件<tchar.h>里找到_tmain的宏定義
#define _tmain wmain
所以,經過預編譯以后, _tmain就變成main了.
#define _tWinMain wWinMain\
參考一下文摘:
http://topic.csdn.net/t/20010930/15/308713.html
答:main()是WINDOWS的控制台程序(32BIT)入口或DOS程序(16BIT)入口,
WinMain()是WINDOWS的GUI程序入口,
wmain()是UNICODE版本的main(),
_tmain()是個宏,如果是UNICODE則他是wmain()否則他是main()
呵呵,看下面這篇博文,發現程序入口點並沒有我原先想象的那么簡單
可執行程序的入口點在那里?
http://blog.163.com/lyzaily@126/blog/static/4243883720091053548157/
今天終於有時間來研究一下一個很大很大的工程編譯成一個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 either explicitly or implicitly specify an entry point that the operating system will call into after loading the image. For a DLL, the default entry point is DllMainCRTStartup. For an EXE, it is WinMainCRTStartup. You can override the default with the /ENTRY linker option. The CRT provides an implementation for DllMainCRTStartup, WinMainCRTStartup, and wWinMainCRTStartup (the Unicode entry point for an EXE). These CRT-provided entry points call constructors on global objects and initialize other data structures that are used by some CRT functions. This startup code adds about 25K to your image if it is linked statically. If it is linked dynamically, most of the code 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函數,而是被稱為 C Runtime startup code的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)指定。