• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 319, comments - 22, trackbacks - 0, articles - 11
              C++博客 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            DLL導(dǎo)出類(lèi)的問(wèn)題

            Posted on 2011-08-10 07:23 RTY 閱讀(837) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): C/C++Windows
            DLL導(dǎo)出類(lèi)的問(wèn)題

            http://www.diybl.com/course/3_program/c++/cppjs/200833/102641.html
            DLL動(dòng)態(tài)鏈接庫(kù)是程序復(fù)用的重要方式,DLL可以導(dǎo)出函數(shù),使函數(shù)被多個(gè)程序復(fù)用,DLL中的函數(shù)實(shí)現(xiàn)可以被修改而無(wú)需重新編譯和連接使用該DLL的應(yīng)用程序。作為一名面向?qū)ο蟮某绦騿T,希望DLL可以導(dǎo)出類(lèi),以便在類(lèi)的層次上實(shí)現(xiàn)復(fù)用。所幸的是,DLL確實(shí)也可以導(dǎo)出類(lèi)。

            然而事實(shí)卻沒(méi)這么簡(jiǎn)單,導(dǎo)出類(lèi)的DLL在維護(hù)和修改時(shí)有很多地方必需很小心,增加成員變量、修改導(dǎo)出類(lèi)的基類(lèi)等操作都可能導(dǎo)致意想不到的后果,也許
            用戶更新了最新版本的DLL庫(kù)后,應(yīng)用程序就再也不能工作了。這就是著名的DLL Hell(DLL地獄)問(wèn)題。

            DLL地獄問(wèn)題是怎么產(chǎn)生的呢?看下面的例子,假設(shè)DLL有一個(gè)導(dǎo)出類(lèi)ClassD1:
            class ClassD
            {
                   public:
            int GetInt();
                          private:
            int m_i;
            };
            int ClassD::GetInt()
            {
                   return m_i;
            }

            應(yīng)用程序使用現(xiàn)在的代碼來(lái)使用這個(gè)類(lèi):
            ClassD d;
            printf(“%d”, d.GetInt());
             
            程序進(jìn)行正正常,沒(méi)有什么問(wèn)題。后來(lái)DLL需要升級(jí),對(duì)ClassD進(jìn)行了修改,增加了一個(gè)成員變量,如下:
            class ClassD // 修改后
            {
                   public:
            int GetInt();
                          private:
                                 int m_i2;
            int m_i;
            };

            把新的DLL編譯連接完成后,復(fù)制到應(yīng)用程序目錄,這個(gè)倒楣的應(yīng)用程序調(diào)用GetInt方法恐怕再也無(wú)法得正確的值了。事實(shí)上它還算幸運(yùn)的,如果GetInt的實(shí)現(xiàn)改成如下這樣,那么它馬上就要出錯(cuò)退出了。
            int ClassD::GetInt() // 修改后
            {
                   return m_i++;
            }

            這樣的事情,稱(chēng)它是個(gè)地獄(Hell)一點(diǎn)也不夸張。為什么會(huì)出錯(cuò)呢?我們要先從類(lèi)實(shí)例的創(chuàng)建開(kāi)始,看看使用一個(gè)類(lèi)的工作過(guò)程。

            首先,程序語(yǔ)句“ClassD d;”為這個(gè)類(lèi)申請(qǐng)一塊內(nèi)存。這塊內(nèi)存保存該類(lèi)的所有成員變量,以及虛函數(shù)表。內(nèi)存的大小由類(lèi)的聲明決定,在應(yīng)用程序編譯時(shí)就已經(jīng)確定。

            然后,當(dāng)調(diào)用“d.GetInt()”時(shí),把申請(qǐng)的這一塊內(nèi)存做為this指針傳給GetInt函數(shù),GetInt函數(shù)從this指向的位置開(kāi)始,加上m_i應(yīng)有的偏移量,計(jì)算m_i所在的內(nèi)存位置,并從該位置取數(shù)據(jù)返回。m_i相對(duì)this的偏移量是由m_i在類(lèi)中定義的位置決定的,定義在前的成員變量在內(nèi)存中也更靠前。這個(gè)偏移量在DLL編譯時(shí)確定。

            當(dāng)ClassD的定義改為修改后的狀態(tài)時(shí),有些東西變了。

            第一個(gè)變的是內(nèi)存的大小。因?yàn)樾薷暮蟮腃lassD多了一個(gè)成員變量,所以內(nèi)存也變大了。然而這一點(diǎn)應(yīng)用程序并不知道。

            第二個(gè)變的是m_i的偏移地址。因?yàn)樵趍_i之前定義了一個(gè)m_i2,m_i的實(shí)現(xiàn)偏移地址實(shí)際已經(jīng)靠后了。所以d.GetInt()訪問(wèn)的將是原來(lái)m_i后面的那個(gè)位置,而這個(gè)位置已經(jīng)超出原來(lái)那塊內(nèi)存的后部范圍了。

            很顯然,在更換了DLL后,應(yīng)用程序還按原來(lái)的大小申請(qǐng)了一塊內(nèi)存,而它調(diào)用的方法卻訪問(wèn)了比這塊內(nèi)存更大的區(qū)域,出錯(cuò)再在所難免。

            同樣的情形還會(huì)發(fā)生在以下這些種情況中:

            1) 應(yīng)用程序直接訪問(wèn)類(lèi)的公有變量,而該公有變量在新DLL中定義的位置發(fā)生了變化;
            2) 應(yīng)用程序調(diào)用類(lèi)的一個(gè)虛函數(shù),而新的類(lèi)中,該虛函數(shù)的前面又增加了一個(gè)虛函數(shù);
            3) 新類(lèi)的后面增加了成員變量,并且新類(lèi)的成員函數(shù)將訪問(wèn)、修改這些變量;
            4) 修改了新類(lèi)的基類(lèi),基類(lèi)的大小發(fā)生了變化;

            等等,總言而之,一不小心,你的程序就會(huì)掉進(jìn)地獄。通過(guò)對(duì)這些引起出錯(cuò)的情況進(jìn)行分析,會(huì)發(fā)現(xiàn)其實(shí)只有三點(diǎn)變化會(huì)引起出錯(cuò),因?yàn)檫@三點(diǎn)是使用這個(gè)DLL的應(yīng)用程序在編譯時(shí)就需要確定的內(nèi)容,它們分別是:
            1) 類(lèi)的大小;
            2) 類(lèi)成員的偏移地址;
            3) 虛函數(shù)的順序。

            要想做一個(gè)可升級(jí)的DLL,必需避免以上三個(gè)問(wèn)題。所以以下三點(diǎn)用來(lái)使DLL遠(yuǎn)離地獄。

            1,不直接生成類(lèi)的實(shí)例。對(duì)于類(lèi)的大小,當(dāng)我們定義一個(gè)類(lèi)的實(shí)例,或使用new語(yǔ)句生成一個(gè)實(shí)例時(shí),內(nèi)存的大小是在編譯時(shí)決定的。要使應(yīng)用程序不依賴于類(lèi)的大小,只有一個(gè)辦法:應(yīng)用程序不生成類(lèi)的實(shí)例,使用DLL中的函數(shù)來(lái)生成。把導(dǎo)出類(lèi)的構(gòu)造函數(shù)定義為私有的(privated),在導(dǎo)出類(lèi)中提供靜態(tài)(static)成員函數(shù)(如NewInstance())用來(lái)生成類(lèi)的實(shí)例。因?yàn)镹ewInstance()函數(shù)在新的DLL中會(huì)被重新編譯,所以總能返回大小正確的實(shí)例內(nèi)存。

            2,不直接訪問(wèn)成員變量。應(yīng)用程序直接訪問(wèn)類(lèi)的成員變量時(shí)會(huì)用到該變量的偏移地址。所以避免偏移地址依賴的辦法就是不要直接訪問(wèn)成員變量。把所有的成員變量的訪問(wèn)控制都定義為保護(hù)型(protected)以上的級(jí)別,并為需要訪問(wèn)的成員變量定義Get或Set方法。Get或Set方法在編譯新DLL時(shí)會(huì)被重新編譯,所以總能訪問(wèn)到正確的變量位置。

            3,忘了虛函數(shù)吧,就算有也不要讓?xiě)?yīng)用程序直接訪問(wèn)它。因?yàn)轭?lèi)的構(gòu)造函數(shù)已經(jīng)是私有(privated)的了,所以應(yīng)用程序也不會(huì)去繼承這個(gè)類(lèi),也不會(huì)實(shí)現(xiàn)自己的多態(tài)。如果導(dǎo)出類(lèi)的父類(lèi)中有虛函數(shù),或設(shè)計(jì)需要(如類(lèi)工場(chǎng)之類(lèi)的框架),一定要把這些函數(shù)聲明為保護(hù)的(protected)以上的級(jí)別,并為應(yīng)用程序重新設(shè)計(jì)調(diào)用該慮函數(shù)的成員函數(shù)。這一點(diǎn)也類(lèi)似于對(duì)成員變量的處理。

            如果導(dǎo)出的類(lèi)能遵循以上三點(diǎn),那么以后對(duì)DLL的升級(jí)將可以認(rèn)為是安全的。

            如果對(duì)一個(gè)已經(jīng)存在的導(dǎo)出類(lèi)的DLL進(jìn)行維護(hù),同樣也要注意:不要改動(dòng)所有的成員變量,包括導(dǎo)出類(lèi)的父類(lèi),無(wú)論定義的順序還是數(shù)量;不要?jiǎng)铀械奶摵瘮?shù),無(wú)論順序還是數(shù)量。

            總結(jié)起來(lái),其實(shí)是一句話:導(dǎo)出類(lèi)的DLL不要導(dǎo)出除了函數(shù)以外的任何內(nèi)容。聽(tīng)起來(lái)是不是有點(diǎn)可笑呢!

            事實(shí)上,建議你在發(fā)布導(dǎo)出類(lèi)的DLL的時(shí)候,重新定義一個(gè)類(lèi)的聲明,這個(gè)聲明可以不管原來(lái)的類(lèi)里的成員變量之類(lèi)的,只把接口函數(shù)列在類(lèi)的聲明里,如下面的例子:
            class ClassInterface
            {
                   privated:
                          ClassInterface();
                   public:
                          static ClassInterface * NewInstance();
                          int GetXXX();
                          void SetXXX();
                          void Function();
            };

            使用該DLL的應(yīng)用程序用上面的定義作為ClassInterface的頭文件,便不會(huì)有任何可能導(dǎo)致的安全問(wèn)題。

            DLL地獄問(wèn)是歸根結(jié)底是因?yàn)镈LL當(dāng)初是作為函數(shù)級(jí)共享庫(kù)設(shè)計(jì)的,并不能真正提供一個(gè)類(lèi)所必需的信息。類(lèi)層上的程序復(fù)用只有Java和C#生成的類(lèi)文件才能做到。 

            久久精品一区二区三区不卡| 精品久久久久久无码不卡| 99热成人精品热久久669| 久久精品国产亚洲AV不卡| 亚洲国产精品无码久久久久久曰 | 久久艹国产| 久久精品中文騷妇女内射| 精品熟女少妇aⅴ免费久久| 亚洲精品乱码久久久久久蜜桃不卡 | 国产精品一久久香蕉国产线看| 久久亚洲中文字幕精品一区| 香蕉久久夜色精品升级完成| 久久精品夜色噜噜亚洲A∨| 欧美大香线蕉线伊人久久| 久久青青草视频| 久久九九久精品国产免费直播| 波多野结衣中文字幕久久| 久久国产免费直播| 亚洲精品无码久久不卡| 精品久久久无码中文字幕| 久久777国产线看观看精品| 久久久久亚洲av无码专区| 久久人人爽人人爽人人片av麻烦| 久久久久久青草大香综合精品| 久久99热只有频精品8| 日韩乱码人妻无码中文字幕久久| 亚洲欧美成人久久综合中文网 | 99久久99久久精品国产片果冻| 亚洲中文字幕久久精品无码喷水 | 亚州日韩精品专区久久久| 国产伊人久久| 国产午夜福利精品久久| 精品久久久久久无码中文野结衣| 无码国内精品久久人妻蜜桃| 亚洲国产精品高清久久久| 久久久无码人妻精品无码| 麻豆成人久久精品二区三区免费| 日韩人妻无码精品久久免费一 | 97久久超碰国产精品2021| 精品午夜久久福利大片| 国产视频久久|