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

            創作,也是一種學習的過程。

               :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::

            三年多前我在blog.csdn.net上半轉載過一篇文章,關于DLL入門的,不過內容有些凌亂,加上非原創,我打算重新寫一下,更突出“快速”,內容嘛,就比較精簡了,雖然精簡,看完后寫寫DLL肯定是沒問題的,相信我。

            一,快速生成一個DLL

            哦,對了,講解還是用VC++ 6.0(簡稱VC6)來講解。在VC6下new一個“Win32 Dynamic-Link Library”的Project,就叫“dllTest”吧,注意不要選擇“MFC AppWizard(dll)”,因為……這不屬于本文的內容,其實最重要的是:我不太懂(^_^),然后呢,選擇“An empty DLL project”即可。

            接下來就是創建以這兩個文件,并添加到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?。ㄟ@么簡單的程序不會通不過吧?)你就能看到你的生成目錄下有個文件,叫“dllTest.dll”,這就是我們第一個dll,沒幾行代碼,簡單吧,但麻雀雖小五臟俱全,我們使用Visual Studio提供的工具“Depends”打開這個文件看看,如圖所示,你能看到我們導出的這個函數,add。

            這個超微型的dll和普通程序的不同在于add函數的聲明,多了一個__declspec(dllexport),這個是關鍵,這個修飾符告訴VC6,這個函數需要導出,而前面的extern "C"的意思是這個函數是以C語言的標準來調用的,如果不加extern "C"的話,會怎么樣呢?自己試試看吧,Build后再用Depends看看生成的dll。

            二、使用這個DLL

            用VC6的向導創建一個“Win32 Console Application”的Project,叫“CallDllTest”,然后選擇“"A Hello, Word!"Application”,為什么用Console?簡單唄。

            把CallDllTest.cpp的內容換成以下:
            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>運行,就能看到結果了,打印了一個“5”出來,說明正常了,有點需要說明的是你得把前面生產的那個DLL和這次生成的EXE放在同一目錄下方可。代碼很簡單,思路就是定義函數指針類型,加載DLL,獲取要調用的函數的指針,然后調用,這么一個過程。

            三、靜態鏈接

            剛才使用LoadLibrary這個API加載DLL的方式叫動態鏈接,現在來介紹靜態鏈接,其實靜態鏈接用得還更多,只不過你不一定注意到而已,不信的話現在查看一下你剛創建的CallDllTest這個Project的Project Settings,如圖:


            注意我畫紅線的這個地方,Kernel32.lib,user32.lib和gdi32.lib等,這些lib叫做“導入庫”,并不包含真正有效的代碼,真正有效的代碼是存放在DLL中的,剛才我們所使用的LoadLibrary這個API,其實就是通過Kernel32.lib鏈接到Kernel32.dll中去的,也就是說LoadLibrary的實現存在于Kernel32.dll中,而我們是通過Kernel32.lib來找到它的,如果還有興趣的話,可以用Depends看看Kernel32.dll,看看里面是否有LoadLibrary,里面函數很多,別看花眼了哦,這可是Windows的核心庫之一,但可能你并沒有找到LoadLibrary,而是找到了,LoadLibraryA和LoadLibraryW,這很正常,因為很多需要使用到字符串的API,都有兩個版本,一個是窄字符版,一個是寬字符版,在程序編譯鏈接的時候,編譯器會根據你的選項來跟你選擇其中一個,這里暫時不展開了。

            那如何靜態使用前面生成的那個dllTest.dll呢?回到剛才dllTest.dll的那個生成目錄,你會發現一個叫dllTest.lib的文件,這就是我前面提到“導入庫”,現在改一下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這個文件得復制到CallDllTest這個Project的目錄下,否則會報找不到文件,執行結果如何?跟剛才是一樣的,這種方式就叫靜態鏈接,區別是什么?不需要在運行時調用LoadLibrary了,我個人覺得靜態鏈接用得更多一些。

            注意看代碼:

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

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

            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這個Project中,DLLTEST_EXPORTS是被定義了的,如圖:

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

            四、DLL中的main函數

            大家都知道C語言的程序是從main開始的,到了Windows環境下,則換成了WinMain,但也差不多,那DLL有沒有類似的入口呢?答案是肯定的,我們來改一下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;
            }

            你能看到一個叫“DllMain”的函數,它就是dll的入口,oh,當然了,我這篇文章所講的都是針對Windows操作系統的,Linux的可不一樣哦,甚至一般來說,Linux的DLL都不叫“DLL”。

            好了,你再按照前面的方法去調用下這個DLL,(記得拷貝這個dll到相應目錄去)這時候你就能看到執行結果中多了“process attach of DLL”和“process detach of DLL”,這是很顯而易見的,一個進程連接和斷開連接到這個dll的時候,DllMain就會被調用,且傳遞的ul_reason_for_call參數分別是DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH,那什么時候會有“DLL_THREAD_ATTACH”和“DLL_THREAD_DETACH”?當庫已經加載,創建新的線程和銷毀線程的時候,會分別使用THREAD_ATTACH和THREAD_DETACH參數來調DllMain。

            五、調用方式

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

            這是三種不同調用方式的比較:

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

            如果dll和exe的命名理解不一致,就有可能出錯。通常來說,我是習慣于用“extern "C"”和“__cdecl”的組合。

            六、導出變量

            前面只說了如何導出函數,那如何導出一個變量呢?方法類似,甚至可以說幾乎一樣,看代碼:

            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

            這樣,就可以了,和導出函數是沒什么差別吧?同樣,你也可以用Depends觀察生成的DLL,你會發現iExportInt這個導出符號,也就是我們導出的這個變量了?,F在看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,說明成功了。不知道你這個時候有沒有想到一個問題,那就是如果兩個進程同時調用dllTest.dll,而且同時修改和讀取iExportInt,那會不會亂掉呢?要不要做一個互斥鎖呢?答案是:不會,不需要。這得益于Windows內存管理的一個底層實現技術,叫Copy-on-write,在調用執行“iExportInt++”的時候,其實并非真正修改了DLL中的值,而是做了一份拷貝,通過內存映射機制,使得程序訪問的那個“iExportInt”其實是那份拷貝,而另一進程使用的也是自己的拷貝,互不干涉。

            七、共享內存

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

            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看看輸出情況。現在來改CallDllText.cpp。這里有非常要注意的兩個地方,一是“={0}”這個初始化,這是必須的,否則共享將不起作用,Microsoft規定了共享段一定需要先初始化,哪怕只是給第一個元素賦個隨便什么值都好,如這個例子,我只是給第一個元素賦值了個0;另一處要注意的是“/SECTION:shared,RWS"這個字符串,中間可別要有空格,否則你同樣會發現共享不起作用,我當時調試就很郁悶,因為我習慣在英文逗號后加個空格。

            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)會讓程序堵塞10秒鐘,這樣就可以運行多個程序的副本,來觀察共享的效果。

            八、結束

            DLL涵蓋的知識面相當廣,本文只是篇入門級的文章,介紹了一些比較實用的內容而已,如果要進一步學習,需要看看《Windows核心編程》這種經典著作,關于DLL的很多內容我都沒有提及到,比如DLL的導出方法其實有好幾種,我介紹的只是其中一種,但我認為我介紹的方法是最好用而且是最簡單的。我們寫程序,是為了實現某些應用,而不是為了炫耀某些技術,所以我是偏向于使用成熟,可靠和易行的方法。

            posted on 2009-09-24 16:35 Jiang Guogang 閱讀(647) 評論(0)  編輯 收藏 引用 所屬分類: Windows Programming
            亚洲伊人久久综合中文成人网| 一本久久a久久精品综合夜夜 | 精品国产综合区久久久久久| 欧美久久久久久精选9999| 久久成人18免费网站| 一本大道久久香蕉成人网| 精品综合久久久久久97| 久久久久久亚洲精品不卡| 久久精品国产亚洲网站| 久久亚洲精品中文字幕| 麻豆一区二区99久久久久| 久久亚洲国产欧洲精品一| 久久久久亚洲精品无码蜜桃 | 国产69精品久久久久777| 中文字幕久久波多野结衣av| 思思久久精品在热线热| 久久国语露脸国产精品电影| 91秦先生久久久久久久| 新狼窝色AV性久久久久久| 欧美午夜精品久久久久免费视 | 人妻久久久一区二区三区| 久久综合色区| 久久99精品久久久久久齐齐| 久久精品夜夜夜夜夜久久| 少妇熟女久久综合网色欲| 久久综合狠狠综合久久97色| 一本大道久久a久久精品综合| 久久久久人妻一区精品色| 久久久噜噜噜久久中文字幕色伊伊 | 日产精品久久久久久久性色| 欧美麻豆久久久久久中文| 国产精品九九久久免费视频| 久久久久亚洲AV无码去区首| 99久久精品免费看国产免费| 久久午夜伦鲁片免费无码| 无码人妻久久一区二区三区蜜桃 | 久久精品国产免费| 99久久精品国产麻豆| 久久99热只有频精品8| 国产精品久久久久久久久久免费| 久久精品国产91久久麻豆自制|