• <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>

            Jiang's C++ Space

            創(chuàng)作,也是一種學(xué)習(xí)的過(guò)程。

               :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::

            三年多前我在blog.csdn.net上半轉(zhuǎn)載過(guò)一篇文章,關(guān)于DLL入門(mén)的,不過(guò)內(nèi)容有些凌亂,加上非原創(chuàng),我打算重新寫(xiě)一下,更突出“快速”,內(nèi)容嘛,就比較精簡(jiǎn)了,雖然精簡(jiǎn),看完后寫(xiě)寫(xiě)DLL肯定是沒(méi)問(wèn)題的,相信我。

            一,快速生成一個(gè)DLL

            哦,對(duì)了,講解還是用VC++ 6.0(簡(jiǎn)稱VC6)來(lái)講解。在VC6下new一個(gè)“Win32 Dynamic-Link Library”的Project,就叫“dllTest”吧,注意不要選擇“MFC AppWizard(dll)”,因?yàn)?#8230;…這不屬于本文的內(nèi)容,其實(shí)最重要的是:我不太懂(^_^),然后呢,選擇“An empty DLL project”即可。

            接下來(lái)就是創(chuàng)建以這兩個(gè)文件,并添加到Project中去:

            lib.h

            #ifndef LIB_H
            #define LIB_H
            extern "C"  int __declspec(dllexport) add(int x,int y);
            #endif

            lib.cpp

            #include "lib.h"
            int add(int x,int y)
            {
                
            return x + y;
            }

            Build!(這么簡(jiǎn)單的程序不會(huì)通不過(guò)吧?)你就能看到你的生成目錄下有個(gè)文件,叫“dllTest.dll”,這就是我們第一個(gè)dll,沒(méi)幾行代碼,簡(jiǎn)單吧,但麻雀雖小五臟俱全,我們使用Visual Studio提供的工具“Depends”打開(kāi)這個(gè)文件看看,如圖所示,你能看到我們導(dǎo)出的這個(gè)函數(shù),add。

            這個(gè)超微型的dll和普通程序的不同在于add函數(shù)的聲明,多了一個(gè)__declspec(dllexport),這個(gè)是關(guān)鍵,這個(gè)修飾符告訴VC6,這個(gè)函數(shù)需要導(dǎo)出,而前面的extern "C"的意思是這個(gè)函數(shù)是以C語(yǔ)言的標(biāo)準(zhǔn)來(lái)調(diào)用的,如果不加extern "C"的話,會(huì)怎么樣呢?自己試試看吧,Build后再用Depends看看生成的dll。

            二、使用這個(gè)DLL

            用VC6的向?qū)?chuàng)建一個(gè)“Win32 Console Application”的Project,叫“CallDllTest”,然后選擇“"A Hello, Word!"Application”,為什么用Console?簡(jiǎn)單唄。

            把CallDllTest.cpp的內(nèi)容換成以下:
            CallDllTest.cpp

            #include "stdafx.h"
            #include 
            "windows.h"

            typedef 
            int ( * lpAddFun)(int,int);

            int main(int argc, char* argv[])
            {
                HINSTANCE hDll;   
            //DLL handle 
                lpAddFun addFun;  //Function pointer
                hDll = LoadLibrary("dllTest.dll");
                
            if (hDll != NULL)
                {
              addFun 
            = (lpAddFun)GetProcAddress(hDll,"add");   
              
            if(addFun!=NULL)
              {
               
            int result =  addFun(2,3);    
               printf(
            "%d",result);
              }
              FreeLibrary(hDll);
                }   
                
            return 0;
            }

            Build,然后<Ctrl>+<F5>運(yùn)行,就能看到結(jié)果了,打印了一個(gè)“5”出來(lái),說(shuō)明正常了,有點(diǎn)需要說(shuō)明的是你得把前面生產(chǎn)的那個(gè)DLL和這次生成的EXE放在同一目錄下方可。代碼很簡(jiǎn)單,思路就是定義函數(shù)指針類型,加載DLL,獲取要調(diào)用的函數(shù)的指針,然后調(diào)用,這么一個(gè)過(guò)程。

            三、靜態(tài)鏈接

            剛才使用LoadLibrary這個(gè)API加載DLL的方式叫動(dòng)態(tài)鏈接,現(xiàn)在來(lái)介紹靜態(tài)鏈接,其實(shí)靜態(tài)鏈接用得還更多,只不過(guò)你不一定注意到而已,不信的話現(xiàn)在查看一下你剛創(chuàng)建的CallDllTest這個(gè)Project的Project Settings,如圖:


            注意我畫(huà)紅線的這個(gè)地方,Kernel32.lib,user32.lib和gdi32.lib等,這些lib叫做“導(dǎo)入庫(kù)”,并不包含真正有效的代碼,真正有效的代碼是存放在DLL中的,剛才我們所使用的LoadLibrary這個(gè)API,其實(shí)就是通過(guò)Kernel32.lib鏈接到Kernel32.dll中去的,也就是說(shuō)LoadLibrary的實(shí)現(xiàn)存在于Kernel32.dll中,而我們是通過(guò)Kernel32.lib來(lái)找到它的,如果還有興趣的話,可以用Depends看看Kernel32.dll,看看里面是否有LoadLibrary,里面函數(shù)很多,別看花眼了哦,這可是Windows的核心庫(kù)之一,但可能你并沒(méi)有找到LoadLibrary,而是找到了,LoadLibraryA和LoadLibraryW,這很正常,因?yàn)楹芏嘈枰褂玫阶址腁PI,都有兩個(gè)版本,一個(gè)是窄字符版,一個(gè)是寬字符版,在程序編譯鏈接的時(shí)候,編譯器會(huì)根據(jù)你的選項(xiàng)來(lái)跟你選擇其中一個(gè),這里暫時(shí)不展開(kāi)了。

            那如何靜態(tài)使用前面生成的那個(gè)dllTest.dll呢?回到剛才dllTest.dll的那個(gè)生成目錄,你會(huì)發(fā)現(xiàn)一個(gè)叫dllTest.lib的文件,這就是我前面提到“導(dǎo)入庫(kù)”,現(xiàn)在改一下CallDllTest.cpp。
            CallDllTest.cpp

            #include "stdafx.h"

            #pragma comment(lib, 
            "dllTest.lib")

            extern "C"  int __declspec(dllimport) add(int x,int y);

            int main(int argc, char* argv[])
            {
             
            int result =  add(2,3);    
             printf(
            "%d",result);
                
            return 0;
            }

            dllTest.lib這個(gè)文件得復(fù)制到CallDllTest這個(gè)Project的目錄下,否則會(huì)報(bào)找不到文件,執(zhí)行結(jié)果如何?跟剛才是一樣的,這種方式就叫靜態(tài)鏈接,區(qū)別是什么?不需要在運(yùn)行時(shí)調(diào)用LoadLibrary了,我個(gè)人覺(jué)得靜態(tài)鏈接用得更多一些。

            注意看代碼:

            extern "C"  int __declspec(dllimport) add(int x,int y);

            這句話很關(guān)鍵,__declspec(dllimport)對(duì)應(yīng)了DllTest中的__declspec(dllexport),表示該函數(shù)將從dll中導(dǎo)入,那你也許要問(wèn)了:“我怎么知道這個(gè)dll中有這個(gè)函數(shù)?并且還知道這個(gè)函數(shù)的參數(shù)類型和返回值類型?該不會(huì)也是用Depends去看吧?”呃……這怎么說(shuō)呢?如果這個(gè)dll是你寫(xiě)的,你當(dāng)然知道啦,但如果這個(gè)Dll不是你寫(xiě)的話,它的作者往往會(huì)提供一個(gè)頭文件,就好像你要使用LoadLibrary,你得包含“windows.h”這個(gè)頭文件一樣,否則就出現(xiàn)符號(hào)未定義的編譯錯(cuò)誤,那么我們改一下lib.h這個(gè)頭文件。

            lib.h

            #ifndef LIB_H
            #define LIB_H
            #ifdef DLLTEST_EXPORTS
            extern "C" int __declspec(dllexport) add(int x,int y);
            #else
            extern "C" int __declspec(dllimport) add(int x,int y);
            #endif
            #endif

            在DllTest這個(gè)Project中,DLLTEST_EXPORTS是被定義了的,如圖:

            所以使用dllexport,而在別的Project中,則使用dllimport。在CallDllTest中include這個(gè)lib.h,就可以了,當(dāng)然你也可以寫(xiě)得更好,我這里僅僅是demo。

            四、DLL中的main函數(shù)

            大家都知道C語(yǔ)言的程序是從main開(kāi)始的,到了Windows環(huán)境下,則換成了WinMain,但也差不多,那DLL有沒(méi)有類似的入口呢?答案是肯定的,我們來(lái)改一下DllTest的lib.cpp。

            lib.cpp

            #include "lib.h"
            #include 
            "windows.h"
            #include 
            "stdio.h"

            BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
            {
                
            switch (ul_reason_for_call)
                {
                
            case DLL_PROCESS_ATTACH:
              printf(
            "\nprocess attach of DLL");
              
            break;
                
            case DLL_THREAD_ATTACH:
              printf(
            "\nthread attach of DLL");
              
            break;
                
            case DLL_THREAD_DETACH:
              printf(
            "\nthread detach of DLL");
              
            break;
                
            case DLL_PROCESS_DETACH:
              printf(
            "\nprocess detach of DLL");
              
            break;
                }
                
            return TRUE;
            }

            int add(int x,int y)
            {
                
            return x + y;
            }

            你能看到一個(gè)叫“DllMain”的函數(shù),它就是dll的入口,oh,當(dāng)然了,我這篇文章所講的都是針對(duì)Windows操作系統(tǒng)的,Linux的可不一樣哦,甚至一般來(lái)說(shuō),Linux的DLL都不叫“DLL”。

            好了,你再按照前面的方法去調(diào)用下這個(gè)DLL,(記得拷貝這個(gè)dll到相應(yīng)目錄去)這時(shí)候你就能看到執(zhí)行結(jié)果中多了“process attach of DLL”和“process detach of DLL”,這是很顯而易見(jiàn)的,一個(gè)進(jìn)程連接和斷開(kāi)連接到這個(gè)dll的時(shí)候,DllMain就會(huì)被調(diào)用,且傳遞的ul_reason_for_call參數(shù)分別是DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH,那什么時(shí)候會(huì)有“DLL_THREAD_ATTACH”和“DLL_THREAD_DETACH”?當(dāng)庫(kù)已經(jīng)加載,創(chuàng)建新的線程和銷毀線程的時(shí)候,會(huì)分別使用THREAD_ATTACH和THREAD_DETACH參數(shù)來(lái)調(diào)DllMain。

            五、調(diào)用方式

            前面我們導(dǎo)入,導(dǎo)出函數(shù)的時(shí)候都加了一個(gè)“extern "C"”,那不加會(huì)怎么樣呢?如果再涉及到這幾個(gè)修飾符:__stdcall,__cdecl和__fastcall,那又會(huì)怎樣呢?我畫(huà)了兩個(gè)表,大家可以比較下,體會(huì)下。

            這是三種不同調(diào)用方式的比較:

            這是命名修飾在不同方式下的比較:

            如果dll和exe的命名理解不一致,就有可能出錯(cuò)。通常來(lái)說(shuō),我是習(xí)慣于用“extern "C"”和“__cdecl”的組合。

            六、導(dǎo)出變量

            前面只說(shuō)了如何導(dǎo)出函數(shù),那如何導(dǎo)出一個(gè)變量呢?方法類似,甚至可以說(shuō)幾乎一樣,看代碼:

            lib.cpp

            #include "lib.h"
            #include 
            "windows.h"
            #include 
            "stdio.h"

            int iExportInt;

            BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
            {
                
            switch (ul_reason_for_call)
                {
                
            case DLL_PROCESS_ATTACH:
              iExportInt 
            = 112;
              
            break;
                }
                
            return TRUE;
            }

            lib.h

            #ifndef LIB_H
            #define LIB_H
            #ifdef DLLTEST_EXPORTS
            extern "C" int __declspec(dllexport) iExportInt;
            #else
            extern "C" int __declspec(dllimport) iExportInt;
            #endif
            #endif

            這樣,就可以了,和導(dǎo)出函數(shù)是沒(méi)什么差別吧?同樣,你也可以用Depends觀察生成的DLL,你會(huì)發(fā)現(xiàn)iExportInt這個(gè)導(dǎo)出符號(hào),也就是我們導(dǎo)出的這個(gè)變量了。現(xiàn)在看CallDllTest的代碼:

            CallDllTest.cpp

            #include "stdafx.h"
            #include 
            "lib.h"

            #pragma comment(lib, 
            "dllTest.lib")

            int main(int argc, char* argv[])
            {
             printf(
            "%d\n", iExportInt);
             iExportInt
            ++;
             printf(
            "%d\n", iExportInt);
             
            return 0;
            }

            輸出是什么?112和113,說(shuō)明成功了。不知道你這個(gè)時(shí)候有沒(méi)有想到一個(gè)問(wèn)題,那就是如果兩個(gè)進(jìn)程同時(shí)調(diào)用dllTest.dll,而且同時(shí)修改和讀取iExportInt,那會(huì)不會(huì)亂掉呢?要不要做一個(gè)互斥鎖呢?答案是:不會(huì),不需要。這得益于Windows內(nèi)存管理的一個(gè)底層實(shí)現(xiàn)技術(shù),叫Copy-on-write,在調(diào)用執(zhí)行“iExportInt++”的時(shí)候,其實(shí)并非真正修改了DLL中的值,而是做了一份拷貝,通過(guò)內(nèi)存映射機(jī)制,使得程序訪問(wèn)的那個(gè)“iExportInt”其實(shí)是那份拷貝,而另一進(jìn)程使用的也是自己的拷貝,互不干涉。

            七、共享內(nèi)存

            緊接著前面這個(gè)問(wèn)題,那如果我企圖通過(guò)DLL來(lái)讓不同進(jìn)程共享一段內(nèi)存,而不是讓系統(tǒng)執(zhí)行默認(rèn)的Copy-on-write操作,那怎么辦呢?有辦法!這是Microsoft提供的一個(gè)方法,個(gè)人覺(jué)得是個(gè)不錯(cuò)的進(jìn)程間通信的方法,比如兩個(gè)進(jìn)程需要交換一大塊數(shù)據(jù),而且這一大塊數(shù)據(jù)變化比較頻繁,通過(guò)數(shù)據(jù)庫(kù)啊,文件啊,就顯得有點(diǎn)慢,如果通過(guò)socket啊,管道啊,就顯得有些不直接,還是直接使用共享內(nèi)存來(lái)得直接,但很多人并不知道這個(gè)功能,我這里跟大家分享下。

            lib.h

            #ifndef LIB_H
            #define LIB_H
            #ifdef DLLTEST_EXPORTS
            extern "C" __declspec(dllexport) unsigned char dataChunk[100000];
            #else
            extern "C" __declspec(dllimport) unsigned char dataChunk[100000];
            #endif
            #endif

            lib.cpp

            #include "lib.h"

            #pragma data_seg(
            "shared")
            unsigned 
            char dataChunk[100000]={0};
            #pragma data_seg()

            #pragma comment(linker, 
            "/SECTION:shared,RWS")

            Build一下,然后用Depends看看輸出情況。現(xiàn)在來(lái)改CallDllText.cpp。這里有非常要注意的兩個(gè)地方,一是“={0}”這個(gè)初始化,這是必須的,否則共享將不起作用,Microsoft規(guī)定了共享段一定需要先初始化,哪怕只是給第一個(gè)元素賦個(gè)隨便什么值都好,如這個(gè)例子,我只是給第一個(gè)元素賦值了個(gè)0;另一處要注意的是“/SECTION:shared,RWS"這個(gè)字符串,中間可別要有空格,否則你同樣會(huì)發(fā)現(xiàn)共享不起作用,我當(dāng)時(shí)調(diào)試就很郁悶,因?yàn)槲伊?xí)慣在英文逗號(hào)后加個(gè)空格。

            CallDllText.cpp

            #include "stdafx.h"
            #include 
            "windows.h"
            #include 
            "lib.h"

            #pragma comment(lib, 
            "dllTest.lib")

            int main(int argc, char* argv[])
            {
             printf(
            "%d\n", dataChunk[0]++);
             Sleep(
            10000);
                
            return 0;
            }

            Sleep(10000)會(huì)讓程序堵塞10秒鐘,這樣就可以運(yùn)行多個(gè)程序的副本,來(lái)觀察共享的效果。

            八、結(jié)束

            DLL涵蓋的知識(shí)面相當(dāng)廣,本文只是篇入門(mén)級(jí)的文章,介紹了一些比較實(shí)用的內(nèi)容而已,如果要進(jìn)一步學(xué)習(xí),需要看看《Windows核心編程》這種經(jīng)典著作,關(guān)于DLL的很多內(nèi)容我都沒(méi)有提及到,比如DLL的導(dǎo)出方法其實(shí)有好幾種,我介紹的只是其中一種,但我認(rèn)為我介紹的方法是最好用而且是最簡(jiǎn)單的。我們寫(xiě)程序,是為了實(shí)現(xiàn)某些應(yīng)用,而不是為了炫耀某些技術(shù),所以我是偏向于使用成熟,可靠和易行的方法。

            posted on 2009-09-24 16:35 Jiang Guogang 閱讀(647) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Windows Programming
            日韩AV毛片精品久久久| 久久永久免费人妻精品下载| 久久久久亚洲AV无码专区网站| 亚洲伊人久久综合中文成人网| 国产精品久久久久久吹潮| 久久青青草原国产精品免费| 要久久爱在线免费观看| 久久成人精品视频| 伊人久久大香线蕉av一区| 久久无码av三级| 伊人久久大香线蕉AV色婷婷色| 精品久久久久久99人妻| 无遮挡粉嫩小泬久久久久久久| 久久久久无码中| 日本道色综合久久影院| 久久久免费精品re6| 亚洲精品第一综合99久久| 香蕉久久一区二区不卡无毒影院| 久久久久久久久66精品片| 国产午夜福利精品久久| 狠狠色噜噜狠狠狠狠狠色综合久久| 成人综合久久精品色婷婷| 精品人妻伦九区久久AAA片69| 久久精品黄AA片一区二区三区| 久久无码专区国产精品发布| 国内精品久久久久久久涩爱 | 少妇内射兰兰久久| 亚洲国产成人精品91久久久| 一级做a爰片久久毛片16| 国产精品久久久久影院嫩草| 亚洲狠狠婷婷综合久久久久| 久久精品国产精品亚洲精品| 国产精品美女久久福利网站| 久久天天躁狠狠躁夜夜2020一| 亚洲精品99久久久久中文字幕 | 伊人久久精品线影院| 国产精品免费福利久久| 无码日韩人妻精品久久蜜桃 | 秋霞久久国产精品电影院| 久久青青草原国产精品免费| 亚洲乱亚洲乱淫久久|