Windows Via C/C++ 讀書筆記 13
動態鏈接庫基礎DLL Basics
1. Overview
本章講DLL概念(比一些教程仔細得多),實現方式,並給一個簡單的DLL例子說明代碼如何構建。廢話少說,先上圖。這張圖說明了一個DLL由編寫到鏈接、被調用的全部過程。看這篇文章前,你至少要知道什麼是compile,什麼是link,都幹了些啥。
左邊的樹是DLL文件的生成過程:
1 編寫頭文件,包含了需要導出的函數 變量 類型等。
2 編寫CPP文件,包含了函數的實現。
3 compile出obj文件。
4-5 Link出一個.dll和一個.lib文件。.dll文件被.exe文件調用。.lib文件只在.exe文件通過隱式調用方式調用DLL文件的時候有用。本章的例子就是個隱式鏈接方式,還有一種是顯示鏈接通過API在程序運行時動態加載的方式,後面DLL高級部分會講到。Step9,exe鏈接的時候講這個lib文件爲什麼有用。Lib文件包含了dll導出的函數 變量等信息,幷包含這個dll文件的名字,當exe鏈接的時候,它知道它用到的函數不是自己實現的,是需要從外部導入的,通過lib文件可以知道哪些函數是從外部導入的,從哪個dll文件導入。
右邊樹是exe文件編譯鏈接過程。
前三步和dll文件類似,和普通exe文件的完全相同。
區別是:step6,頭文件和DLL導出聲明的頭文件是同一個文件,因此在compile的時候,exe程序已經完全知道了要調用的函數名稱,數據類型等信息。這些信息是compile出obj文件必需也僅需的。
Step 9,鏈接的時候必須鏈入lib文件,否則程序在link的過程中,會報無法找到函數實現的錯誤。因爲link的過程會遍歷所有的obj,嘗試尋找所有函數的實現部分。而這些函數都是從外部導入的,肯定是找不到的。當它看到lib文件後,會得知這些函數的實現信息,"哦,在dll中,不鏈接到exe代碼段,執行的時候從dll中映射過來。"。
Step10,程序啓動的時候尋找dll文件(尋找dll文件的順序各個操作系統微有不同,見msdn dll path)。找到後把代碼和變量映射到自己的地址空間,如果找不到,會彈一個窗口,"xx.dll找不到!"。它爲什麼會知道哪個dll找不到?Lib文件告訴它了。
2. DLL與進程地址空間
進程在調用DLL之前,需要把dll文件映射到自己的地址空間中,然後就像調用自己的代碼一樣調用dll。前面《Windows Via C/C++ 讀書筆記 11 Memory-Mapped Files(內存映射文件)》講了這部分內容。雖然dll只會被加載一次到Page File,然後被多個進程共享,但是它們之間的靜態變量是不會共享的(copy-on-write方式實現,見筆記11)。這是dll編寫者最常見的問題。
3. 構建一個簡單DLL
注意MYLIBAPI的定義,其實在h文件定義一次就足夠了。
extern "C" 表示生成的函數命名不要用c++的方式,c++會修改命名。不然,這個dll只能被c++程序使用,而不能被其它語言編寫的程序使用。
__declspec(dllimport) 告知dll編譯器需要導出,compiler會在obj文件裏面加入這些信息,link讀到這些信息然後生成dll;也告知exe編譯器,這些函數在dll中找到,不要在obj文件中找。
用dumpbin工具可以查看dll文件的導出段(export section),使用-exports開關。Dumpbin使用見筆記11。
/***************************************************************************
Module: MyLib.h
***************************************************************************/
#ifdef MYLIBAPI
// MYLIBAPI should be defined in all of the DLL's source
// code modules before this header file is included.
// All functions/variables are being exported.
#else
// This header file is included by an EXE source code module.
// Indicate that all functions/variables are being imported.
#define MYLIBAPI extern "C" __declspec(dllimport)
#endif
////////////////////////////////////////////////////////////////////////////
// Define any data structures and symbols here.
////////////////////////////////////////////////////////////////////////////
// Define exported variables here. (NOTE: Avoid exporting variables.)
MYLIBAPI int g_nResult;
////////////////////////////////////////////////////////////////////////////
// Define exported function prototypes here.
MYLIBAPI int Add(int nLeft, int nRight);
////////////////////////////// End of File /////////////////////////////////
In each of your DLL's source code files, you should include the header file as follows:
/***************************************************************************
Module: MyLibFile1.cpp
***************************************************************************/
// Include the standard Windows and C-Runtime header files here.
#include <windows.h>
// This DLL source code file exports functions and variables.
#define MYLIBAPI extern "C" __declspec(dllexport)
// Include the exported data structures, symbols, functions, and variables.
#include "MyLib.h"
////////////////////////////////////////////////////////////////////////////
// Place the code for this DLL source code file here.
int g_nResult;
int Add(int nLeft, int nRight) {
g_nResult = nLeft + nRight;
return(g_nResult);
}
////////////////////////////// End of File /////////////////////////////////
我的測試文件打印出的dumpbin結果,可以看到導出的變量g_nResult和函數Add。
E:/projects/dlltest/Debug>dumpbin -exports dlltest.dll
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file dlltest.dll
File Type: DLL
Section contains the following exports for dlltest.dll
00000000 characteristics
4A3B8423 time date stamp Fri Jun 19 20:27:15 2009
0.00 version
1 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
1 0 000110BE Add = @ILT+185(_Add)
2 1 00017150 g_nResult = _g_nResult
Summary
1000 .data
1000 .idata
2000 .rdata
1000 .reloc
1000 .rsrc
4000 .text
10000 .textbss