我們在用VC++編寫Windows程序的時候可能會發現一般可執行體(.EXE)的文件體積都比較大,於是非常羨慕那些使用Win32彙編編寫程 序的人,因爲他們編寫的可執行文件非常小。其實應用程序的體積是一方面,另外應用程序的部署環境則是需要注意的另一方面,這方面我深有體會,曾經使用 Visual Studio 2008編譯過一個C++的Win32程序,本地測試正常,但是部署到客戶機時,出現缺少什麼動態庫,於是還要安裝Visual C++ 2008可再發行組件包(Visual C++ 2005 Redistributable Package) ,這給軟件部署帶來了一定的麻煩,另外對於一個功能比較簡單的程序,安裝如此的組件包,可能心裏會不好受,我們希望對於一些比較簡單的應用程序可以直接調用系統提供的API,從而降低部署程序的複雜度。
其實對於VC++我們可以採用忽略所有默認庫的方式避免編譯器引入不必要的動態鏈接庫,當然你可以使用如下的預編譯宏。
#pragma comment(linker, "/nodefaultlib")
#pragma comment(linker, "/nodefaultlib")
實際上,我們還需要在屬性的 連接器->清單文件 將 生成清單 改爲 否;然後選擇 清單工具->輸入和輸出 將嵌入清單改爲否;在 C/C++中選擇代碼生成將緩衝區安全檢查改爲否(/GS-),否則編譯會出現一個錯誤,設定程序的主入口點。注意上述配置一般在Release下,生成文件也在Release下編譯鏈接,Debug可能無法使用,如果需要防止Debug模式編譯,可以使用如下宏命令:
#ifdef _DEBUG
#error Debug is disabled
#endif // _DEBUG
#ifdef _DEBUG #error Debug is disabled #endif // _DEBUG
另外對於函數入口的重新定義可以使用如下宏以代替屬性配置。
#pragma comment(linker,"/ENTRY:wWinMainCRTStartup")
#pragma comment(linker,"/ENTRY:wWinMainCRTStartup")
由於這裏指定使用的寬字符(Unicode)API調用,所以我們將入口點定義爲wWinMainCRTStartup,ANSI版建議定義爲 WinMainCRTStartup,另外Windows API有兩個版本的API接口,ANSI版和Unicode版,ANSI版主要是爲了兼容 Windows 98等舊系統,一般ANSI API編譯的程序體積比較小,但由於現在的基於NT的新的操作系統基本使用的Unicode API,相對而言,對於這些系統,Unicode API接口的速度要快於ANSI 接口的速度。
入口點做如下定義
#include
HINSTANCE g_hInst = NULL;
int WINAPI SimpleMain(VOID)
{
return 0;
}
VOID WINAPI wWinMainCRTStartup(VOID)
{
g_hInst = GetModuleHandle(NULL);
ExitProcess(SimpleMain());
}
#include HINSTANCE g_hInst = NULL; int WINAPI SimpleMain(VOID) { return 0; } VOID WINAPI wWinMainCRTStartup(VOID) { g_hInst = GetModuleHandle(NULL); ExitProcess(SimpleMain()); }
很多朋友可能還需要獲得程序的執行命令行,以獲得所需要的執行參數,我們可以使用GetCommandLine()這個API獲得。這裏提供一個我寫的分離命令的函數。
VOID WINAPI AnalyseCommandLine(const LPTSTR lpOriginal, LPTSTR lpParameter)
{
int i, j, k = 0, nCmdLen = lstrlen(lpOriginal);
for(i = nCmdLen; i>0; i--)
{
if(*(lpOriginal + i)==' ')
{
for(j=i+1; j < nCmdLen+1; j++)
{
*(lpParameter + k) = *(lpOriginal + j);
k++;
}
break;
}
}
*(lpParameter+k) = '/0';
}
VOID WINAPI AnalyseCommandLine(const LPTSTR lpOriginal, LPTSTR lpParameter) { int i, j, k = 0, nCmdLen = lstrlen(lpOriginal); for(i = nCmdLen; i>0; i--) { if(*(lpOriginal + i)==' ') { for(j=i+1; j < nCmdLen+1; j++) { *(lpParameter + k) = *(lpOriginal + j); k++; } break; } } *(lpParameter+k) = '/0'; }
具體使用如下:
LPTSTR lpCommand = NULL;
TCHAR zCmd[MAX_PATH];
lpCommand = GetCommandLine();
AnalyseCommandLine(lpCommand, zCmd);
LPTSTR lpCommand = NULL; TCHAR zCmd[MAX_PATH]; lpCommand = GetCommandLine(); AnalyseCommandLine(lpCommand, zCmd);
理論上這樣可以編譯鏈接了,實際上還有很多錯誤,主要是鏈接方面的。由於我們使用了純Windows API,所以不能使用memset,這時可以調用 FillMemory(RtlFillMemory)、ZeroMemory(RtlZeroMemory)等等,這時編譯會出現鏈接錯誤 是關於 _memset符號鏈接的,實際上我們並沒有使用memset,那這個錯誤是怎麼產生的呢?其實微軟重新定義了RtlFillMemory等API並使它們掛接到memset這個函數下,爲了我們能夠順利編譯,我們需要在自己的頭文件裏做如下處理。
#undef RtlFillMemory
#undef RtlZeroMemory
extern "C" NTSYSAPI BOOL NTAPI
RtlFillMemory ( VOID *Source1,DWORD Source2,BYTE Fill );
extern "C" NTSYSAPI BOOL NTAPI
RtlZeroMemory( PVOID Destination, SIZE_T Length);
#define memset(Destination,Fill,Length) RtlFillMemory((Destination),(Length),(Fill))
#undef RtlFillMemory #undef RtlZeroMemory extern "C" NTSYSAPI BOOL NTAPI RtlFillMemory ( VOID *Source1,DWORD Source2,BYTE Fill ); extern "C" NTSYSAPI BOOL NTAPI RtlZeroMemory( PVOID Destination, SIZE_T Length); #define memset(Destination,Fill,Length) RtlFillMemory((Destination),(Length),(Fill))
下面我們繼續我們的編譯,在鏈接這裏又出現錯誤。
error LNK2001: 無法解析的外部符號 __imp__InitCommonControls@0
error LNK2001: 無法解析的外部符號 __imp__InitCommonControlsEx@4
跟蹤後發現這兩個函數InitCommonControls和InitCommonControlsEx由COMCTL32.dll導出,參考網上的解決方案,加入lib庫。
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
#include #pragma comment(lib, "comctl32.lib")
錯誤依舊!經過仔細尋找發現在屬性配置裏 鏈接器->輸入 在附加依賴項裏填入 comctl32.lib,編譯,通過!
另外對於空間分配建議參考HeapAlloc、HeapFree等等API函數。
如果大家在操作過程中遇到什麼問題歡迎討論!