動態鏈接庫開發說明

1 基本概念    1

1.1 一個簡單的例子    1

1.1.1 新建一個VC++項目    1

1.1.2 添加源文件    3

1.1.3 輸入源代碼    6

1.1.4 __declspec(dllexport)    7

1.1.5 WINAPI    7

1.1.6 導出符號    7

1.1.7 DEF文件    8

1.2 調用動態庫    9

1.2.1 隱式鏈接    9

1.2.2 顯式鏈接    10

1.3 導出數據    11

1.3.1 隱式鏈接    11

1.3.2 顯式鏈接    12

1.4 導出類    12

1.4.1 成員類    12

1.4.2 導出模板類    13

1.4.3 內聯成員函數    13

1.4.4 友元函數    14

1.4.5 嵌套類    14

1.4.6 靜態成員變量    15

1.4.7 查看導出    15

1.5 導入類    16

1.5.1 內聯成員函數    17

2 MFC Regular DLL    18

2.1 三種DLL    18

2.1.1 non-MFC Win32 DLL    18

2.1.2 MFC Regular DLL    18

2.1.3 MFC Extension DLL    18

2.2 模塊狀態    18

2.3 InitInstance    19

2.4 AfxGetApp    20

2.5 PreTranslateMessage    20

2.6 OnIdle    21

3 MFC Extension DLL    22

3.1 顯式鏈接    22

3.2 查找資源    22

3.3 代碼解析    23

 

 

1 基本概念

1.1 一個簡單的例子

下面將使用VC++創建一個動態鏈接庫文件。這個文件將導出兩個函數StringReverseAStringReverseW,前者將一個ANSI字符串逆序,後者將一個Unicode字符串逆序。

1.1.1 新建一個VC++項目

對於VC++6.0而言,項目類型請選擇Win32 Dynamic-Link Library。輸入項目名稱後,單擊"OK"按鈕。

在接下來的界面裏,選擇"An empty DLL project",然後單擊"Finish"按鈕。

在接下來的界面裏單擊"OK"按鈕完成項目創建。

對於VC++9.0(即VC++2008)而言,項目類型請選擇Win32。輸入項目名稱後,單擊"確定"按鈕。

在接下來的界面裏,請選擇"應用程序設置"下的"DLL"和"空項目"。單擊"完成"按鈕完成項目創建。

1.1.2 添加源文件

對於VC++6.0而言,在Workspace 窗口的 FileView 選項卡內,右鍵單擊"Test files",在右鍵菜單裏單擊【Add Files to Project...】菜單項

輸入源文件名後,單擊"OK"按鈕

彈出對話框裏詢問是否在項目裏增加Test.c這個文件的引用。請單擊"是"按鈕。

此時鼠標雙擊Test.c。因爲這個文件還不存在,VC++6.0會提示是否創建,請單擊"是"按鈕。

對於VC++9.0而言,在解決方案資源管理器裏,右鍵單擊"Test",在右鍵菜單裏單擊【添加】【新建項】菜單項。

接下來的界面內,請選擇"C++文件(.cpp)",並輸入源文件名Test.c,然後單擊"添加"按鈕。完成Test.c文件的添加和創建。

1.1.3 輸入源代碼

Test.c裏輸入如下源代碼:

#include <windows.h>

 

/***************************************************************************\

將一個 Unicode 字符串逆序

\***************************************************************************/

__declspec(dllexport) wchar_t* WINAPI StringReverseW(wchar_t*wzStr)

{

if(wzStr)

{

int p1 = 0;

int p2 = wcslen(wzStr) - 1;

wchar_t t;

 

while(p1 < p2)

{

t = wzStr[p1];

wzStr[p1++] = wzStr[p2];

wzStr[p2--] = t;

}

}

return wzStr;

}

 

/***************************************************************************\

將一個 ANSI 字符串逆序

\***************************************************************************/

__declspec(dllexport) char* WINAPI StringReverseA(char*szStr)

{

if(szStr)

{

int nLenA = strlen(szStr) + 1;

int nLenW = MultiByteToWideChar(CP_ACP,0,szStr,nLenA,NULL,0);

wchar_t*pStrW = (wchar_t*)malloc(nLenW * sizeof(wchar_t));

 

MultiByteToWideChar(CP_ACP,0,szStr,nLenA,pStrW,nLenW);

StringReverseW(pStrW);

WideCharToMultiByte(CP_ACP,0,pStrW,nLenW,szStr,nLenA,NULL,NULL);

free(pStrW);

}

return szStr;

}

