MSVC:關於編譯、鏈接、裝載、庫相關的一些概念


轉自:http://www.fmddlmyy.cn/text3.html , 有改動.

1、Run-Time Library(運行時庫)

Run-Time Library是編譯器提供的標准庫,提供一些基本的庫函數和系統調用。

我們一般使用的Run-Time Library是C Run-Time Libraries。當然也有Standard C++ libraries。

C Run-Time Libraries實現ANSI C的標准庫。VC安裝目錄的CRT目錄有C Run-Time庫的大部分源代碼。

C Run-Time Libraries有靜態庫版本,也有動態鏈接庫版本;有單線程版本,也有多線程版本;還有調試和非調試版本。

可以在"project"-"settings"-"C/C++"-"Code Generation"中選擇Run-Time Library的版本。

動態鏈接庫版本

/MD Multithreaded DLL 使用導入庫MSVCRT.LIB,對應MSVCRT.DLL

/MDd Debug Multithreaded DLL 使用導入庫MSVCRTD.LIB,對應MSVCRTD.DLL

靜態庫版本

/MT Multithreaded 使用靜態庫LIBCMT.LIB

/MTd Debug Multithreaded 使用靜態庫LIBCMTD.LIB

注:

1.單線程的運行庫已廢止.

2.標准C++庫的選擇同於CRT的選擇,但是由編譯器默認添加的。即,若使用了動態版本的CRT且使用了標准C++庫中的相關函數,則編譯器默認添加使用標准C++庫的動態版本:msvcprt.lib(對應msvcpr.dll);否則使用標准C++庫的靜態版本libcpmt.lib.

C Run-Time Library的標准io部分與操作系統的關系很密切,在Windows上,CRT的io部分代碼只是一個包底層要用到

操作系統內核kernel32.dll中的函數,在編譯時使用導入庫kernel32.lib。這也就是為什么在嵌入式環境中,我們一般不能直接

使用C標准庫。

在Linux環境當然也有C標准庫,例如:

ld -o output /lib/crt0.o hello.o -lc

參數"-lc"就是在引用C標准庫libc.a。猜一猜"-lm"引用哪個庫文件?

2、常見的編譯參數

VC建立項目時總會定義"Win32"。控制台程序會定義"_CONSOLE",否則會定義"_WINDOWS"。Debug版定義"_DEBUG",

Release版定義"NDEBUG"。

與MFC DLL有關的編譯常數包括

_WINDLL 表示要做一個用到MFC的DLL

_USRDLL 表示做一個用戶DLL(相對MFC擴展DLL而言)

_AFXDLL 表示使用MFC動態鏈接庫

_AFXEXT 表示要做一個MFC擴展DLL

所以:

Regular, statically linked to MFC :_WINDLL,_USRDLL

Regular, using the shared MFC DLL: _WINDLL,_USRDLL,_AFXDLL

Extension DLL :_WINDLL,_AFXDLL,_AFXEXT

CL.EXE編譯所有源文件,LINK.EXE鏈接EXE和DLL,LIB.EXE產生靜態庫。

(轉自:TN011: Using MFC as Part of a DLL ):

When compiling regular DLLs that statically link to MFC, the symbols "_USRDLL" and "_WINDLL" must be defined. Your DLL code must also be compiled with the following compiler switches:

  • /D_WINDLL signifies the compilation is for a DLL
  • /D_USRDLL specifies you are building a regular DLL

When compiling regular DLLs that dynamically link to MFC, you must define the above symbols and use the above compiler switches. Additionally, the symbol "_AFXDLL" must be defined and your DLL code must be compiled with:

  • /D_AFXDLL specifies that you are building a regular DLL that dynamically links to MFC

The interfaces (APIs) between the application and the DLL must be explicitly exported. It is recommended that you define your interfaces to be low bandwidth, sticking to C interfaces where possible. More direct C interfaces are easier to maintain than more complex C++ classes.

Place your APIs in a separate header that can be included by both C and C++ files (that way you won't limit your DLL customers to C++ programmers). See the header ScreenCap.h in the MFC Advanced Concepts sample DLLScreenCap for an example. To export your functions, enter them in the EXPORTS section of your module definition file (.DEF) or include __declspec(dllexport) on your function definitions. Use __declspec(dllimport) to import these functions into the client executable.

You must add the AFX_MANAGE_STATE macro at the beginning of all the exported functions in regular DLLs that dynamically link to MFC to set the current module state to the one for the DLL. This is done by adding the following line of code to the beginning of functions exported from the DLL:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

3、subsystem和可執行文件的啟動

LINK的時候需要指定/subsystem,這個鏈接選項告訴Windows如何運行可執行文件。

控制台程序是/subsystem:"console"

其它程序一般都是/subsystem:"windows "

將 subsystem 選成"console"后,Windows在進入可執行文件的代碼前(如mainCRTStartup),就會產生一個控制台窗

口。

如果選擇"windows",操作系統就不產生console窗口,該類型應用程序的窗口由用戶自己創建。

可執行文件都有一個Entry Point,LINK時可以用/entry指定。

缺省情況下,如果subsystem是“console”,Entry Point是 mainCRTStartup(ANSI)或 wmainCRTStartuup(UNICODE),即:

/subsystem:"console" /entry:"mainCRTStartup" (ANSI)

/subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)

mainCRTStartup 或 wmainCRTStartuup 會調用main或wmain。

值得一提的是,在進入應用程序的Entry Point前,Windows的裝載器已經做過C變量的初始化,有初值的全局變量擁有了它們

的初值,沒有初值的變量被設為0。

如果subsystem是“windows”,Entry Point是WinMain(ANSI)或wWinMain(UINCODE),即:

