使用VC++的編譯器建立最小的映象檔案(DLL/EXE)[譯]
一、序
本文通過描述一些方法來告訴你如何打造一個最小的映象檔案(DLL/EXE)。這些方法包括:
1)剔除C執行時Stub;
2)編譯器(cl.exe)和連結器(link.exe)的一些引數設定。
如題,這裡所指的編譯器及連結器我主要集中在MSVC6上(這些方法通常也適用於MSVC5)。當一些出現在這裡的觀念在應用於其它開發環境中的命令列引數及#pragmas出現明顯差異時,請參考您的環境文件。
二、拋開C執行時(C-Runtime)
C執行時是一個專為程式設計師準備的函式庫。這些函式是獨立於平臺的,並且它擔當了一個位於你程式與作業系統之間的抽像層角色。雖然這些函式都是組合語言所編,但它在某一方面,會為我們的程式帶來一些負面影響:
1)BUG。儘管大多數的C執行時函式都測試的很好,但是也有一些在您引入這些函式到您程式中時,可能會帶來更多的Bug;
2)它會佔用程式空間。為了使用C執行時函式,您的應用程式必須包含C執行時的程式碼,或是根據你的指示僅呼叫一個共享的DLL。一個通常的動作是,編譯器在編譯程式碼時會把C執行時函式的程式碼塞到你的程式中(這就是C執行時Stubs);
3)這個抽像層並不是比作業系統提供的操作更簡單,它僅僅是能跨平臺而已。其實,多數的任務都能直接使用作業系統層提供的API以更少的程式碼量來完美的演繹完成;
4)使用C執行時也同樣會犧牲由作業系統帶來的更多的功能,犧牲建立一個應用程式的更簡潔、更多的可提升效能的潛力。
如你和我一樣,無法接受如上的折衷,那麼就應該做幾個完全地去除C執行時的工作。如下:
1)停止不再使用C執行時函式。但是,還有一些以內部形式存在的函式您可繼續使用(字串及記憶體操作)。當然,您也可以直接地使用作業系統提供的等價的API函式。但是你要做的,更多的工作是替換那些作業系統沒有提供等價的服務的函式;
2)實現幾個C/C++編譯器假定存在的C執行時函式。如C++模組可能是必需的new、delete及_purecall操作。與此同時,也要為程式提供一個入口函式(EntryPoint)。什麼是入口函式?入口函式就是作業系統在載入程序後,第一個執行您程式程式碼的入口點。在我們還沒有去掉C執行時的時候,那個操作是由它來自動定位我們提供的main(console)
3)為了生成不依賴於C執行時環境的目標檔案,您的一些編譯器的開關可能需要改變;
4)為了防止連結器把C執行時的函式庫包含進來,您可能需要改變連結器的一些設定;
5)注意:記住C執行時的啟動程式碼主要負責初始化全域性物件。不要在已脫離C執行時環境下中使用需要初始化的全域性物件,否則應用程式可能會認為它們是沒有初始化的(例如全域性物件的建構函式)。
三、一些可繼續使用的函式
下列以編譯器內在形式存在的函式是可用的。注意儘管這些函式是以內在形式存在的,也可能不是像那些庫函式一樣是最優化的。這意味著您可編寫更高效的替代方案。
lmemcmp
lmemcpy
lmemset
lstrcmp
lstrcpy
lstrlen
lstrcat
lstrset
四、必需的函式
毫無疑問,C++編譯器需要您實現__purecall、new和delete。如果您開啟了C++異常處理可能需要更多。我不會教你怎麼寫那些程式碼,您只能從兩個方案中選其一:
1)不要使用C++異常處理;
2)找到那些已實現異常處理的*.obj目標檔案,然後連結到您的工程中。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__purecall、new和delete簡單實現如下:
void* __cdecl operator new ( unsigned int cb )
{
return HeapAlloc( GetProcessHeap(), 0, cb );
}
void __cdecl operator delete ( void* pv )
{
if ( pv )
HeapFree( GetProcessHead(), 0, pv );
}
extern "C" int _cdecl _purecall( void )
{
return 0;
}
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
另外,在新增上面的函式之後,將要為您的應用程式提供一個新的入口點。一個應用程式典型的啟動是由作業系統呼叫函式main/WinMian/DllMain。實際上,那些函式是由C執行時的入口點呼叫的。下面就是C執行時入口點函式的原型及名稱:(它們內部的實現過程和MASM32的入口點程式碼幾乎一樣,學過MASM32的一定會知道如何實現下面的函式程式碼^_^)
EXTERN_C int WINAPI mainCRTStartup();
EXTERN_C int WINAPI WinMainCRTStartup();
EXTERN_C BOOL WINAPI _DllMainCRTStartup(
HINSTANCE hInstDll, // handle to the DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved, // reserved
);
五、應用程式的結束
通常,在我們的程式碼執行到離開main(或是WinMain)函式時,應用程式將結束。導致這個原因的是上面的函式預設實現了:在我們的主函式執行完時呼叫了作業系統的API函式ExitProcess(MASM32的FANS們會心一笑)。當然如果您堅決不呼叫ExitProcess也行,那麼您的應用程式這時將不會根據您的指示而結束,它會一直等到——當它所有的執行緒完全地關閉時才會善罷甘休結束。
六、一個例項
自己手工實現一個如C執行時入口點程式碼一樣的入口點函式是非常有用的,例如偵錯程式。如下:
EXTERN_C int WINAPI WinMainCRTStartup()
{
HINSTANCE hInstance = GetModuleHandle(NULL);
LPSTR lpszCmdLine = GetCommandLine();
int r = WinMain(hInstance,NULL,lpszCmdLine,SW_SHOWDEFAULT);
ExitProcess(r);
return r; // this will never be reached.
}
七、編譯器開關
下面的那張表格描述了MSVC++6編譯器應該設定的確保成功編譯的開關:
開關 |
動作 |
說明 |
/GX |
刪除 |
這個開關激活了需要涉及到那些需要展開堆疊操作的函式的C++異常處理。 |
/GZ |
刪除 |
這個開關激活了一些高階的C執行時除錯特性。當這個特性啟用後,連結器將會搜尋_chkstk的呼叫。 |
/Oi (第一個是大寫的字母o而不是羅馬數字O) |
新增 |
新增這個開關可確保編譯器內在形式的函式啟用。 |
/Zl(大寫的Z和小寫的L) |
新增 |
通常編譯器會嵌入一個“defaultlib”來引用.obj檔案內的C執行時,這個開關確保deafultlib不會寫入到產生的目標檔案(*.obj)內。 |
八、連結器開關
如果編譯器已正確配置的話,下面的開關是可選的。但是,如果剛好工程中有一個obj檔案漏網的話,C執行時的入口點程式碼可能會被呼叫。當然,如查你不放心的話,那麼,下面的一個或多個開關你可能需要設定:
開關 |
動作 |
說明 |
/nodefaultlib |
新增 |
如果您在編譯器開關中已使用了/Zl,這個開關可不需設。正如編譯器的開關說明一樣,這個開關的意思是忽略預設庫。如果你使用的第三方函式庫或是一些舊的obj檔案中仍然包含了一個defaultlib,除非你使用下面的開關,否則連結器將會忽略掉你定義的入口點。 |
/entry:function |
新增 |
如果你希望使用一個非標準的入口點函式名稱,那麼這個開關你就要使用。如果你需要連結一些第三方的函式庫或是目的碼中包含了defaultlib的指示的話,這將是一個不錯的想法!否則若給了一半的機會與連結器,只要它能找到它,將會使用C執行時的函式庫入口點。 |
/opt:nowin98 |
新增 |
在windows98的平臺下,MSVC6連結器將會預設的為PE檔案分配4KB左右的節對齊方式用來優化載入速度的時間。如果啟用這個開關,將會對非常小的工程受益,控制PE的大小約在16KB左右。 |
九、更多的MSVC6連結器設定
微軟的連結器早在6.0版本之前,產生的所有PE映象的檔案標準節對齊都是512位元組。這在6.0開始,為了優化98下的載入速度,對齊將改為4KB左右。但是也為了相容原因,98載入檔案也必須支援舊的對齊方式(但是那麼做將會犧牲效率),並且,如果你的目標機器是NT的話,你可以使用舊的512位元組不會浪費你一絲效率。在程式碼中嵌入連結器開關的程式碼行如下:(第二行的意思,在.C的檔案中嵌入一個命令到連結器的選項)
// linker options can be embedded directly in .cpp code thus:
#if defined(_MSC_VER) && _MSC_VER >= 1200
#pragma comment(linker, "/OPT:NOWIN98" )
#endif
由於本人英文較差,如有不正之處請加以指正。多謝大家棒場,我會繼續為大家獻上更多的好文。