WinMain是如何被呼叫的

鍋煙煤發表於2022-01-08

WinMain函式

WinMain函式原型

Win32應用程式的入口函式為WinMain,函式原型在WinBase.h檔案中:

int WINAPI WinMain (

    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nShowCmd
);

WinMain函式原型中的符號

符號 描述 其它
int 返回值  
WINAPI 函式呼叫約定

WINAPI巨集展開:

#define WINAPI      __stdcall

WinMain 函式名  
HINSTANCE hInstance 當前應用程式例項控制程式碼

HINSTANCE巨集展開:

DECLARE_HANDLE(HINSTANCE);

#define DECLARE_HANDLE(name) struct name##__{int unused;}; typedef struct name##__ *name

HINSTANCE hPrevInstance 沒有意義。 它用於 16 位Windows,但現在始終為零  
LPSTR lpCmdLine 命令列引數  
int nShowCmd 視窗顯示控制引數  

程式入口

Win32程式設計入口為什麼是WinMain呢?

C/C++語言中,程式設計入口為什麼是main,而不是其它的呢?

CPU控制權傳遞

CPU是計算機的大腦,CPU讀取指令後執行指令,即:控制CPU讀指令的位置,就控制了CPU。

計算機加電後,CPU從BIOS指定位置讀取並執行開機程式第一條指令,即,CPU被開機程式控制,開機程式獲得了CPU的控制權。

開機程式獲得CPU控制權後,其它程式如何獲得CPU控制權呢?開機程式約定,我會去讀取哪個地址的指令來執行。

其它程式把指令寫入開機程式約定的地方,CPU的控制權就到了其它程式。

CPU控制權一步一步傳遞到作業系統。

CPU控制權到了作業系統後,其它程式想要獲得控制權,就需要遵循作業系統的約定。

作業系統約定包括PE格式等,首先按PE格式生成可執行檔案,如.exe。

雙擊或者呼叫程式建立函式啟動程式,作業系統將可執行程式裝載到記憶體,將下一條讀取地址指向程式的入口地址,這樣CPU的控制權傳遞到了可執行程式。

PE檔案生成

要執行自定義的指令,就要遵循與作業系統的約定。

作業系統要求裝載PE格式的檔案,可以通過編譯工具編譯生成PE格式檔案,如.exe。

程式語言

編譯工具負責生成PE格式檔案。

編譯工具約定了輸入的格式,即程式語言。

程式入口

C/C++程式語言的編譯工具約定,可執行程式的入口預設是mainCRTStartup,該入口函式可以通過設定重新指定。

入口函式

C/C++程式語言約定了程式的入口函式,如main。程式入口(mainCRTStartup)呼叫入口函式main。

不論是main還是WinMain都只是一種約定,可以根據需要制定約定。

WinMain和main

C/C++語言的入口函式都是main,為什麼Win32是WinMain?

程式入口

程式入口可以通過編譯器指定,根據需要指定入口點,控制檯預設入口為mainCRTStartup,Win32預設入口為WinMainCRTStartup,也設定為自定義函式。

main呼叫

main呼叫可以通過exe_main.cpp,exe_common.inl檔案檢視

mainCRTStartup()->__scrt_common_main()->__scrt_common_main_seh()->invoke_main()->main()。

WinMain呼叫

WinMain呼叫可以通過exe_winmain.cpp,exe_common.inl檔案檢視

WinMainCRTStartup()->__scrt_common_main()->__scrt_common_main_seh()->invoke_main()->WinMain()。

為什麼是WinMain不是main?

不管是main還是WinMain都是程式語言的約定,約定內容包括返回值,呼叫約定,函式名,函式引數。

main和WinMain引數都包含命令列引數。

對Win32應用程式來說,主函式大概率會用到應用程式例項,即HINSTANCE hInstance引數。

為了給Win32應用程式開發提供便捷,制定了更適合Win32應用程式開發的介面約定WinMain。

應用程式例項也可以通過GetModuleHandle獲取,也就是說,只要通過設定,用main或者自定義的函式作為主函式也可以編寫Win32應用程式。

構建原理

入口函式缺失

在控制檯程式中,不寫main函式,編譯報錯:

1>MSVCRTD.lib(exe_main.obj) : error LNK2019: 無法解析的外部符號 main,函式 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中引用了該符號

在Win32程式中,不寫WinMain函式,編譯報錯:

1>MSVCRTD.lib(exe_winmain.obj) : error LNK2019: 無法解析的外部符號 WinMain,函式 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中引用了該符號

LIB檔案

上面的MSVCRTD.lib(exe_main.obj) 是什麼意思?

.lib檔案可以是靜態連結庫,也可以是匯入庫。

靜態連結庫包含可執行程式碼,匯入庫包含匯入庫資訊。可以通過lib命令檢視lib檔案的型別。

MSVCRTD.lib是一個靜態連結庫。

exe_main.obj是MSVCRTD.lib中的一部分。

連結

連結時,由於程式入口為mainCRTStartup或WinMainCRTStartup,因此連結時會對程式入口的依賴進行檢查。

main連結時,exe_main.obj包含mainCRTStartup()。

mainCRTStartup()->__scrt_common_main()->__scrt_common_main_seh()->invoke_main()->main()。

如果不寫main函式,連結找不到main函式,連結報錯。

 

main連結時,exe_winmain.obj包含WinMainCRTStartup()。

WinMainCRTStartup()->__scrt_common_main()->__scrt_common_main_seh()->invoke_main()->WinMain()。

如果不寫WinMain函式,連結找不到WinMain函式,連結報錯。

 

相關文章