1.1.4 __declspec(dllexport)

__declspec(dllexport)修飾符用來導出函數StringReverseAStringReverseW。它還可以導出變量和類,這個後面介紹。

1.1.5 WINAPI

WINAPI 其實就是__stdcall。以StringReverseA爲例,調用它時,參數szStr將被壓入棧中,從StringReverseA返回時,參數szStr需要出棧。__stdcall表示由StringReverseA自己執行出棧操作。假如將__stdcall去掉或換爲__cdecl,則由調用StringReverseA的函數負責執行出棧操作。說了這麼多,最重要的是:某些語言,如VB6.0只支持__stdcall,所以爲了讓這個dll被儘可能多的編程語言支持,請使用WINAPI

1.1.6 導出符號

現在可以編譯程序,生成Test.dll了。使用eXeScope6.30打開Test.dll,可以看到Test.dll確實導出了兩個函數。請注意:每個導出函數都有一個序號,它是一個正整數。

不過有意思的是:導出函數的名稱並不是StringReverseAStringReverseW,而是_StringReverseA@4_StringReverseW@4

如果把Test.c改名爲Test.cpp,則導出的名稱更爲複雜。請參考下圖:

這是什麼原因呢?因爲Test.c的擴展名爲cVC++使用C編譯器進行編譯。Test.cpp的擴展名爲cppVC++使用C++編譯器進行編譯。C++爲了實現函數重載,編譯時會根據參數類型和個數對函數名進行再次命名。

1.1.7 DEF文件

如何防止VC++編譯器生成dll時將導出函數名更改掉?答案就是使用模塊定義文件。請在VC++項目裏增加模塊定義文件Test.def。這個文件名可以是1.defA.def……只要擴展名是def即可。編輯Test.def,使其內容如下:

EXPORTS

StringReverseA

StringReverseW

上述內容表示:導出函數StringReverseAStringReverseW。此時,這兩個函數前面的__declspec(dllexport)修飾符將不再需要。

DEF文件的功能還有很多,具體請參考MSDN

1.2 調用動態庫

生成的動態庫文件可以被多種編程語言使用。限於篇幅下面僅介紹VC++如何調用動態庫。

1.2.1 隱式鏈接

編譯動態庫文件時,同時會生成Lib文件。使用動態庫的VC++程序可以鏈接這個Lib文件,這就是隱式鏈接。可參考的代碼如下:

#include <windows.h>

#include <stdio.h>

 

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    

#pragma comment(lib,"D:/VC6/Test/Debug/Test.lib")

 

void main()

{

char szStr[] = "隱式鏈接動態庫";

puts(StringReverseA(szStr));

}

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    是函數聲明。修飾符__declspec(dllimport)表示這是一個導入函數。去除這個修飾符不影響程序的編譯、運行,但有了__declspec(dllimport)之後,生成的代碼更小,運行更快。

注意:對於C++程序而言,可能需要這樣聲明函數:

extern "C"

{

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    

}

extern "C" 的作用是:告訴C++編譯器連接時不要以C++語法修改StringReverseA的名稱。爲什麼說是"可能"需要extern "C"呢?這與dll的編譯有關係。如果使用C編譯器編譯dll,則需要extern "C";如果使用C++編譯器編譯dll,則不需要extern "C"

#pragma comment(lib,"D:/VC6/Test/Debug/Test.lib")表示鏈接的時候使用D:\VC6\Test\Debug\Test.Lib文件。就是編譯動態庫時產生的那個Lib文件。

採用隱式鏈接,運行程序的時候動態庫文件首先被加載至內存。系統如何定位dll文件呢?其搜索順序爲:exe所在目錄、當前目錄(GetCurrentDirectory)、System32目錄(GetSystemDirectory)、Windows目錄(GetWindowsDirectory)、環境變量PATH指定的目錄。不用記這麼多,最保險的做法就是將dllexe放在同一文件夾下。如果爲了多個exe程序共享一個dll,請將這個dll文件複製到System32目錄下。

1.2.2 顯式鏈接

顯式鏈接可以靈活控制動態庫文件的加載、卸載。其使用步驟如下:

1、使用LoadLibrary函數載入動態庫文件至內存;

2、使用GetProcAddress函數獲得導出函數的地址;

3、調用導出函數;

4、使用FreeLibrary卸載動態庫文件。

可參考如下代碼:

#include <windows.h>

#include <stdio.h>

 

void main()

{

HINSTANCE hDll = LoadLibrary("Test.dll"); //載入動態庫文件

if(hDll)

{//載入成功

char* (WINAPI*pfn)(char*szStr) = NULL; //聲明一個函數指針

//獲得函數StringReverseA的指針

#ifdef __cplusplus

(FARPROC&)pfn = GetProcAddress(hDll,"StringReverseA");

#else

(FARPROC)pfn = GetProcAddress(hDll,"StringReverseA");

#endif

if(pfn)

{//成功獲得函數指針

char szStr[] = "顯式鏈接動態庫";

pfn(szStr); //調用函數,等價於(*pfn)(szStr)

puts(szStr);

}

FreeLibrary(hDll); //卸載動態庫文件

}

}

需要說明的是

1LoadLibrary("Test.dll")在載入Test.dll時是有搜索順序的:首先在exe所在目錄查找,然後在當前目錄(GetCurrentDirectory)下查找,然後在System32目錄下查找……具體請參考MSDN幫助;

2、注意獲得函數指針的C代碼和C++代碼是不同的,它們通過#ifdef __cplusplus這個條件編譯語句來區分。或者使用C/C++通用的代碼:

typedef char* (WINAPI*STRINGREVERSEA)(char*szStr);

STRINGREVERSEA pfn = (STRINGREVERSEA)GetProcAddress(hDll,"StringReverseA");

3GetProcAddress的第二個參數可以指定函數名,如:"StringReverseA"。還可以指定爲序號,如:GetProcAddress(hDll,(LPCSTR)1)GetProcAddress(hDll,MAKEINTRESOURCEA(1))。其中1表示導出函數的序號爲1GetProcAddress如何區分第2個參數是名稱還是序號?對於字符串而言,首地址是一定大於0xFFFF的,而序號必須小於等於0xFFFF。這就是判斷的依據。使用序號定位導出函數,效率上會高一些,但是這樣的代碼不利於閱讀和維護;

4、關於FreeLibrary,需要說明的是:不能自己釋放自己。如下面的函數在Test.dll內,其意圖是自己釋放自己。實際上它是行不通的:

void FreeMyself(HINSTANCE hDll)

{

FreeLibrary(hDll);

}

1.3 導出數據

dll導出數據很簡單,下面是一個示例:

__declspec(dllexport) int nDataInDll;

或者

extern "C"

{//C++編譯時,防止重命名導出符號nDataInDll

__declspec(dllexport) int nDataInDll;

}

或者使用DEF文件,其內容如下

EXPORTS

nDataInDll DATA

1.3.1 隱式鏈接

客戶端程序隱式鏈接dll時,使用導出數據很簡單。首先是按下列語法聲明變量,然後就可以使用了。

extern "C"    //是否使用extern "C"需要根據實際情況而定

{

__declspec(dllimport) int nDataInDll;

}

1.3.2 顯式鏈接

客戶端程序顯式鏈接dll時,使用導出數據稍顯麻煩,其代碼如下:

HINSTANCE hDll = LoadLibrary("Test.dll"); //載入動態庫文件

if(hDll)

{//載入成功

int* nDataDll = NULL;

#ifdef __cplusplus

(FARPROC&)nDataDll = GetProcAddress(hDll,"nDataInDll");

#else

(FARPROC)nDataDll = GetProcAddress(hDll,"nDataInDll");

#endif

//使用數據

... ... ...

FreeLibrary(hDll); //卸載動態庫文件

}

注意:GetProcAddress獲得的是數據的地址。

1.4 導出類

導出類的語法有兩種。方法1是定義類的時候同時定義導出,方法2是先聲明類導出,再定義類。方法2比方法1靈活,但有時會有限制。

方法1

class __declspec(dllexport) CTest

{

public:

int        m_nValue;

CObj    m_obj;

};

方法2

//類聲明,說明是一個導出類

class __declspec(dllexport) CTest;    

class CTest

{

public:

int        m_nValue;

CObj    m_obj;

};

導出類的實質其實就是把類的成員函數給導出了。

1.4.1 成員類

以上面的代碼爲例,實例化CTest時需要構造m_obj。因此CObj也必須被導出,否則編譯的時候會產生警告,客戶程序可能無法正常構造CTest類(Debug版正常,Release版分配內存但不調用構造函數)。

1.4.2 導出模板類

首先看下面的代碼

template <class TYPE>

class CTemplate

{

public:

CTemplate() { a = 0; }

public:

TYPE a;

};

 

template class __declspec(dllexport) CTemplate<int>;

template class __declspec(dllexport) CTemplate<double>;

 

class __declspec(dllexport) CUseTemplate

{

public:

CTemplate<int> i;

CTemplate<double> d;

};

導出CUseTemplate時,CTemplate<int>CTemplate<double>也應該被導出。

注意:類模板是無法導出的,如下面的代碼無法導出CTemplate

template <class TYPE>

class __declspec(dllexport) CTemplate

{

public:

CTemplate() { a = 0; }

public:

TYPE a;

};

如果不想導出模板類,請修改成員變量爲指針類型。這樣的話,成員變量的構造、析構將在DLL內完成,而不是在客戶程序裏完成。

class __declspec(dllexport) CUseTemplate

{

public:

CTemplate<int>* pi;

CTemplate<double>* pd;

};

1.4.3 內聯成員函數

內聯函數相當於宏,編譯的時候用來替換源代碼,用以提高效率。一般它是不會被編譯成目標代碼的,但是一旦使用了__declspec(dllexport),編譯程序將會爲其生成一份目標代碼,並導出。

1.4.4 友元函數

友元函數的實質上還是一個函數,只不過它們是類的朋友,可以訪問類的私有成員變量。友元函數的導出,需要專門聲明。具體方法如下:

方法1

class __declspec(dllexport) CTest

{

public:

int m_nValue;

public:

//導出友元函數要專門聲明

friend __declspec(dllexport) CTest operator+(const CTest&a,const CTest&b);

};

方法2

//類聲明

class __declspec(dllexport) CTest;

//友元函數聲明

__declspec(dllexport) CTest operator+(const CTest&a,const CTest&b);

class CTest

{

public:

int m_nValue;

public:

friend CTest operator+(const CTest&a,const CTest&b);

};

1.4.5 嵌套類

下面是導出嵌套類的示例代碼:

class __declspec(dllexport) CTest

{

public:

//導出嵌套類,前面也要使用__declspec(dllexport)

class __declspec(dllexport) CNest

{

}

};

1.4.6 靜態成員變量

一個類被導出,則該類的所有靜態成員變量被當作變量導出。如:下面的代碼:

class __declspec(dllexport) CTest

{

public:

static int s_nValue; //s_nValue 被當作變量導出

};

1.4.7 查看導出

按下圖設置VC++6.0,使得編譯時生成map文件。

編譯如下代碼

class __declspec(dllexport) CTest

{

public:

void        SetValue(int v)        {m_Value = v;}

int        GetValue();

private:

int        m_Value;

public:

static int    s_nValue;

};

int CTest::s_nValue = 1;

int CTest::GetValue()                {return m_Value;}

編譯後查看 map 文件,提取包含 CTest 的函數或變量:

Publics by Value 

Rva+Base 

說明

?SetValue@CTest@@QAEXH@Z 

10001030 f i 

SetValue函數

??4CTest@@QAEAAV0@ABV0@@Z 

10001070 f i 

構造函數

?GetValue@CTest@@QAEHXZ 

100010b0 f 

GetValue函數

?s_nValue@CTest@@2HA 

1002ba30 

s_nValue

注意上表的第2列,f表示函數,i表示內聯。如果將 class __declspec(dllexport) CTest 中的 __declspec(dllexport) 去掉,重新編譯,則上表第2列包含 f i 的在map文件中不會再出現。

去掉CTest定義中的__declspec(dllexport)後,可以使用 DEF文件導出一些CTest成員函數,如:下面的DEF文件導出了函數CTest::GetValue和變量CTest::s_nValue。

EXPORTS

?GetValue@CTest@@QAEHXZ

?s_nValue@CTest@@2HA DATA

1.5 導入類

客戶端程序可以使用dll導出的類,定義類的時候需要__declspec(dllimport)

方法1

class __declspec(dllimport) CTest

{

public:

int m_nValue;

public:

//友元函數

friend __declspec(dllimport) CTest operator+(const CTest&a,const CTest&b);

};

方法2

class __declspec(dllimport) CTest; //類聲明

//下面這句話不再需要

//friend __declspec(dllimport) CTest operator+(const CTest&a,const CTest&b);

class CTest

{

public:

int m_nValue;

public:

//友元函數

friend CTest operator+(const CTest&a,const CTest&b);

};

1.5.1 內聯成員函數

考慮如下代碼:

class __declspec(dllimport) CTest

{

public:

int m_nValue;

public:

int GetValue()

{

return m_nValue;

}

};

現在的問題是:對於內聯函數GetValue,到底使用這裏的定義,還是使用dll導出的內聯函數?經測試發現:在__declspec(dllimport)存在的情況下,將使用dll導出的內聯函數(此時就不是內聯了);在刪除上面的__declspec(dllimport)之後,將使用上面定義的內聯函數。

 

2 MFC Regular DLL

2.1 三種DLL

使用VC++生成dll,共有三種類型。分別爲:non-MFC Win32 DLLMFC Regular DLLMFC Extension DLL

2.1.1 non-MFC Win32 DLL

上一章創建的 Win32 Dynamic-Link Library 即爲non-MFC Win32 DLL。它的特點是可以使用MFC,也可以不使用MFC。導出的函數、類涉及界面、Windows消息處理較少。它特別適合封裝數值計算、硬件控制等功能,此外它還可用於製作純資源dll(連接時指定 /NOENTRY 即可)。

它可以被多種編程語言所支持。

2.1.2 MFC Regular DLL

對於過多的界面處理,如果不借助MFC,則難度是比較大的。此時,可以使用MFC Regular DLL。它必須使用MFC共享庫,可以靜態連接也可以動態鏈接MFC共享庫,建議動態鏈接。

它也可以被多種編程語言所支持。

2.1.3 MFC Extension DLL

擴展的含義是對MFC類的擴展,它主要用於實現MFC類的派生類。如:如果覺得CButton類不太好,可以派生一個CButtonEx類,並在MFC Extension DLL裏實現、導出CButtonEx。任何MFC程序都可以直接使用這個CButtonEx

它必須使用MFC共享庫,而且必須動態連接MFC共享庫。只有MFC程序才能使用它。

2.2 模塊狀態

使用MFC Regular DLL要特別注意模塊狀態。在導出函數的第一行請增加如下代碼:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

它在棧上創建了一個對象。該對象記錄下當前的模塊狀態,然後設置當前模塊狀態爲本dll模塊狀態。導出函數返回的時候,對象被析構。析構時,會恢復當前模塊狀態。

如果不注意模塊狀態的切換,則本dll內的資源可能無法被調用,甚至程序會崩潰。

2.3 InitInstance

CWinApp派生類的InitInstance函數必須返回 TRUE,否則dll將加載失敗。

如果dll內部需要 ActiveX 控件或 OLE 拖放,請在InitInstance裏調用OleInitialize(NULL)ExitInstance裏調用OleUninitialize。因爲每個MFC模塊都有自己的模塊狀態,所以exe裏調用了AfxOleInitMFC Regular DLL是沒有什麼影響的,後者需要再次初始化COM庫。但是AfxOleInit這個函數在MFC Regular DLL裏沒有發揮應有的作用(可能是MFCBUG吧)。

請參考如下代碼:

class CDllApp : public CWinApp

{

public:

BOOL InitInstance()

{

AfxEnableControlContainer();

OleInitialize(NULL);

return TRUE;

}

int ExitInstance()

{

OleUninitialize();

return CWinApp::ExitInstance();

}

}theApp;

順便說一句OleInitializeCoInitializeCoInitializeEx的區別:CoInitializeExCoInitialize的擴展;OleInitialize將調用CoInitialize初始化COM庫,並且還做了其它一些工作,以支持OLE拖放等操作。

2.4 AfxGetApp

AfxGetApp() 等價於 AfxGetModuleState()->m_pCurrentWinApp,而AfxGetModuleState()則是當前模塊狀態。

所以AFX_MANAGE_STATE(AfxGetStaticModuleState());之後,AfxGetModuleState()就是AfxGetStaticModuleState(),即當前模塊狀態是本dll模塊狀態。AfxGetApp()將返回本dllCWinApp全局對象的地址(即theApp的地址)。

如果從exe調用dll的導出函數,而導出函數沒有調用AFX_MANAGE_STATE(AfxGetStaticModuleState()),則AfxGetModuleState()將是AfxGetAppModuleState(),即當前模塊狀態是exe模塊狀態。AfxGetApp()將返回exeCWinApp全局對象的地址。如果exe不是MFC程序,而是VB6.0VC#程序,則AfxGetAppModuleState的返回值是不能使用的。

2.5 PreTranslateMessage

雖然MFC Regular DLL派生了CWinApp類,並有一個theApp全局對象。但它不包含CWinApp::Run機制,主消息由exe負責接收、分發。如果DLL 生成了無模式對話框或有自己的主框架窗口,則它應該導出函數來調用 PreTranslateMessageexe程序需要調用這個導出函數。示例代碼如下:

DLL端需要導出函數,調用AfxGetApp()->PreTranslateMessage

__declspec(dllexport) BOOL DllPreTranslateMessage(MSG* pMsg)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState()); //切換模塊狀態

return AfxGetApp()->PreTranslateMessage(pMsg);

}

exe端需要調用DLL的導出函數

class CTestApp : public CWinApp

{

public:

BOOL PreTranslateMessage(MSG* pMsg)

{

if(DllPreTranslateMessage(pMsg))

{

return TRUE;

}

return CWinApp::PreTranslateMessage(pMsg);

}

... ... ...

}theApp;

如果不這麼做,則dll內部的非模態對話框PreTranslateMessage函數不會被執行,對話框內按Tab鍵也無法切換焦點。

如果exe不是MFC程序,而是VB6.0VC#程序,該如何處理PreTranslateMessage?這個問題需要再進行深入研究。此時,非模態對話框內按Tab鍵無法切換焦點,該如何處理?估計得使用鍵盤鉤子了……

2.6 OnIdle

一個標準的MFC程序裏,主窗口菜單項、工具欄的顯示更新,以及臨時對象的銷燬是在應用程序類的OnIdle裏進行處理的。如果MFC Regular DLL裏有框架窗口(CFrameWnd),並有菜單、工具欄,則它也需要處理OnIdle。方法與PreTranslateMessage的處理相同,其代碼如下:

DLL端需要導出函數,調用AfxGetApp()->OnIdle

__declspec(dllexport) void DllOnIdle(LONG lCount)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState()); //切換模塊狀態

AfxGetApp()->OnIdle(lCount);

}

exe端需要調用DLL的導出函數

class CTestApp : public CWinApp

{

public:

BOOL OnIdle(LONG lCount)

{

DllOnIdle(lCount);

return CWinApp::OnIdle(lCount);

}

... ... ...

}theApp;

如果exe端不是MFC程序,則在dll內部需要定時調用如下代碼:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

AfxGetApp()->OnIdle(0);    //更新菜單、工具欄

AfxGetApp()->OnIdle(1);    //刪除臨時對象

可以把這幾行代碼放在導出函數的最前面。

 

 

3 MFC Extension DLL

如前所言,MFC Extension DLL主要用於實現MFC類的派生類。如:如果覺得CButton類不太好,可以派生一個CButtonEx類,並在MFC Extension DLL裏實現、導出CButtonEx。任何MFC程序都可以直接使用這個CButtonEx。當然是可以在MFC Extension DLL裏創建一個非模態對話框的,但它的PreTranslateMessage同樣不會被主動執行。也就是說MFC Extension DLL的主要功能不是處理界面、Windows消息,而是擴展MFC已有的類。

MFC Extension DLL必須使用MFC共享庫,而且必須動態連接MFC共享庫。只有MFC程序才能使用它。

順便說一句:MFC Extension DLL不再像MFC Reguler DLL那樣需要切換模塊狀態。

3.1 顯式鏈接

大多數情況下,MFC Extension DLL都是隱式連接的,因爲它導出的基本上都是擴展MFC的類。但顯式鏈接也是可行的,不過使用的不是LoadLibrary,而是AfxLoadLibrary。卸載的函數也由FreeLibrary改爲AfxFreeLibrary

3.2 查找資源

代碼CDialog(_T("DlgRes")).DoModal();用於顯示一個對話框。爲了顯示這個對話框,需要查找對話框資源"DlgRes"。查找資源的順序如下圖所示:

即:首先在本模塊(MFC Regular DLLEXE)裏查找,然後在各個MFC Extension DLL裏查找,最後在MFC42.DLL裏查找。

注意:如果是MFC Regular DLL,則默認情況下不會在各個MFC Extension DLL裏查找資源。需要做如下處理後才行:

1MFC Extension DLL導出一個函數

__declspec(dllexport) void WINAPI ExtFunc()

{

new CDynLinkLibrary(ExtDLL);

}

2、在MFC Regular DLLInitInstance函數裏導入調用該函數

BOOL CRegDllApp::InitInstance()

{

{//

AFX_MANAGE_STATE(AfxGetStaticModuleState());

ExtFunc();

}

return CWinApp::InitInstance();

}

現在MFC Regular DLL裏的代碼CDialog(_T("DlgRes")).DoModal();在執行時就會在MFC Extension DLL裏查找資源"DlgRes"了。

3.3 代碼解析

MFC模塊狀態是一個AFX_MODULE_STATE,它裏面有一個成員變量

CTypedSimpleList<CDynLinkLibrary*> m_libraryList;

它是exe模塊用到的所有的MFC Extension DLL鏈表。MFC Extension DLL是通過DllMain完成初始化的,其典型代碼如下:

static AFX_EXTENSION_MODULE TestDLL = { NULL, NULL };

 

extern "C" int APIENTRY

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)

{

if (dwReason == DLL_PROCESS_ATTACH)

{

if (!AfxInitExtensionModule(TestDLL, hInstance))

return 0;

new CDynLinkLibrary(TestDLL);

}

else if (dwReason == DLL_PROCESS_DETACH)

{

AfxTermExtensionModule(TestDLL);

}

return 1; // ok

}

new CDynLinkLibrary(TestDLL)將創建一個CDynLinkLibrary對象,並將自己加入m_libraryList,其代碼如下:

CDynLinkLibrary::CDynLinkLibrary(AFX_EXTENSION_MODULE&,BOOL)

{

... ... ...

m_pModuleState->m_libraryList.AddHead(this);

... ... ...

}

C:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC\DLLINIT.CPPAfxFindResourceHandle函數說明了資源查找的順序:

HINSTANCE AFXAPI AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType)

{

HINSTANCE hInst;

... ...

if(!pModuleState->m_bSystem)

{//1

hInst = AfxGetResourceHandle();

}

... ...

for(CDynLinkLibrary* pDLL = pModuleState->m_libraryList;

pDLL != NULL;pDLL = pDLL->m_pNextDLL)

{//2

if(!pDLL->m_bSystem && pDLL->m_hResource != NULL

&& ::FindResource(pDLL->m_hResource, lpszName, lpszType))

{

return pDLL->m_hResource;

}

}

... ...

hInst = pModuleState->m_appLangDLL; //3

... ...

if(!pModuleState->m_bSystem)

{//4

hInst = AfxGetResourceHandle();

}

... ...

for(pDLL = pModuleState->m_libraryList; pDLL != NULL;

pDLL = pDLL->m_pNextDLL)

{//5

if(pDLL->m_bSystem && pDLL->m_hResource != NULL

&& ::FindResource(pDLL->m_hResource, lpszName, lpszType))

{

return pDLL->m_hResource;

}

}

... ...

return AfxGetResourceHandle();//6

}

注意:第2步與第5步的區別在於MFC Extension DLL一個是非系統的(!pDLL->m_bSystem),另一個是系統的(pDLL->m_bSystem)。MFC42.DLL就是一個系統的MFC Extension DLL

注意CDynLinkLibrary構造函數裏的代碼m_pModuleState->m_libraryList.AddHead(this);其中的m_pModuleStateexe的模塊狀態,即AfxGetAppModuleState()的返回值。默認情況下MFC Extension DLL中的new CDynLinkLibrary(TestDLL)不會加入MFC Regular DLLm_pModuleState->m_libraryList,在此情況下MFC Regular DLL需要的資源不可能在MFC Extension DLL裏找到。下面的代碼把MFC Extension DLL中的new CDynLinkLibrary(TestDLL)加入到MFC Regular DLLm_pModuleState->m_libraryList

BOOL CRegDllApp::InitInstance()

{

{//

AFX_MANAGE_STATE(AfxGetStaticModuleState());

ExtFunc();

}

return CWinApp::InitInstance();

}

 

發佈了113 篇原創文章 · 獲贊 46 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章