MFC 正規DLL
2.5 MFC 正規DLL ——CWinApp 派生類
當用 AppWizard 產生正規 DLL 時, DllMain 函數將出現在框架內,並且我們會得到一個 CWinApp 的派生類 ( 和一個該類的全局對象 ) ,就像 EXE 程序的情形一樣。我們可以通過重載 CWinApp::InitInstance 和 CWinApp::ExitInstance 函數獲得控制。然而大多數情況下,我們不用重載這兩函數。我們只要編寫 C 函數,然後用 __declspec(dllexport) 修飾符導出這些函數 ( 或者在工程的 DEF 文件里加入函數入口 ) 即可。
2.6 使用AFX_MANAGE_STATE 宏
當 mfc42.dll 作爲進程的一部分被裝入時,它把數據存放在一些可靠的全局變量裏。如果我們從一個 MFC 程序或擴展 DLL 中調用 MFC 函數,則 mfc42.dll 會知道如何代表調用進程去設置這些全局變量。然而,如果我們從一個正規 MFC DLL 中調用進入 mfc42.dll ,則全局變量並不同步,其後果不可預知。爲了解決這個問題,請在正規 DLL 所有導出函數的開始處,插入下面的代碼行:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
如果 MFC 代碼被靜態連接,則該宏不會有任何影響。
2.7 如何使用MFC 正規DLL
u 創建 MFC 正規 DLL
a) 運行 AppWizard ,選擇 MFC AppWizard(dll)->Regular DLL Using Shared MFC DLL ,工程名爲 ex21c 。
b) 在 ex21c.cpp 文件中加入導入的函數代碼:
+ expand sourceview plaincopy to clipboardprint?
// The one and only CEx21cApp object
CEx21cApp theApp;
extern "C" __declspec(dllexport) double Ex21cSquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Note!
TRACE("Entering EX21cSquareRoot/n");
if (d>=0.0)
{
return sqrt(d); // 添加math.h頭文件
}
AfxMessageBox("Can't take square root of a negative number.");
return 0.0;
}
// The one and only CEx21cApp object
CEx21cApp theApp;
extern "C" __declspec(dllexport) double Ex21cSquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Note!
TRACE("Entering EX21cSquareRoot/n");
if (d>=0.0)
{
return sqrt(d); // 添加math.h頭文件
}
AfxMessageBox("Can't take square root of a negative number.");
return 0.0;
}
c) 編譯工程,得到 ex21c.dll 和 ex21c.lib 兩個文件。
u 測試 DLL 的客戶程序
a) 創建一個空白的 Win32 控制檯程序,工程名爲 client ,添加如下測試代碼:
+ expand sourceview plaincopy to clipboardprint?
#include <stdio.h>
#include <iostream>
//聲明Ex21cSquareRoot爲一個導入函數
extern "C" __declspec(dllimport) double Ex21cSquareRoot(double d);
int main()
{
printf("input a number:");
double dInput,dOutput;
scanf("%lf",&dInput);
//測試使用導出的函數
dOutput=Ex21cSquareRoot(dInput);
printf("sqrt(%lf)=%lf/n",dInput,dOutput);
system("pause");
return 0;
}
#include <stdio.h>
#include <iostream>
//聲明Ex21cSquareRoot爲一個導入函數
extern "C" __declspec(dllimport) double Ex21cSquareRoot(double d);
int main()
{
printf("input a number:");
double dInput,dOutput;
scanf("%lf",&dInput);
//測試使用導出的函數
dOutput=Ex21cSquareRoot(dInput);
printf("sqrt(%lf)=%lf/n",dInput,dOutput);
system("pause");
return 0;
}
b) 將 ex21c.dll 和 ex21c.lib 這兩個文件拷貝到 client 工程目錄中,並且在 Project->Settings->Link ,在 Object/library modules 中添加 ex21c.lib( 多個 lib 用空格分開 ) 。
c) 編譯並測試,輸入 2 ,將輸出如下結果,即可以成功地調用正規 DLL 導出的函數。
input a number:2
sqrt(2.000000)=1.414214
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/delphiwcdj/archive/2010/01/06/5144940.aspx
使用 MFC 可以生成兩類 DLL : MFC 擴展 DLL 和常規 DLL 。常規 DLL 又分爲兩類:動態鏈接 (dynamically linked) 和靜態鏈接 (statically linked) 。關於這兩種 DLL 的區別和簡單的用法已經在前面的博文中總結過,本文主要針對以下三個方面進一步地討論關於這兩類 DLL 的用法和注意事項。
構建 DLL
在客戶程序中使用 DLL
DLL 與 MFC 和編譯器的兼容相關的一些常見問題
From: CodeGuru Visual C++ 編程精粹(wcdj借於圖書館發現此書翻譯的不是很好,將主要精髓整理如下)
--------------------------------------------------------------------------------
n 構建 DLL
通過 AppWizard 可以生成一個不完成任何實質性任務的 DLL 。新的 DLL 能編譯,但由於它沒有導出任何類或函數,所以仍然不能發揮作用。所以我們的任務就是:
² 給 DLL 添加功能,使之發揮作用;
² 修改客戶應用程序來運用這個 DLL ;
我們可以完成下面的工作:
1) 導出類
一旦結束了 AppWizard ,然後就可以從另一個項目添加 .cpp 和 .h ,來給 DLL 添加類,或者在當前項目中從頭開始創建它們。爲了導出類,在類聲明中添加“ __declspec(dllexport) ”,如下所示:
view plaincopy to clipboardprint?
class __declspec(dllexport) CMyClass
{
// 在此放置類聲明
};
class __declspec(dllexport) CMyClass
{
// 在此放置類聲明
};
如果創建的是 MFC 擴展 DLL ,可使用宏 AFX_EXT_CLASS :
view plaincopy to clipboardprint?
class AFX_EXT_CLASS CMyClass
{
// 在此放置類聲明
};
class AFX_EXT_CLASS CMyClass
{
// 在此放置類聲明
};
還有其他途徑來導出類,但以上辦法最容易。
2) 導出變量、導出常量和導出對象
如果不導出整個類,也可以讓 DLL 導出一個變量、常量或對象。導出一個變量或常量,按如下方式聲明:
view plaincopy to clipboardprint?
__declspec(dllexport) int MyInt;
__declspec(dllexport) extern const COLORREF MyColor=RGB(50,50,50);
__declspec(dllexport) int MyInt;
__declspec(dllexport) extern const COLORREF MyColor=RGB(50,50,50);
需要導出一個常量時,必須使用定義符 extern 。否則,會得到鏈接錯誤。
可以按相同的方式聲明和導出一個類對象:
view plaincopy to clipboardprint?
__declspec(dllexport) CRect MyRect(30,30,300,300);
__declspec(dllexport) CRect MyRect(30,30,300,300);
注意: 如果客戶程序識別這個類而且有自己的頭文件,則只能導出一個類對象。如果在 DLL 中創建一個新類,客戶程序不借助頭文件,就不能識別它。
當你導出一個變量或對象時,載入此 DLL 的每個客戶程序都將獲得自己的拷貝。於是,如果兩個不同的應用程序使用同一個 DLL ,一個應用程序所做的修改不會影響另一個應用程序。
務必記住: 只能導出處於 DLL 中具有全局作用域的對象和變量。當局部對象和變量越出作用域時,它們就會消亡。因此,如果 DLL 包含如下代碼,就不會正常工作:
view plaincopy to clipboardprint?
MyFunction()
{
__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;
}
MyFunction()
{
__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;
}
一旦對象和變量越出作用域,它們就不復存在了。
3) 導出函數
導出函數與導出對象或變量相似。只要將“ __declspec(dllexport) ”放到函數原型開頭:
view plaincopy to clipboardprint?
__declspec(dllexport) int SomeFunction(int);
__declspec(dllexport) int SomeFunction(int);
如果創建的是常規 DLL ,它將提供 C 寫成的客戶程序使用,則函數聲明應如下所示:
view plaincopy to clipboardprint?
extern "C" __declspec(dllexport) int SomeFunction(int);
extern "C" __declspec(dllexport) int SomeFunction(int);
注意: 如果創建的是一個動態鏈接到 MFC 代碼庫 DLL 的常規 DLL ,則必須插入宏 AFX_MANAGE_STATE ,作爲導出函數的首行。因此,函數定義應如下:
view plaincopy to clipboardprint?
extern "C" __declspec(dllexport) int AddFive(int x)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x+5;
}
extern "C" __declspec(dllexport) int AddFive(int x)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x+5;
}
說明: 在每個常規 DLL 中,這樣做沒有危害。如果將你的 DLL 切換到靜態鏈接,其中的宏只是無效而已。
注意: 只有 MFC 擴展 DLL 導出的函數纔可以讓參數和返回值使用 MFC 數據類型。
4) 導出指針
導出指針與導出一個變量或對象的方式相同,比如:
view plaincopy to clipboardprint?
// 導出未初始化的指針
__declspec(dllexport) int* SomeInt;
// 導出未初始化的指針
__declspec(dllexport) int* SomeInt;
view plaincopy to clipboardprint?
// 導出一個初始化的指針
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;
// 導出一個初始化的指針
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;
當然,如果聲明和初始化指針,需要找到刪除它的地方。
u 在擴展 DLL 中,有一個 DllMain() 函數。當客戶程序附加到 DLL 上,而且再次分離時,會調用這個函數。因此,這是一種在擴展 DLL 中處理指針的可能方式:
+ expand sourceview plaincopy to clipboardprint?
#include "SomeClass.h"
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if (dwReason==DLL_PROCESS_ATTACH)
{
// 附加時
}
else if (dwReason==DLL_PROCESS_DETACH)
{
// 分離時
delete SomePointer;
}
}
#include "SomeClass.h"
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if (dwReason==DLL_PROCESS_ATTACH)
{
// 附加時
}
else if (dwReason==DLL_PROCESS_DETACH)
{
// 分離時
delete SomePointer;
}
}
u 在常規 DLL 中,看起來和普通 MFC 可執行文件更像。它有一個從 CwinApp 派生的對象來處理 DLL 的開關。可以使用 Class Wizard 添加 InitInstance() 和 ExitInstance() 函數。
+ expand sourceview plaincopy to clipboardprint?
int CMyDllApp::ExitInstance()
{
delete SomePointer;
return CWinApp::ExitInstance();
}
int CMyDllApp::ExitInstance()
{
delete SomePointer;
return CWinApp::ExitInstance();
}
在客戶程序中使用 DLL
DLL 不能在自身上運行。它需要有一個客戶應用程序載入它,並使用它的接口。
編譯 DLL 時,編譯器創建兩個重要文件: .dll 文件和 .lib 文件。客戶應用程序需要這兩個文件。必須將它們複製到客戶應用程序的項目文件夾中。
注意: 在 debug 模式下通過生成創建的 .dll 文件和 .lib 文件與在 release 模式下創建的文件是不同的。在 debug 模式下生成的客戶程序時,需要 .dll 和 .lib 的 debug 版本;而在 release 模式下生成的客戶程序時,則需要 .dll 和 .lib 的 release 版本。
除了 .dll 和 .lib 文件外,客戶程序還需要針對導出內容 ( 導出類、函數、對象和變量 ) 的頭文件。導出時,在聲明中添加“ __declspec(dllexport) ”,而在導入時,需要添加“ __declspec(dllimport) ”。
記住: 如果在 DLL 中使用定義符 extern “C” ,也必須在客戶程序中使用它。
爲了導入整個類,必須複製整個 .h 頭文件到客戶程序。 DLL 和客戶程序於是具有導出類的相同的頭文件,不同的是,在 DLL 中是“ class __declspec(dllexport) CMyClass ”,在客戶程序文件中是“ class __declspec(dllimport) CMyClass ”。如果創建的是 MFC 擴展 DLL ,在兩個位置均爲“ class AFX_EXT_CLASS CMyClass ”。
說明: 一旦完成了客戶程序的構建,就準備將它移交給實際用戶,給予他們的應該是發行版本的可執行文件以及發行版本的 DLL ,不必給予用戶 .lib 文件。 .dll 文件和 .exe 文件放在同一個目錄中,或者將 .dll 放在 Windows 的 System 目錄下。
警告: 由於 DLL 存在一些嚴重的缺陷,因此 COM 和 ATL 才應運而生。 DLL 存在的主要問題有兩個: (1) 通過某種版本編譯器構建的 DLL 可能與另外的編譯器構建的客戶程序不兼容。 (2) 修改 DLL 時,可能必須重新編譯客戶程序,即使沒有更改客戶程序中的代碼時也是如此。可能仍然要拷入新的 .dll 和 .lib 文件,再進行重新的編譯。
在某些情況下,可以通過一些途徑來避免這個問題。下文將介紹兩種方法。
n DLL 與 MFC 和編譯器的兼容相關的一些常見問題
DLL 是任何 MFC 程序員有用的工具,但是它們存在許多重要的限制。
Ø MFC 問題 。 DLL 必須具備正確版本的 MFC 代碼庫。
Ø 編譯器不兼容性問題 。用一種版本的編譯器來構建 DLL ,但用另一種版本的編譯器構建的 App 來調用它,這時就有可能會出現問題。
Ø 重新編譯問題 。比如現在構建的 DLL 導出一個名叫 CMyClass 的類,爲 CMyClass 提供一個頭文件的拷貝,供客戶應用程序使用,並假設 CMyClass 對象的大小爲 30 字節。這時,假如修改 DLL 來更改 CMyClass ,爲其增加一個 int 型的私有成員變量,因此,當創建 CMyClass 類型的對象時,大小變爲 34 個字節。將這個新的 DLL 發送到用戶,並通知他們替換掉舊的 DLL ,於是就產生了一個問題:客戶 App 期望的是 30 字節大小的對象,但是新的 DLL 創建的是 34 個字節大小的對象,這時客戶 App 就會出錯。
解決方案是什麼?
如果上述這些問題存在完美的解決方案的話,這也許就是 COM 。但是掌握 COM 或 ATL 需要花費大量的時間和精力,運用 DLL 相對容易得多。下面介紹兩種通過修改 DLL 來解決上述問題的辦法:
1) 使用接口類
接口類的目標是分離需要導出的類與該類的接口。完成的途徑是創建第二個類,它將作爲需要導出的類的接口。於是,即使在導出類改變時,也不必重新編譯客戶的 App ,因爲接口類保持不變。
創建一個單獨的接口類,只要接口類 ( 大小 ) 不改變,就不必重新編譯。但是採用這種方法仍然存在兩個相對較小的問題。 (1) 首先,對於 CMyClass 中的每個公有函數和成員變量,必須在 CMyInterface 中創建一個對應的函數或變量。如果 CMyClass 中有上百個函數和變量,創建過程就會非常冗長且很容易出錯。 (2) 這樣做導致必須完成的過程數量增加了,客戶的 App 不再直接調用 CMyClass ,相反,它調用一個調用 CMyClass 的 CMyInterface 函數,如果這是一個被客戶 App 經常調用的函數,那麼額外的處理時間會激增。
2) 使用創建和銷燬導出類的靜態函數
避免必須重新編譯的另一條途徑是使用靜態函數來創建和銷燬導出類。在創建導出類時,添加兩個公有靜態函數 CreateMe() 和 DestoryMe() ,使用這種技術也可以修改 CMyClass 的大小而不必重新編譯客戶的 App 。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/delphiwcdj/archive/2010/01/12/5182966.aspx