1.dll的優點
代碼復用是提高軟件開發效率的重要途徑。一般而言,只要某部分代碼具有通用性,就可將它構造成相對獨立的功能模塊並在之后的項目中重復使用。比較常見的例子是各種應用程序框架, ATL 、 MFC 等,它們都以源代碼的形式發布。由於這種復用是 “ 源碼級別 ” 的,源代碼完全暴露給了程序員,因而稱之為 “ 白盒復用 ” 。 “ 白盒復用 ” 的缺點比較多,總結起來有 4 點。暴露了源代碼;多份拷貝,造成存儲浪費;
容易與程序員的 “ 普通 ” 代碼發生命名沖突;
更新功能模塊比較困難,不利於問題的模塊化實現;
實際上,以上 4 點概括起來就是 “ 暴露的源代碼 ” 造成 “ 代碼嚴重耦合 ” 。為了彌補這些不足,就提出了 “ 二進制級別 ” 的代碼復用。使用二進制級別的代碼復用一定程度上隱藏了源代碼,對於緩解代碼耦合現象起到了一定的作用。這樣的復用被稱為 “ 黑盒復用 ” 。
說明:實現“黑盒復用”的途徑不只dll一種,靜態鏈接庫甚至更高級的COM組件都是。
2.dll的創建
參考程序原文: http://msdn.microsoft.com/zh-cn/library/ms235636.aspx新建“Win32項目”,選擇應用程序類型為"DLL”,其他默認。添加頭文件testdll.h
//testdll.h
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif
namespace MathFuncs
{
// This class is exported from the testdll.dll
class MyMathFuncs
{
public:
// Returns a + b
static TESTDLL_API double Add( double a, double b);
// Returns a - b
static TESTDLL_API double Subtract( double a, double b);
// Returns a * b
static TESTDLL_API double Multiply( double a, double b);
// Returns a / b
// Throws const std::invalid_argument& if b is 0
static TESTDLL_API double Divide( double a, double b);
};
}
當定義了符號TESTDLL_EXPORTS,TESTDLL_API被設置為 __declspec(dllexport) 修飾符, This modifier enables the function to be exported by the DLL so that it can be used by other applications。若未定義則TESTDLL_API被設置為__declspec(dllimport),This modifier enables the compiler to optimize the importing of the function from the DLL for use in other applications。當DLL項目生成時,TESTDLL_EXPORTS默認是定義的,所以默認設置的是__declspec(dllexport) 修飾符。
添加 cpp 文件// testdll.cpp : 定義 DLL 應用程序的導出函數。
#include " testdll.h "
#include <stdexcept>
using namespace std;
namespace MathFuncs
{
double MyMathFuncs::Add( double a, double b)
{
return a + b;
}
double MyMathFuncs::Subtract( double a, double b)
{
return a - b;
}
double MyMathFuncs::Multiply( double a, double b)
{
return a * b;
}
double MyMathFuncs::Divide( double a, double b)
{
if (b == 0)
{
throw invalid_argument( " b cannot be zero! ");
}
return a / b;
}
}
編譯就會生成對應的dll文件,同時也會生成對應的lib文件。
注意:a.DLL中導出函數的聲明有兩種方式:在函數聲明中加上__declspec(dllexport);采用模塊定義(.def)文件聲明。詳見:http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792099.html
b.對於C文件創建dll時或者想使用C編譯器創建dll時,建議使用 extern “C” 標志,參見extern "C"的簡單解析(在C++中調用DLL中的函數(1))
3.dll的調用
應用程序使用DLL可以采用兩種方式:一種是隱式鏈接(調用),另一種是顯式鏈接。在使用DLL之前首先要知道DLL中函數的結構信息。VS在VC\bin目錄下提供了一個名為Dumpbin.exe的小程序,用它可以查看DLL文件中的函數結構。兩種的對比詳見:http://blog.sina.com.cn/s/blog_53004b4901009h3b.html
隱式鏈接采用靜態加載的方式,比較簡單,需要.h、.lib、.dll三件套。。
新建“控制台應用程序”或“空項目”配置如下:(非常關鍵)
項目->屬性->配置屬性->VC++ 目錄-> 在“包含目錄”里添加頭文件testdll.h所在的目錄
項目->屬性->配置屬性->VC++ 目錄-> 在“庫目錄”里添加頭文件testdll.lib所在的目錄
項目->屬性->配置屬性->鏈接器->輸入-> 在“附加依賴項”里添加“testdll.lib”(若有多個 lib 則以空格隔開)
添加cpp文件
//mydll.cpp
#include " testdll.h "
using namespace std;
int main()
{
double a = 7.4;
int b = 99;
cout << " a + b = " <<
MathFuncs::MyMathFuncs::Add(a, b) << endl;
cout << " a - b = " <<
MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
cout << " a * b = " <<
MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
cout << " a / b = " <<
MathFuncs::MyMathFuncs::Divide(a, b) << endl;
try
{
cout << " a / 0 = " <<
MathFuncs::MyMathFuncs::Divide(a, 0) << endl;
}
catch ( const invalid_argument &e)
{
cout << " Caught exception: " << e.what() << endl;
}
return 0;
}
現在可以編譯通過了,但是程序運行就報錯,還需要將testdll.dll復制到當前項目生成的可執行文件所在的目錄。
顯式鏈接是應用程序在執行過程中隨時可以加載DLL文件,也可以隨時卸載DLL文件,這是隱式鏈接所無法作到的,所以顯式鏈接具有更好的靈活性,對於解釋性語言更為合適。
新建項目,不需要特殊配置,添加cpp文件
#include<Windows.h> //加載的頭文件
using namespace std;
int main()
{
typedef double (*pAdd)( double a, double b);
typedef double (*pSubtract)( double a, double b);
HMODULE hDLL = LoadLibrary( " testdll.dll "); // 加載dll文件
if(hDLL != NULL)
{
pAdd fp1 = pAdd(GetProcAddress(hDLL, MAKEINTRESOURCE( 1))); // 得到dll中的第一個函數
if(fp1 != NULL)
{
cout<<fp1( 2.5, 5.5)<<endl;
}
else
{
cout<< " Cannot Find Function "<< " add "<<endl;
}
pSubtract fp2 = pSubtract(GetProcAddress(hDLL, " ?Subtract@MyMathFuncs@MathFuncs@@SANNN@Z ")); // 得到dll中標示為"?..."的函數,C++編譯器考慮了函數的參數
if(fp2 != NULL)
{
cout<<fp2( 5.5, 2.5)<<endl;
}
else
{
cout<< " Cannot Find Function "<< " Subtract "<<endl;
}
FreeLibrary(hDLL);
}
else
{
std::cout<< " Cannot Find "<< " testdll "<<std::endl;
}
return 1;
}
顯式調用的問題:在DLL文件中,dll工程中函數名稱在編譯生成DLL的過程中發生了變化(C++編譯器),在DLL文件中稱變化后的字符為“name標示”。GetProcAddress中第二個參數可以由DLL文件中函數的順序獲得,或者直接使用DLL文件中的”name標示”,這個標示可以通過Dumpbin.exe小程序查看。如果C++編譯器下,想讓函數名更規范(和原來工程中一樣),具體方法詳見:http://blog.csdn.net/btwsmile/article/details/6676802。
當然,為了讓函數名更規范,最常用的方式是:創建dll過程中使用C編譯器來編譯函數,這樣DLL文件中的函數名和原dll工程中的函數名就一致了。
4.更一般的顯式調用
在 DLL 創建的工程中,添加 cpp 文件
#include "stdafx.h"
#ifdef __cplusplus // if used by C++ code
extern " C " { // we need to export the C interface
#endif
__declspec(dllexport) int addfun( int a, int b)
{
return a+b;
}
#ifdef __cplusplus
}
#endif
編譯即可生成DLL文件。在dll調用工程中,添加cpp文件
/*
*作者:侯凱
*說明:顯式調用dll
*日期:2013-6-5
*/
#include <windows.h>
#include <iostream>
using namespace std;
void main()
{
typedef int(*FUNA)( int, int);
HMODULE hMod = LoadLibrary( " cdll.dll "); // dll路徑
if (hMod)
{
FUNA addfun = (FUNA)GetProcAddress(hMod, TEXT( " addfun ")); // 直接使用原工程函數名
if (addfun != NULL)
{
cout<<addfun( 5, 4)<<endl;
}
else
{
cout<< " ERROR on GetProcAddress "<<endl;
}
FreeLibrary(hMod);
}
else
cout<< " ERROR on LoadLibrary "<<endl;
}
運行,這樣便可以調用dll的函數了。再進一步,上述dll文件如果通過隱式調用,利用.dll、.lib文件,調用函數應為
//隱式鏈接
#pragma comment(lib,"cdll.lib")
using namespace std;
extern " C " _declspec(dllimport) int addfun( int a, int b);
// 載入addfun函數,這里起到了.h文件的作用
// dll中使用C編譯器 故這里需要extern "C" 如果dll中無extern "C"
// 此處為:_declspec(dllimport) int addfun(int a,int b);
void main()
{
cout<<addfun( 5, 4)<<endl;
}