RTY 實踐出真知
posts - 319, comments - 22, trackbacks - 0, articles - 11
C++博客
::
首頁
::
新隨筆
::
聯系
::
聚合
::
管理
DLL導出類的問題
Posted on 2011-08-10 07:23
RTY
閱讀(822)
評論(0)
編輯
收藏
引用
所屬分類:
C/C++
、
Windows
DLL導出類的問題
http://www.diybl.com/course/3_program/c++/cppjs/200833/102641.html
DLL動態鏈接庫是程序復用的重要方式,DLL可以導出函數,使函數被多個程序復用,DLL中的函數實現可以被修改而無需重新編譯和連接使用該DLL的應用程序。作為一名面向對象的程序員,希望DLL可以導出類,以便在類的層次上實現復用。所幸的是,DLL確實也可以導出類。
然而事實卻沒這么簡單,導出類的DLL在維護和修改時有很多地方必需很小心,增加成員變量、修改導出類的基類等操作都可能導致意想不到的后果,也許
用戶
更新了最新版本的DLL庫后,應用程序就再也不能
工作
了。這就是著名的DLL Hell(DLL地獄)問題。
DLL地獄問題是怎么產生的呢?看下面的例子,假設DLL有一個導出類ClassD1:
class ClassD
{
public:
int GetInt();
private:
int m_i;
};
int ClassD::GetInt()
{
return m_i;
}
應用程序使用現在的代碼來使用這個類:
ClassD d;
printf(“%d”, d.GetInt());
程序進行正正常,沒有什么問題。后來DLL需要升級,對ClassD進行了修改,增加了一個成員變量,如下:
class ClassD // 修改后
{
public:
int GetInt();
private:
int m_i2;
int m_i;
};
把新的DLL編譯連接完成后,復制到應用程序目錄,這個倒楣的應用程序調用GetInt方法恐怕再也無法得正確的值了。事實上它還算幸運的,如果GetInt的實現改成如下這樣,那么它馬上就要出錯退出了。
int ClassD::GetInt() // 修改后
{
return m_i++;
}
這樣的事情,稱它是個地獄(Hell)一點也不夸張。為什么會出錯呢?我們要先從類實例的創建開始,看看使用一個類的工作過程。
首先,程序語句“ClassD d;”為這個類申請一塊內存。這塊內存保存該類的所有成員變量,以及虛函數表。內存的大小由類的聲明決定,在應用程序編譯時就已經確定。
然后,當調用“d.GetInt()”時,把申請的這一塊內存做為this指針傳給GetInt函數,GetInt函數從this指向的位置開始,加上m_i應有的偏移量,計算m_i所在的內存位置,并從該位置取數據返回。m_i相對this的偏移量是由m_i在類中定義的位置決定的,定義在前的成員變量在內存中也更靠前。這個偏移量在DLL編譯時確定。
當ClassD的定義改為修改后的狀態時,有些東西變了。
第一個變的是內存的大小。因為修改后的ClassD多了一個成員變量,所以內存也變大了。然而這一點應用程序并不知道。
第二個變的是m_i的偏移地址。因為在m_i之前定義了一個m_i2,m_i的實現偏移地址實際已經靠后了。所以d.GetInt()訪問的將是原來m_i后面的那個位置,而這個位置已經超出原來那塊內存的后部范圍了。
很顯然,在更換了DLL后,應用程序還按原來的大小申請了一塊內存,而它調用的方法卻訪問了比這塊內存更大的區域,出錯再在所難免。
同樣的情形還會發生在以下這些種情況中:
1)
應用程序直接訪問類的公有變量,而該公有變量在新DLL中定義的位置發生了變化;
2)
應用程序調用類的一個虛函數,而新的類中,該虛函數的前面又增加了一個虛函數;
3)
新類的后面增加了成員變量,并且新類的成員函數將訪問、修改這些變量;
4)
修改了新類的基類,基類的大小發生了變化;
等等,總言而之,一不小心,你的程序就會掉進地獄。通過對這些引起出錯的情況進行分析,會
發現
其實只有三點變化會引起出錯,因為這三點是使用這個DLL的應用程序在編譯時就需要確定的內容,它們分別是:
1)
類的大小;
2)
類成員的偏移地址;
3)
虛函數的順序。
要想做一個可升級的DLL,必需避免以上三個問題。所以以下三點用來使DLL遠離地獄。
1,不直接生成類的實例。對于類的大小,當我們定義一個類的實例,或使用new語句生成一個實例時,內存的大小是在編譯時決定的。要使應用程序不依賴于類的大小,只有一個辦法:應用程序不生成類的實例,使用DLL中的函數來生成。把導出類的構造函數定義為私有的(privated),在導出類中提供靜態(static)成員函數(如NewInstance())用來生成類的實例。因為NewInstance()函數在新的DLL中會被重新編譯,所以總能返回大小正確的實例內存。
2,不直接訪問成員變量。應用程序直接訪問類的成員變量時會用到該變量的偏移地址。所以避免偏移地址依賴的辦法就是不要直接訪問成員變量。把所有的成員變量的訪問控制都定義為保護型(protected)以上的級別,并為需要訪問的成員變量定義Get或Set方法。Get或Set方法在編譯新DLL時會被重新編譯,所以總能訪問到正確的變量位置。
3,忘了虛函數吧,就算有也不要讓應用程序直接訪問它。因為類的構造函數已經是私有(privated)的了,所以應用程序也不會去繼承這個類,也不會實現自己的多態。如果導出類的父類中有虛函數,或設計需要(如類工場之類的框架),一定要把這些函數聲明為保護的(protected)以上的級別,并為應用程序重新設計調用該慮函數的成員函數。這一點也類似于對成員變量的處理。
如果導出的類能遵循以上三點,那么以后對DLL的升級將可以認為是安全的。
如果對一個已經存在的導出類的DLL進行維護,同樣也要注意:不要改動所有的成員變量,包括導出類的父類,無論定義的順序還是數量;不要動所有的虛函數,無論順序還是數量。
總結起來,其實是一句話:導出類的DLL不要導出除了函數以外的任何內容。聽起來是不是有點可笑呢!
事實上,建議你在發布導出類的DLL的時候,重新定義一個類的聲明,這個聲明可以不管原來的類里的成員變量之類的,只把接口函數列在類的聲明里,如下面的例子:
class ClassInterface
{
privated:
ClassInterface();
public:
static ClassInterface * NewInstance();
int GetXXX();
void SetXXX();
void Function();
};
使用該DLL的應用程序用上面的定義作為ClassInterface的頭文件,便不會有任何可能導致的安全問題。
DLL地獄問是歸根結底是因為DLL當初是作為函數級共享庫設計的,并不能真正提供一個類所必需的信息。類層上的程序復用只有Java和C#生成的類文件才能做到。
只有注冊用戶
登錄
后才能發表評論。
【推薦】100%開源!大型工業跨平臺軟件C++源碼提供,建模,組態!
相關文章:
Dynamic Library Design Guidelines (Xcode)
Qt internal error: qt_menu.nib could not be loaded.
Cocoa讀取和寫入plist文件
什么叫IOC(編程術語)
otool 與dylib
找不到 dirent.h 文件
vc調試窗口表達式格式化資料
Context Operator (C/C++ Language Expressions)
Managed Expressions in C++ (VC 2010 調試)
Find path of an application
網站導航:
博客園
IT新聞
BlogJava
博問
Chat2DB
管理
Powered by:
C++博客
Copyright © RTY
日歷
<
2025年5月
>
日
一
二
三
四
五
六
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
常用鏈接
我的隨筆
我的評論
我參與的隨筆
留言簿
(12)
給我留言
查看公開留言
查看私人留言
隨筆分類
(538)
C/C++(70)
CSS(41)
JavaScript(6)
Linux(7)
Lua專題(3)
Mac os(34)
Python(33)
QML(21)
Qt(65)
TBB(4)
Windows(38)
XML
編程常識(37)
軟件(38)
數據庫
需要注意的(14)
質量保障(14)
轉載隨筆(113)
隨筆檔案
(319)
2013年3月 (2)
2013年2月 (5)
2013年1月 (4)
2012年12月 (2)
2012年11月 (7)
2012年10月 (1)
2012年9月 (1)
2012年7月 (1)
2012年6月 (2)
2012年5月 (5)
2012年4月 (15)
2012年3月 (13)
2012年2月 (5)
2012年1月 (1)
2011年12月 (1)
2011年11月 (5)
2011年10月 (5)
2011年9月 (26)
2011年8月 (51)
2011年7月 (23)
2011年6月 (22)
2011年5月 (82)
2011年4月 (38)
2010年12月 (2)
文章分類
(11)
C/C++文章收集
Linux(3)
QT相關資料收集(8)
文章檔案
(11)
2010年12月 (11)
官方教程
知識共享鏈接
搜索
最新評論
1.?re: OSGi開發起步
評論內容較長,點擊標題查看
--M.I
2.?re: iOS中plist的創建,數據寫入與讀取
評論內容較長,點擊標題查看
--泰國
3.?re: Windows 7使用技巧:在當前路徑下打開命令行(cmd)命令窗口
給力啊
--gy
4.?re: codesign CSSMERR_TP_NOT_TRUSTED
你好,能告訴我你QQ么,我遇到了這問題,但是安裝了證書后還是這樣,能幫我看看么
--潮
5.?re: Windows 7使用技巧:在當前路徑下打開命令行(cmd)命令窗口
好用
--xxoo
閱讀排行榜
1.?找不到 dirent.h 文件(9679)
2.?實現Qt日志功能并輸出到文件(qDebug\qWarning\ qCritical\qFatal)(9274)
3.?VirtualBox虛擬機安裝Mac OS X Lion(8934)
4.?Windows 7使用技巧:在當前路徑下打開命令行(cmd)命令窗口(8116)
5.?iOS中plist的創建,數據寫入與讀取(7712)
評論排行榜
1.?字符集編碼與 C/C++ 源文件字符編譯亂彈(收集轉載)(4)
2.?Windows 7使用技巧:在當前路徑下打開命令行(cmd)命令窗口(4)
3.?Intel Parallel Studio XE 2011(3)
4.?QML與c++交互學習筆記(八) qt c++直接調用QML中的函數, 直接設置屬性 (2)
5.?VirtualBox虛擬機安裝Mac OS X Lion(2)
99久久国产亚洲综合精品
|
精品久久综合1区2区3区激情
|
一本久久免费视频
|
精品综合久久久久久97
|
日本久久久久亚洲中字幕
|
亚洲AⅤ优女AV综合久久久
|
欧美日韩精品久久久免费观看
|
亚洲色婷婷综合久久
|
久久国产一区二区
|
久久亚洲精品国产精品婷婷
|
国产精品一久久香蕉国产线看
|
久久亚洲2019中文字幕
|
欧美牲交A欧牲交aⅴ久久
|
女同久久
|
久久免费小视频
|
久久婷婷五月综合97色直播
|
久久最近最新中文字幕大全
|
亚洲欧美伊人久久综合一区二区
|
7国产欧美日韩综合天堂中文久久久久
|
色婷婷久久久SWAG精品
|
国产69精品久久久久99
|
久久99精品久久久久久久不卡
|
亚洲国产高清精品线久久
|
伊人久久免费视频
|
久久96国产精品久久久
|
久久久免费精品re6
|
久久久久久毛片免费播放
|
久久综合久久综合亚洲
|
国産精品久久久久久久
|
一本久久a久久精品综合夜夜
|
久久久精品人妻一区二区三区四
|
久久精品人妻中文系列
|
四虎国产精品成人免费久久
|
人妻中文久久久久
|
久久精品国产亚洲av瑜伽
|
一本一道久久精品综合
|
久久综合久久久
|
中文字幕亚洲综合久久
|
久久九色综合九色99伊人
|
久久青青国产
|
亚洲国产天堂久久久久久
|