C++編寫動態庫.so或者.dll的陷阱
一、介面不變就可以不需要重新編譯?
對於很多庫的實現者可能會有這樣的認識“介面不變就可以不需要重新編譯”,其實這句話是有前提的,前提是實現的動態庫有足夠的相容性和魯棒性。尤其是C++實現的動態庫,C++只對語言層規則做了規定,沒有二進位制級別的任何規定。
COM本質論裡面的例子很好的闡述了這點,簡單摘錄如下:
查詢字串的dll版本1如下
class StringFind{
char *p;
public:
StringFind();
~StringFind();
int Find(*p);
int Length(*p);
};
查詢字串的dll版本2如下
class StringFind{
char *p;
int length;
public:
StringFind();
~StringFind();
int Find(*p);
int Length(*p);
};
這兩個版本的在介面上沒有變動,只是版本2增加了一個成員變數用來記錄字串的長度。但是如果不重新編譯可執行程式,直接替換到版本1的環境中,可執行程式將會崩潰,因為StringFind類在版本2中佔有了8位元組,而版本1中只佔有4位元組,導致訪問length的時候出現越界現象。當然也有方法解決這個需要編譯的問題,將上面的類分為一個介面類和一個實現類,介面類只定義介面,具體實現在實現類中,介面類通過物件指標方式訪問實現類。或者使用抽象基類作為介面類,繼承類作為實現類。實現類的物件每次使用new的方式來建立。
class StringFind{
public:
StringFind();
~StringFind();
virtual int Find(*p) = 0;
virtual int Length(*p) = 0;
};
extern "C" CreatStringFind();
動態庫內部通過整合基類StringFind,實現具體的實現類。
外部採用如下方式使用:
StringFind Obj = CreatStringFind();
if(NULL != Obj)
{
Obj->Find();
Obj->Length();
delete Obj;
}
二、解構函式引起的記憶體洩露上述方法解決了之前的崩潰問題但是同樣引入了新的記憶體洩露問題,問題原因在於StringFind的解構函式不是虛擬函式,在外面delete指向基類物件的指標的時候只會呼叫基類的解構函式不會呼叫繼承類中的解構函式所以沒法釋放基類StringFind的繼承類中的成員,造成記憶體洩露。解決辦法就是將解構函式也定義成純虛擬函式,如下:
class StringFind{
public:
StringFind();
virtual ~StringFind() = 0;;
virtual int Find(*p) = 0;
virtual int Length(*p) = 0;
};
extern "C" CreatStringFind();
或者定義一個destroy虛擬函式函式讓繼承類自己實現析構,如下:class StringFind{
public:
StringFind();
virtual Destroy() = 0;;
virtual int Find(*p) = 0;
virtual int Length(*p) = 0;
};
extern "C" CreatStringFind();