使用DLL的一個比較嚴重的問題就是編譯器之間的兼容性問題。不同的編譯器對c++函數在二進制級別的實現方式是不同的。所以對基於C++的DLL,如果編譯器不同就有很麻煩的。如果創建的是MFC擴展DLL,就不會存在問題,因為它只能被動態連接到MFC的客戶應用程序。
這裡不是本文討論的重點。
一、重新編譯問題
我們先來看一個在實際中可能遇到的問題:
比如現在建立好了一個DLL導出了CMyClass類,客戶也能正常使用這個DLL,假設CMyClass對象的大小為30字節。如果我們需要修改DLL中的CMyClass類,讓它有相同的函數和成員變量,但是給增加了一個私有的成員變量int類型,現在CMyClass對象的大小就是34字節了。當直接把這個新的DLL給客戶使用替換掉原來30字節大小的DLL,客戶應用程序期望的是30字節大小的對象,而現在卻變成了一個34字節大小的對象,糟糕,客戶程序出錯了。
類似的問題,如果不是導出CMyClass類,而在導出的函數中使用了CMyClass,改變對象的大小仍然會有問題的。這個時候修改這個問題的唯一辦法就是替換客戶程序中的CMyClass的頭文件,全部重新編譯整個應用程序,讓客戶程序使用大小為34字節的對象。
這就是一個嚴重的問題,有的時候如果沒有客戶程序的源代碼,那麼我們就不能使用這個新的DLL了。
二、解決方法
為了能避免重新編譯客戶程序,這裡介紹兩個方法:(1)使用接口類。(2)使用創建和銷毀類的靜態函數。
1、使用接口類
接口類的也就是創建第二個類,它作為要導出類的接口,所以在導出類改變時,也不需要重新編譯客戶程序,因為接口類沒有發生變化。
假設導出的CMyClass類有兩個函數FunctionA FunctionB。現在創建一個接口類CMyInterface,下面就是在DLL中的CMyInterface類的頭文件的代碼:
# include "MyClass.h"
class _declspec(dllexport) CMyInterface
{
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
而在客戶程序中的頭文件稍不同,不需要INCLUDE語句,因為客戶程序沒有它的拷貝。相反,使用一個CMyClass的向前聲明,即使沒有頭文件也能編譯:
class _declspec(dllexport) CMyInterface
{
class CMyClass;//向前聲明
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
在DLL中的CMyInterface的實現如下:
CMyInterface::CMyInterface()
{
pmyclass = new CMyClass();
}
CMyInterface::~CMyInterface()
{
delete pmyclass;
}
int CMyInterface::FunctionA()
{
return pmyclass->FunctionA();
}
int CMyInterface::FunctionB()
{
return pmyclass->FunctionB();
}
.....
對導出類CMyClass的每個成員函數,CMyInterface類都提供自己的對應的函數。客戶程序與CMyClass沒有聯系,這樣任意改CMyClass也不會有問題,因為CMyInterface類的大小沒有發生變化。即使為了能訪問CMyClass中的新增變量而給CMyInterface類加了函數也不會有問題的。
但是這種方法也存在明顯的問題,對導出類的每個函數和成員變量都要對應實現,有的時候這個接口類會很龐大。同時增加了客戶程序調用所需要的時間。增加了程序的開銷。
2、使用靜態函數
還可以使用靜態函數來創建和銷毀類對象。創建一個導出類的時候,增加兩個靜態的公有函數CreateMe()/DestroyMe(),頭文件如下:
class _declspec(dllexport) CMyClass
實現函數就是:
{
CMyClass();
~CMyClass();
public:
static CMyClass *CreateMe();
static void DestroyMe(CMyClass *ptr);
};
CMyClass * CMyClass::CMyClass()
{
return new CMyClass;
}
void CMyClass::DestroyMe(CMyClass *ptr)
{
delete ptr;
}
然後象其他類一樣導出CMyClass類,這個時候在客戶程序中使用這個類的方法稍有不同了。如若想創建一個CMyClass對象,就應該是:
CMyClass x;
CMyClass *ptr = CMyClass::CreateMe();
在使用完後刪除:
CMyClass::DestroyMe(ptr);