根據本人學習中遇到的問題,本文圍繞以下幾個問題展開:
1.DLL的相關概念
2.動態非MFC DLL在VC++中的使用
3.宏在DLL導出函數和導入函數聲明中的應用
一 DLL相關概念
可以簡單把DLL看做一個倉庫,它提供給你可以直接使用的變量、函數或類。動態鏈接庫DLL實現了庫的共享,體現了代碼重用的思想。我們可以把廣泛的、具有共性的、能夠多次被利用的函數和類定義在庫中。這樣,在再次使用這些函數和類的時候,就不再需要重新添加與這些函數和類相關的代碼。DLL在軟件世界中隨處可見,比如我們在Windows目錄下的system32文件夾中會看到kernel32.dll、user32.dll和gdi32.dll,windows的大多數API都包含在這些DLL中。kernel32.dll中的函數主要處理內存管理和進程調度;user32.dll中的函數主要控制用戶界面;gdi32.dll中的函數則負責圖形方面的操作。在MFC應用程序編譯時就會經常調用這里面的函數和基類,因為這些庫里的函數和類給我們提供了一般的方法方便我們編寫應用程序。
VC中DLL的分類:Visual C++支持三種DLL,它們分別是Non-MFC DLL(非MFC動態庫)、MFC Regular DLL(MFC規則DLL)、MFC Extension DLL(MFC擴展DLL)。他們之間的區別簡單概括如下:
非MFC動態庫不采用MFC類庫結構,其導出函數為標准的C接口,能被非MFC和MFC編寫的應用程序所調用;MFC規則DLL 包含一個繼承自CWinApp的類,但其無消息循環,可以使用MFC,但是接口不能為MFC;MFC擴展DLL采用MFC的動態鏈接版本創建,它只能被用MFC類庫所編寫的應用程序所調用。
DLL應用場合:需要經常使用的函數、變量或基類都可以封裝在DLL中。比如通用的算法、常用的通信控制等;此外采用DLL在一個大工程中還可以方便分工,提高效率,增強系統易擴展性,就不必把整個代碼全放在一個工程文件內,初學者最喜歡這樣了(我也是初學者^_^)。
二 動態非MFC DLL在VC++中的使用
DLL的三種類型用法其實都是大同小異 ,所不同的是接口和應用范圍的差異,使用方法均相同。這里我就只討論非MFC DLL的使用,接口涉及到變量、函數和類,這里只討論函數的情況,變量和類的情況是差不多的,類的調用的話可能要求按需要加類定義的頭文件。也就是此節只討論非MFC DLL函數調用分方法。下面內容只說函數(變量和類也可作為接口,這里省略不做討論)
DLL的使用包括兩個方面:一是函數的導出,二是函數的導入。導出在DLL中進行,導入在調用DLL的應用程序中。
導出方法(兩種方法):①在函數聲明前加上extern "C" _declspec(dllexport)
②在.def文件中說明(貌似vs2010中沒有這個文件。。。)
注:通常情況下,為了確保不同的語言編寫的可執行模塊都能夠正確地訪問到導出函數,習慣上都采用extern "C"來指定導出函數采用C鏈接方式。_declspec(dllexport)為DLL導出的關鍵字。
代碼示例: extern "C" __declspec(dllexport) float Add(float,float);
在函數定義時就不需要再加extern "C" __declspec(dllexport)這些關鍵字了,跟通常函數定義一樣。
導入方法(兩種方法):①靜態導入:需要將相應的.dll和.lib文件拷貝到應用程序的工程目錄中,在調用函數的源文件中使用用#pragma comment(lib,"xxx.lib")或直接項工程中加.lib文件,然后再聲明導入函數,聲明導入函數代碼如下:
extern "C" __declspec(import) float Add(float,float);
在需要調用函數的位置直接使用函數即可,注意函數參數和返回值類型,就跟使用MFC類庫你的函數一樣。
②動態調用:需要將相應的.DLL拷貝到應用程序工程目錄中,再采用LoadLibrary-GetProcAddress-FreeLibrary方法進行函數導入。LoadLibrary、GetProcAddress、FreeLibrary函數的使用參見msdn,這里不詳訴。
代碼示例:
.h文件:
protected:
float m_Num1;
float m_Num2;
float m_Result;
.cpp文件:
typedef float (*fn_Add)(float,float);
HINSTANCE hLib=LoadLibrary(_T("Win32Dll.dll"));
fn_Add pAdd=(fn_Add)GetProcAddress(hLib,"Add");
UpdateData(true);
m_Result=(*pAdd)(m_Num1,m_Num2);
FreeLibrary(hLib);
UpdateData(false);
注:這里為了運行不出錯,最好使用if語句判斷下hLib和pAdd是否為NULL。此外有時還需要LoadLibrary函數的參數寫DLL的具體文件地址而不是DLL文件名(比如從VC低版本轉高版本時需要注意這個問題)。
DllMain函數:Windows在加載DLL的時候,需要一個入口函數,就如同控制台或DOS程序需要main函數、WIN32程序需要WinMain函數一樣。在非MFC DLL中,DLL並沒有提供DllMain函數,應用工程也能成功引用DLL,這是因為Windows在找不到DllMain的時候,系統會從其它運行庫中引入一個不做任何操作的缺省DllMain函數版本,並不意味着DLL可以放棄DllMain函數。需要注意的是,在MFC規則DLL和MFC擴展DLL工程中都有DllMain函數,但在非MFC DLL工程中沒有,除非特殊需要,我們一般不管這個函數。
三 宏在DLL導出函數和導入函數聲明的應用
前面講了函數的導出和導入,我們看到在靜態導入中還需要對導入函數進行聲明,而在調用MFC的函數時是不需要聲明的,可不可以是本文討論的函數也這樣呢?那當然是可以的,這里就要用到宏定義,在Dll中使用宏對導出和導入關鍵字進行定義。定義一個宏,用於控制函數處於導出聲明或調用導入聲明狀態。對於DLL定義文件,在包含DLL頭文件之前,首先定義一個控制宏,用於聲明所有的函數為導出函數;而在隱式調用中,在包含DLL頭文件時不需要定義控制宏,用於聲明所有的函數為導入函數。
DLL頭文件格式如下:
#ifdef WIN32DLL_EXPORTS
#define WIN32DLL_API __declspec(dllexport)
#else
#define WIN32DLL_API __declspec(dllimport)
#endif
函數聲明:extern "C" WIN32DLL_API float Add(float,float); //函數聲明也在頭文件中
對DLL文件進行使用時,就可以直接使用函數Add了而不需要再聲明:m_Result=Add(m_Num1,m_Num2);
注意:調用DLL的應用程序原文件還要加入以上宏定義所在的頭文件,不然會編譯出錯,提示Add函數不能識別。