/subsystem:"windows" /entry:"WinMainCRTStartup" (ANSI)

/sbusystem:"windows" /entry:"wWinMainCRTStartup" (UINCODE)

WinMainCRTStartup 或 wWinMainCRTStartup 會調用 WinMain 或 wWinMain。

這些入口點函數,在CRT目錄都可以看到源代碼,例如(為了簡潔,我刪除了原代碼的一些條件編譯):

void mainCRTStartup(void)
{
	int mainret;
	/* Get the full Win32 version */
	_osver = GetVersion();
	_winminor = (_osver >> 8) & 0x00FF ;
	_winmajor = _osver & 0x00FF ;
	_winver = (_winmajor << 8) + _winminor;
	_osver = (_osver >> 16) & 0x00FFFF ;
#ifdef _MT
	if ( !_heap_init(1) ) /* initialize heap */
#else /* _MT */
	if ( !_heap_init(0) ) /* initialize heap */
#endif /* _MT */
		fast_error_exit(_RT_HEAPINIT); /* write message and die */
#ifdef _MT
	if( !_mtinit() ) /* initialize multi-thread */
		fast_error_exit(_RT_THREAD); /* write message and die */
#endif /* _MT */
	__try {
		_ioinit(); /* initialize lowio */
		_acmdln = (char *)GetCommandLineA(); /* get cmd line info */
		_aenvptr = (char *)__crtGetEnvironmentStringsA(); /* get environ info */
		_setargv();
		_setenvp();
		__initenv = _environ;
		mainret = main(__argc, __argv, _environ);
		exit(mainret);
	}
	__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
	{
		_exit( GetExceptionCode() ); /* Should never reach here */
	} /* end of try - except */
} 

如果使用MFC框架,WinMain也會被埋藏在MFC庫中(APPMODUL.CPP):

extern "C" int WINAPI
	_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,	LPTSTR lpCmdLine, int nCmdShow)
{
	// call shared/exported WinMain
	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

對於ANSI版本,"_tWinMain"就是"WinMain";對於UINCODE版本,"_tWinMain"就是"wWinMain"。可參見

afx.h:

#ifdef _UNICODE
#define _tmain wmain
#define _tWinMain wWinMain
#else
#define _tmain main
#define _tWinMain WinMain
#endif

全局C++對象的構造函數是在什么地方調用的?答案是在進入應用程序的Entry Point后,在調用main函數前的初始化操作

中。所以MFC的theApp的構造函數是在_tWinMain之前調用的。

4、不顯示Console窗口的Console程序

在默認情況下/subsystem 和/entry開關是匹配的,也就是:

"console"對應"mainCRTStartup"或者"wmainCRTStartup".

"windows"對應"WinMain"或者"wWinMain".

我們可以通過手動修改的方法使他們不匹配。例如:

#include "windows.h"
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 設置入口地址 
void main(void)
{
	MessageBox(NULL, "hello", "Notice", MB_OK);
}

這個Console程序就不會顯示Console窗口。如果選/MLd的話,這個程序只需要鏈接LIBCD.LIB user32.lib kernel32.lib。

其實如果不想看到Console窗口,還有一個更直接的方法:那就是直接在EXE文件中將PE文件頭的Subsystem從3改成2。在

EXE文件中,PE文件頭的偏移地址是0x3c,Subsystem是一個WORD,它在PE文件頭中的偏移是0x5c。

5、MFC的庫文件

MFC的庫可以靜態鏈接,也可以動態鏈接。靜態庫和動態庫又有Debug和Release,ANSI和Unicode版本之分。

靜態MFC庫主要有

ANSI Debug   : NAFXCWD.LIB

ANSI Release : NAFXCW.LIB

Unicode Debug  : UAFXCWD.LIB

Unicode Release: UAFXCW.LIB

動態鏈接庫主要有:

ANSI Debug:

MFCxxD.LIB (core,MFCxxD.DLL),

MFCOxxD.LIB (OLE,MFCOxxD.DLL),

MFCDxxD.LIB (database,MFCDxxD.DLL),

MFCNxxD.LIB (network,MFCNxxD.DLL),

MFCSxxD.LIB (static)

ANSI Release:

MFCxx.LIB (combined,MFCxx.DLL)

MFCSxx.LIB (static)

Unicode Debug:

MFCxxUD.LIB (core,MFCxxUD.DLL),

MFCOxxUD.LIB (OLE,MFCOxxUD.DLL),

MFCDxxUD.LIB (database,MFCDxxUD.DLL),

MFCNxxUD.LIB (network,MFCNxxUD.DLL),

MFCSxxUD.LIB (static)

Unicode Release:

MFCxxU.LIB(combined,MFCxxU.DLL),

MFCSxxU.LIB (static)

上面的LIB文件除了MFCSxx(D、U、UD).LIB以外都是導入庫。

MFC動態鏈接庫版本也需要靜態鏈接一些文件,這些文件就放在MFCSxx(D、U、UD).LIB中。例如包含_tWinMain的

appmodul.cpp。

6、結束語

研究這些問題的動機是想弄清楚我們的程序是如何裝載、運行的。但是,由於Windows不是開源平台,我也只能研究到PE文件

(Windows上可執行文件的格式)。entry point、subsystem都是PE文件頭的一部分。

Windows在進入PE文件的 entry point 之前做了些什么,就看不到了,只能大概推測:應該是創建一個進程,裝載PE文件和

有需要的DLL,初始化C變量,然后從某個起點函數開始運行。不同的subsystem,應該有不同的起點。調用這個起點函數

應該傳入PE文件的entry point地址。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM