2 MFC DLL ——擴展的和正規的
MFC 的 AppWizard 可以讓我們創建 MFC 庫支持的兩種 DLL :擴展的 DLL 和正規的 DLL 。這兩種類型的區別是什麼呢?
說明: 當然, Developer Studio 也讓我們創建純的、與 MFC 庫無關的 Win32 DLL ,就像它讓我們創建與 MFC 庫無關的 Windows 程序一樣。
² 擴展 DLL 支持 C++ 接口,換句話說,該 DLL 可以導出整個類,客戶可以構造這些類的對象或從這些類進行派生。擴展 DLL 動態連接到 MFC 庫的 DLL 版本的代碼,因此,擴展 DLL 要求客戶程序被動態連接到 MFC 庫 (AppWizard 默認設置 ) ,並且客戶程序和擴展 DLL 要一致連接到 MFC DLL 的相同版本 (mfc42.dll 、 mfcd42.dll 等 ) 。擴展 DLL 很小,我們可以創建一個簡單的擴展 DLL ,大約 10KB 左右,它的裝載會很快。
² 如果我們需要一個 DLL ,並希望它可以被任何 Win32 編程環境 ( 包括 Visual Basic 5.0) 裝載,那麼我們必須使用正規 DLL 。這裏有一個很大的限制就是,正規 DLL 可以導出 C 風格的函數,但不能導出 C++ 類、成員函數或重載函數,因爲每一個 C++ 編譯器都有自己修飾名字的方法。不過,我們可以在正規 DLL 內部使用 C++ 類 ( 特別是 MFC 庫的類 ) 。
當我們創建 MFC 正規 DLL 時,我們可以選擇靜態連接或動態連接到 MFC 庫。如果選擇了靜態連接, DLL 將包括所有它需要的 MFC 庫代碼的拷貝,因此它可以獨立於 MFC 庫。一個典型的 Release 版本靜態連接的正規 DLL 大約爲 144KB 左右。如果選擇動態連接,大小可降到 17KB 左右,但必須保證適當的 MFC DLL 在目標機器上存在。
當我們指定 AppWizard 創建 DLL 或 EXE 的類型時,編譯器 #define 常量將按下表設置:
動態連接到共享的 MFC 庫
靜態連接到 MFC 庫
正規 DLL
_AFXDLL 、 _USRDLL
_USRDLL
擴展 DLL
_AFXEXT 、 _AFXDLL
不支持
客戶 EXE
_AFXDLL
沒有定義常量
如果我們查看 MFC 源代碼和頭文件,將會看到大量的這些常量的 #ifdef 語句。這表明庫代碼的編譯也是非常不同的,具體取決於我們產生工程時指定的類型。
2.1 共享的MFC DLL 和Windows DLL
² 如果我們使用共享 MFC DLL 選項創建一個 Windows 的 Debug 目標的程序,則該程序將動態連接到下列一個或多個 (ANSI)MFC DLL :
mfc42d.dll
核心 MFC 類
mfco42d.dll
ActiveX(OLE) 類
mfcd42d.dll
數據庫類 (ODBC 和 DAO)
Mfcn42.dll
Winsock 和 WinInet 類
² 當創建 Release 目標時,程序只是動態連接到 mfc42.dll 。對這些 MFC DLL 的連接都是通過導入庫隱式連接。我們也可以假定隱式連接到 Windows 裏的 ActiveX 和 ODBC 的 DLL ,這種情況下,我們可以認爲當裝入用 Release 目標創建的客戶時,所有這些 DLL 都被連接到了程序中,而不管客戶是否使用了 ActiveX 和 ODBC 特性。然而,實際情況並不是這樣。通過一些創造性的手段,當這些函數中的某一個首先被調用時, MFC 會顯示裝入 ActiveX 和 ODBC DLL( 通過調用 LoadLibrary) 。這樣客戶程序只是裝入自己所需要的 DLL 。
2.2 MFC 擴展DLL ——導出類
如果擴展 DLL 只包含被導出的 C++ 類,那麼我們可以很方便地創建和使用該 DLL 。創建 EX21A 示例程序 (P433) 的步驟顯示瞭如何讓 AppWizard 創建一個擴展 DLL 的框架。該框架只有 DllMain 函數,我們只要簡單地把 C++ 類加到工程裏。但有一件特殊的事情我們必須要做,即我們必須把宏 AFX_EXT_CLASS 加到類聲明中,如下所示:
view plaincopy to clipboardprint?
class AFX_EXT_CLASS Cstudent:public Cobject
class AFX_EXT_CLASS Cstudent:public Cobject
不僅對 DLL 工程中的 H 文件要作這樣的修改,對客戶程序使用的 H 文件同樣也要作修改。換句話說, H 文件對於客戶和 DLL 是一樣的。該宏根據相應條件會產生不同的代碼——在 DLL 裏導出類,在客戶程序裏導入類。
2.3 MFC 擴展DLL 資源搜索的順序
如果我們創建一個動態連接到 MFC 的客戶程序,許多 MFC 庫的標準資源 ( 錯誤信息字符串和打印預覽對話框模板等 ) 都被保存在 MFC DLL 裏 (mfc42.dll 、 mfcd42.dll 等 ) ,但應用程序也有自己的資源。當程序調用一個 MFC 函數 ( 如 Cstring::LoadString 或 Cbitmap::LoadBitmap) 時,框架首先搜索 EXE 的資源,然後搜索 MFC DLL 的資源 。
如果程序包含一個擴展的 DLL ,並且 EXE 需要一個資源,則搜索順序爲:首先是 EXE 文件,然後是擴展 DLL ,再是 MFC DLL 。例如我們有一個字符串資源 ID ,在所有資源中它是唯一的,則 MFC 庫將會找到該資源。如果在 EXE 文件和擴展 DLL 文件裏有重複的字符串 ID ,則 MFC 庫會裝入 EXE 文件裏的字符串。
如果擴展 DLL 裝入一個資源,則搜索序列爲:首先是擴展 DLL ,然後是 MFC DLL ,再是 EXE 。
說明: 如果需要的話,我們可以改變搜索序列。假定我們希望 EXE 代碼首先搜索擴展 DLL 的資源,則可以使用下面的代碼:
+ expand sourceview plaincopy to clipboardprint?
HINSTANCE hInstResourceClient=AfxGetResourceHandle();
//Use DLL's instance handle
AfxSetResourceHandle(::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString(IDS_MYSTRING);
//Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);
HINSTANCE hInstResourceClient=AfxGetResourceHandle();
//Use DLL's instance handle
AfxSetResourceHandle(::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString(IDS_MYSTRING);
//Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);
我們不能用 AfxGetInstanceHandle 代替 ::GetModuleHandle 。在一個擴展 DLL 裏, AfxGetInstanceHandle 返回的是 EXE 的實例句柄,而不是 DLL 的句柄。
2.4 如何使用MFC 擴展DLL
u 將一個獨立的類 MyClass ,做成一個擴展的 DLL
a) 首先製作一個單獨的功能簡單的類,內容如下:
+ expand sourceview plaincopy to clipboardprint?
// MyClass.h
class CMyClass
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
// MyClass.cpp
#include "StdAfx.h"
#include "MyClass.h"
CMyClass::CMyClass()
{
printf("CMyClass::CMyClass()/n");
}
void CMyClass::SetValue(int n)
{
printf("SetValue(int n)/n");
_a=n;
}
void CMyClass::GetValue()
{
printf("GetValue()/n");
printf("_a=%d/n",_a);
}
// MyClass.h
class CMyClass
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
// MyClass.cpp
#include "StdAfx.h"
#include "MyClass.h"
CMyClass::CMyClass()
{
printf("CMyClass::CMyClass()/n");
}
void CMyClass::SetValue(int n)
{
printf("SetValue(int n)/n");
_a=n;
}
void CMyClass::GetValue()
{
printf("GetValue()/n");
printf("_a=%d/n",_a);
}
b) 然後,運行 AppWizard 產生一個擴展的 DLL ,通過選擇 MFC AppWizard(dll)->MFC Extension DLL->Finish ,工程名爲 ex21a 。
c) 將 MyClass.h 和 MyClass.cpp 這兩個文件拷貝到剛纔創建的工程中,通過 Project->Add to Project->Files 。添加後,編輯 MyClass.h 文件如下:
+ expand sourceview plaincopy to clipboardprint?
// MyClass.h
// 擴展DLL使用AFX_EXT_CLASS宏導出類,
// 在_AFXDLL和_AFXEXT被定義時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_EXPORT
// #define AFX_CLASS_EXPORT __declspec(dllexport)
//class CMyClass
class AFX_EXT_CLASS CMyClass // 修改此處
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
// MyClass.h
// 擴展DLL使用AFX_EXT_CLASS宏導出類,
// 在_AFXDLL和_AFXEXT被定義時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_EXPORT
// #define AFX_CLASS_EXPORT __declspec(dllexport)
//class CMyClass
class AFX_EXT_CLASS CMyClass // 修改此處
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
d) 最後編譯工程,得到了我們需要的兩個文件 ex21a.dll 和 ex21a.lib 。
u DLL 測試客戶程序
a) 運行 AppWizard 產生一個 Win32 控制檯程序,通過選擇 Win32 Console Application-> 工程名爲 client-> 一個空白工程,然後再新建一個 C++ Source File ,文件名爲 client 。完成後,在 client.cpp 文件中添加如下測試代碼:
+ expand sourceview plaincopy to clipboardprint?
// client.cpp
#include <stdio.h>
#include <iostream>
#include "MyClass.h"
int main()
{
printf("main()/n");
int nVal;
nVal=100;
//測試使用我們導入的類
CMyClass mc;
mc.SetValue(nVal);
mc.GetValue();
system("pause");
return 0;
}
// client.cpp
#include <stdio.h>
#include <iostream>
#include "MyClass.h"
int main()
{
printf("main()/n");
int nVal;
nVal=100;
//測試使用我們導入的類
CMyClass mc;
mc.SetValue(nVal);
mc.GetValue();
system("pause");
return 0;
}
b) 修改設置,在 Project->Settings->General-> 在 Microsoft Foundation Classes 中選擇 Use MFC in a Shared DLL 。
c) 將 ex21a.dll 、 ex21a.lib 和 MyClass.h 三個文件拷貝到 client 工程目錄下,類似上述相同的方法,將 MyClass.h 添加到工程中,並且在 Project->Settings->Link ,在 Object/library modules 中添加 ex21a.lib( 多個 lib 用空格分開 ) 。
d) 修改 MyClass.h 文件的內容。當時我在調試的時候,出現了錯誤,需要小心。我們在擴展 DLL 工程中的 MyClass.h 文件中使用宏 AFX_EXT_CLASS 來聲明導出類,而同時要在 client 工程中的 MyClass.h 文件中使用宏 AFX_EXT_CLASS 來聲明導入類。但是在我編譯 client 工程的時候問題出現了:
error C2079: 'CMyClass' uses undefined class 'AFX_EXT_CLASS'
提示 AFX_EXT_CLASS 沒有定義,應該是少加頭文件了。在網上 g 一下找到了一些相同的問題貼:
http://www.eggheadcafe.com/software/aspnet/29755654/problem-using-self-built.aspx
http://msdn.microsoft.com/en-us/library/h5f7ck28.aspx (Extension DLLs)
http://msdn.microsoft.com/en-us/library/4fezhh3d.aspx (DLLHUSK Sample: Dynamically Links the MFC Library)
http://www.codeguru.com/Cpp/W-P/dll/article.php/c119 (Using one extension DLL in another)
解決方法如下,修改 MyClass.h 文件內容爲:
方法 1 :
+ expand sourceview plaincopy to clipboardprint?
// MyClass.h
// 客戶程序使用AFX_EXT_CLASS宏導入類,
// 在只定義了_AFXDLL時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_IMPORT
// #define AFX_CLASS_IMPORT __declspec(dllimport)
#include <afxext.h> // 缺少頭文件MFC extensions (可以在StdAfx.h中找到)
//class CMyClass
class AFX_EXT_CLASS CMyClass
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
// MyClass.h
// 客戶程序使用AFX_EXT_CLASS宏導入類,
// 在只定義了_AFXDLL時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_IMPORT
// #define AFX_CLASS_IMPORT __declspec(dllimport)
#include <afxext.h> // 缺少頭文件MFC extensions (可以在StdAfx.h中找到)
//class CMyClass
class AFX_EXT_CLASS CMyClass
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
方法 2 :
+ expand sourceview plaincopy to clipboardprint?
// MyClass.h
// 客戶程序使用AFX_EXT_CLASS宏導入類,
// 在只定義了_AFXDLL時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_IMPORT
// #define AFX_CLASS_IMPORT __declspec(dllimport)
//class CMyClass
//class AFX_EXT_CLASS CMyClass
class __declspec(dllimport) CMyClass //通過上述分析,直接使用__declspec(dllimport)
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
// MyClass.h
// 客戶程序使用AFX_EXT_CLASS宏導入類,
// 在只定義了_AFXDLL時,AFX_EXT_CLASS宏被定義爲:
// #define AFX_EXT_CLASS AFX_CLASS_IMPORT
// #define AFX_CLASS_IMPORT __declspec(dllimport)
//class CMyClass
//class AFX_EXT_CLASS CMyClass
class __declspec(dllimport) CMyClass //通過上述分析,直接使用__declspec(dllimport)
{
public:
CMyClass();
void SetValue(int n);
void GetValue();
private:
int _a;
};
e) 現在重新編譯沒問題了,可以得到如下的輸出結果,即可以在 client 程序中成功地調用我們在擴展 DLL 中導出的類了。
main()
CMyClass::CMyClass()
SetValue(int n)
GetValue()
_a=100
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/delphiwcdj/archive/2010/01/06/5142937.aspx