• <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 - 0,  comments - 5,  trackbacks - 0

             

            半年不能上網,最近網絡終于通了,終于可以更新博客了,寫點什么呢?決定最近寫一個編程技術系列,其內容是一些通用的編程技術。例如DLLCOMSocket,多線程等等。這些技術的特點就是使用廣泛,但是誤解很多;網上教程很多,但是幾乎沒有什么優質良品。我以近幾個月來的編程經驗發現,很有必要好好的總結一下這些編程技術了。一來對自己是總結提高,二來可以方便光顧我博客的朋友。

            好了,廢話少說,言歸正傳。第一篇就是《DLL編寫教程》,為什么起這么土的名字呢?為什么不叫《輕輕松松寫DLL》或者《DLL一日通》呢?或者更nb的《深入簡出DLL》呢?呵呵,常常上網搜索資料的弟兄自然知道。

            本文對通用的DLL技術做了一個總結,并提供了源代碼打包下載,下載地址為:

            http://www.blogjava.net/Files/wxb_nudt/DLL_SRC.rar

            DLL的優點

            簡單的說,dll有以下幾個優點:

            1)      節省內存。同一個軟件模塊,若是以源代碼的形式重用,則會被編譯到不同的可執行程序中,同時運行這些exe時這些模塊的二進制碼會被重復加載到內存中。如果使用dll,則只在內存中加載一次,所有使用該dll的進程會共享此塊內存(當然,像dll中的全局變量這種東西是會被每個進程復制一份的)。

            2)      不需編譯的軟件系統升級,若一個軟件系統使用了dll,則該dll被改變(函數名不變)時,系統升級只需要更換此dll即可,不需要重新編譯整個系統。事實上,很多軟件都是以這種方式升級的。例如我們經常玩的星際、魔獸等游戲也是這樣進行版本升級的。

            3)      Dll庫可以供多種編程語言使用,例如用c編寫的dll可以在vb中調用。這一點上DLL還做得很不夠,因此在dll的基礎上發明了COM技術,更好的解決了一系列問題。

            最簡單的dll

            開始寫dll之前,你需要一個c/c++編譯器和鏈接器,并關閉你的IDE。是的,把你的VCC++ BUILDER之類的東東都關掉,并打開你以往只用來記電話的記事本程序。不這樣做的話,你可能一輩子也不明白dll的真諦。我使用了VC自帶的cl編譯器和link鏈接器,它們一般都在vcbin目錄下。(若你沒有在安裝vc的時候選擇注冊環境變量,那么就立刻將它們的路徑加入path吧)如果你還是因為離開了IDE而害怕到哭泣的話,你可以關閉這個頁面并繼續去看《VC++技術內幕》之類無聊的書了。

            最簡單的dll并不比chelloworld難,只要一個DllMain函數即可,包含objbase.h頭文件(支持COM技術的一個頭文件)。若你覺得這個頭文件名字難記,那么用windows.H也可以。源代碼如下:dll_nolib.cpp

            #include <objbase.h>

            #include <iostream.h>

            BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

            {

                HANDLE g_hModule;

                switch(dwReason)

                {

                case DLL_PROCESS_ATTACH:

                   cout<<"Dll is attached!"<<endl;

                   g_hModule = (HINSTANCE)hModule;

                   break;

                case DLL_PROCESS_DETACH:

                   cout<<"Dll is detached!"<<endl;

                   g_hModule=NULL;

                   break;

                }

                return true;

            }

            其中DllMain是每個dll的入口函數,如同cmain函數一樣。DllMain帶有三個參數,hModule表示本dll的實例句柄(聽不懂就不理它,寫過windows程序的自然懂),dwReason表示dll當前所處的狀態,例如DLL_PROCESS_ATTACH表示dll剛剛被加載到一個進程中,DLL_PROCESS_DETACH表示dll剛剛從一個進程中卸載。當然還有表示加載到線程中和從線程中卸載的狀態,這里省略。最后一個參數是一個保留參數(目前和dll的一些狀態相關,但是很少使用)。

            從上面的程序可以看出,當dll被加載到一個進程中時,dll打印"Dll is attached!"語句;當dll從進程中卸載時,打印"Dll is detached!"語句。

            編譯dll需要以下兩條命令:

            cl /c dll_nolib.cpp

            這條命令會將cpp編譯為obj文件,若不使用/c參數則cl還會試圖繼續將obj鏈接為exe,但是這里是一個dll,沒有main函數,因此會報錯。不要緊,繼續使用鏈接命令。

            Link /dll dll_nolib.obj

            這條命令會生成dll_nolib.dll

            注意,因為編譯命令比較簡單,所以本文不討論nmake,有興趣的可以使用nmake,或者寫個bat批處理來編譯鏈接dll

            加載DLL(顯式調用)

            使用dll大體上有兩種方式,顯式調用和隱式調用。這里首先介紹顯式調用。編寫一個客戶端程序:dll_nolib_client.cpp

            #include <windows.h>

            #include <iostream.h>

            int main(void)

            {

                //加載我們的dll

                HINSTANCE hinst=::LoadLibrary("dll_nolib.dll"); 

                if (NULL != hinst)

                {

                   cout<<"dll loaded!"<<endl;

                }

                return 0;

            }

            注意,調用dll使用LoadLibrary函數,它的參數就是dll的路徑和名稱,返回值是dll的句柄。 使用如下命令編譯鏈接客戶端:

            Cl dll_nolib_client.cpp

            并執行dll_nolib_client.exe,得到如下結果:

            Dll is attached!

            dll loaded!

            Dll is detached!

            以上結果表明dll已經被客戶端加載過。但是這樣僅僅能夠將dll加載到內存,不能找到dll中的函數。

            使用dumpbin命令查看DLL中的函數

            Dumpbin命令可以查看一個dll中的輸出函數符號名,鍵入如下命令:

            Dumpbin –exports dll_nolib.dll

            通過查看,發現dll_nolib.dll并沒有輸出任何函數。

            如何在dll中定義輸出函數

            總體來說有兩種方法,一種是添加一個def定義文件,在此文件中定義dll中要輸出的函數;第二種是在源代碼中待輸出的函數前加上__declspec(dllexport)關鍵字。

            Def文件

            首先寫一個帶有輸出函數的dll,源代碼如下:dll_def.cpp

            #include <objbase.h>

            #include <iostream.h>

            void FuncInDll (void)

            {

                cout<<"FuncInDll is called!"<<endl;

            }

            BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

            {

                HANDLE g_hModule;

                switch(dwReason)

                {

                case DLL_PROCESS_ATTACH:

                   g_hModule = (HINSTANCE)hModule;

                   break;

                case DLL_PROCESS_DETACH:

                    g_hModule=NULL;

                    break;

                }

                return TRUE;

            }

            這個dlldef文件如下:dll_def.def

            ;

            ; dll_def module-definition file

            ;

            LIBRARY         dll_def.dll

            DESCRIPTION     '(c)2007-2009 Wang Xuebin'

            EXPORTS

                            FuncInDll @1 PRIVATE

            你會發現def的語法很簡單,首先是LIBRARY關鍵字,指定dll的名字;然后一個可選的關鍵字DESCRIPTION,后面寫上版權等信息(不寫也可以);最后是EXPORTS關鍵字,后面寫上dll中所有要輸出的函數名或變量名,然后接上@以及依次編號的數字(從1N),最后接上修飾符。

            用如下命令編譯鏈接帶有def文件的dll

            Cl /c dll_def.cpp

            Link /dll dll_def.obj /def:dll_def.def

            再調用dumpbin查看生成的dll_def.dll

            Dumpbin –exports dll_def.dll

            得到如下結果:

            Dump of file dll_def.dll

            File Type: DLL

             Section contains the following exports for dll_def.dll

                       0 characteristics

                46E4EE98 time date stamp Mon Sep 10 15:13:28 2007

                    0.00 version

                       1 ordinal base

                       1 number of functions

                       1 number of names

                ordinal hint RVA      name

                      1    0 00001000 FuncInDll

             Summary

                    2000 .data

                    1000 .rdata

                    1000 .reloc

                    6000 .text

            觀察這一行

                      1    0 00001000 FuncInDll

            會發現該dll輸出了函數FuncInDll

            顯式調用DLL中的函數

            寫一個dll_def.dll的客戶端程序:dll_def_client.cpp

            #include <windows.h>

            #include <iostream.h>

            int main(void)

            {

                //定義一個函數指針

                typedef void (* DLLWITHLIB )(void); 

                //定義一個函數指針變量

                DLLWITHLIB pfFuncInDll = NULL; 

                //加載我們的dll

                HINSTANCE hinst=::LoadLibrary("dll_def.dll"); 

                if (NULL != hinst)

                {

                   cout<<"dll loaded!"<<endl;

                }

                //找到dllFuncInDll函數

                pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll"); 

                //調用dll里的函數

                if (NULL != pfFuncInDll)

                {

                   (*pfFuncInDll)();  

                }

                return 0;

            }

            有兩個地方值得注意,第一是函數指針的定義和使用,不懂的隨便找本c++書看看;第二是GetProcAddress的使用,這個API是用來查找dll中的函數地址的,第一個參數是DLL的句柄,即LoadLibrary返回的句柄,第二個參數是dll中的函數名稱,即dumpbin中輸出的函數名(注意,這里的函數名稱指的是編譯后的函數名,不一定等于dll源代碼中的函數名)。

            編譯鏈接這個客戶端程序,并執行會得到:

            dll loaded!

            FuncInDll is called!

            這表明客戶端成功調用了dll中的函數FuncInDll

            __declspec(dllexport)

            為每個dlldef顯得很繁雜,目前def使用已經比較少了,更多的是使用__declspec(dllexport)在源代碼中定義dll的輸出函數。

            Dll寫法同上,去掉def文件,并在每個要輸出的函數前面加上聲明__declspec(dllexport),例如:

            __declspec(dllexport) void FuncInDll (void)

            這里提供一個dll源程序dll_withlib.cpp,然后編譯鏈接。鏈接時不需要指定/DEF:參數,直接加/DLL參數即可,

            Cl /c dll_withlib.cpp

            Link /dll dll_withlib.obj

            然后使用dumpbin命令查看,得到:

            1    0 00001000 ?FuncInDll@@YAXXZ

            可知編譯后的函數名為?FuncInDll@@YAXXZ,而并不是FuncInDll,這是因為c++編譯器基于函數重載的考慮,會更改函數名,這樣使用顯式調用的時候,也必須使用這個更改后的函數名,這顯然給客戶帶來麻煩。為了避免這種現象,可以使用extern “C”指令來命令c++編譯器以c編譯器的方式來命名該函數。修改后的函數聲明為:

            extern "C" __declspec(dllexport) void FuncInDll (void)

            dumpbin命令結果:

            1    0 00001000 FuncInDll

            這樣,顯式調用時只需查找函數名為FuncInDll的函數即可成功。

            隱式調用DLL

            顯式調用顯得非常復雜,每次都要LoadLibrary,并且每個函數都必須使用GetProcAddress來得到函數指針,這對于大量使用dll函數的客戶是一種困擾。而隱式調用能夠像使用c函數庫一樣使用dll中的函數,非常方便快捷。

            下面是一個隱式調用的例子:dll包含兩個文件dll_withlibAndH.cppdll_withlibAndH.h

            代碼如下:dll_withlibAndH.h

            extern "C" __declspec(dllexport) void FuncInDll (void);

            dll_withlibAndH.cpp

            #include <objbase.h>

            #include <iostream.h>

            #include "dll_withLibAndH.h"http://看到沒有,這就是我們增加的頭文件

            extern "C" __declspec(dllexport) void FuncInDll (void)

            {

                cout<<"FuncInDll is called!"<<endl;

            }

            BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

            {

                HANDLE g_hModule;

                switch(dwReason)

                {

                case DLL_PROCESS_ATTACH:

                   g_hModule = (HINSTANCE)hModule;

                   break;

                case DLL_PROCESS_DETACH:

                    g_hModule=NULL;

                    break;

                }

                return TRUE;

            }

            編譯鏈接命令:

            Cl /c dll_withlibAndH.cpp

            Link /dll dll_withlibAndH.obj

            在進行隱式調用的時候需要在客戶端引入頭文件,并在鏈接時指明dll對應的lib文件(dll只要有函數輸出,則鏈接的時候會產生一個與dll同名的lib文件)位置和名稱。然后如同調用api函數庫中的函數一樣調用dll中的函數,不需要顯式的LoadLibraryGetProcAddress。使用最為方便。客戶端代碼如下:dll_withlibAndH_client.cpp

            #include "dll_withLibAndH.h"

            //注意路徑,加載 dll的另一種方法是 Project | setting | link 設置里

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

            int main(void)

            {

                FuncInDll();//只要這樣我們就可以調用dll里的函數了

                return 0;

            }

            __declspec(dllexport)__declspec(dllimport)配對使用

            上面一種隱式調用的方法很不錯,但是在調用DLL中的對象和重載函數時會出現問題。因為使用extern “C”修飾了輸出函數,因此重載函數肯定是會出問題的,因為它們都將被編譯為同一個輸出符號串(c語言是不支持重載的)。

            事實上不使用extern “C”是可行的,這時函數會被編譯為c++符號串,例如(?FuncInDll@@YAXH@Z ?FuncInDll@@YAXXZ),當客戶端也是c++時,也能正確的隱式調用。

            這時要考慮一個情況:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函數,但同時DLL2也是一個DLL,也要輸出一些函數供Client.CPP使用。那么在DLL2中如何聲明所有的函數,其中包含了從DLL1中引入的函數,還包括自己要輸出的函數。這個時候就需要同時使用__declspec(dllexport)__declspec(dllimport)了。前者用來修飾本dll中的輸出函數,后者用來修飾從其它dll中引入的函數。

            所有的源代碼包括DLL1.HDLL1.CPPDLL2.HDLL2.CPPClient.cpp。源代碼可以在下載的包中找到。你可以編譯鏈接并運行試試。

            值得關注的是DLL1DLL2中都使用的一個編碼方法,見DLL2.H

            #ifdef DLL_DLL2_EXPORTS

            #define DLL_DLL2_API __declspec(dllexport)

            #else

            #define DLL_DLL2_API __declspec(dllimport)

            #endif

            DLL_DLL2_API void FuncInDll2(void);

            DLL_DLL2_API void FuncInDll2(int);

            在頭文件中以這種方式定義宏DLL_DLL2_EXPORTSDLL_DLL2_API,可以確保DLL端的函數用__declspec(dllexport)修飾,而客戶端的函數用__declspec(dllimport)修飾。當然,記得在編譯dll時加上參數/D “DLL_DLL2_EXPORTS”,或者干脆就在dllcpp文件第一行加上#define DLL_DLL2_EXPORTS

            VC生成的代碼也是這樣的!事實證明,我是抄襲它的,hoho

            DLL中的全局變量和對象

            解決了重載函數的問題,那么dll中的全局變量和對象都不是問題了,只是有一點語法需要注意。如源代碼所示:dll_object.h

            #ifdef DLL_OBJECT_EXPORTS

            #define DLL_OBJECT_API __declspec(dllexport)

            #else

            #define DLL_OBJECT_API __declspec(dllimport)

            #endif

            DLL_OBJECT_API void FuncInDll(void);

            extern DLL_OBJECT_API int g_nDll;

            class DLL_OBJECT_API CDll_Object {

            public:

                CDll_Object(void);

                show(void);

                // TODO: add your methods here.

            };

            Cpp文件dll_object.cpp如下:

            #define DLL_OBJECT_EXPORTS

            #include <objbase.h>

            #include <iostream.h>

            #include "dll_object.h"

            DLL_OBJECT_API void FuncInDll(void)

            {

                cout<<"FuncInDll is called!"<<endl;

            }

            DLL_OBJECT_API int g_nDll = 9;

            CDll_Object::CDll_Object()

            {

                cout<<"ctor of CDll_Object"<<endl;

            }

            CDll_Object::show()

            {

                cout<<"function show in class CDll_Object"<<endl;

            }

            BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

            {

                HANDLE g_hModule;

                switch(dwReason)

                {

                case DLL_PROCESS_ATTACH:

                   g_hModule = (HINSTANCE)hModule;

                   break;

                case DLL_PROCESS_DETACH:

                    g_hModule=NULL;

                    break;

                }

                return TRUE;

            }

            編譯鏈接完后Dumpbin一下,可以看到輸出了5個符號:

            1    0 00001040 ??0CDll_Object@@QAE@XZ

             2    1 00001000 ??4CDll_Object@@QAEAAV0@ABV0@@Z

             3    2 00001020 ?FuncInDll@@YAXXZ

             4    3 00008040 ?g_nDll@@3HA

             5    4 00001069 ?show@CDll_Object@@QAEHXZ

            它們分別代表類CDll_Object,類的構造函數,FuncInDll函數,全局變量g_nDll和類的成員函數show。下面是客戶端代碼:dll_object_client.cpp

            #include "dll_object.h"

            #include <iostream.h>

            //注意路徑,加載 dll的另一種方法是 Project | setting | link 設置里

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

            int main(void)

            {

                cout<<"call dll"<<endl;

                cout<<"call function in dll"<<endl;

                FuncInDll();//只要這樣我們就可以調用dll里的函數了

                cout<<"global var in dll g_nDll ="<<g_nDll<<endl;

                cout<<"call member function of class CDll_Object in dll"<<endl;

                CDll_Object obj;

                obj.show();

                return 0;

            }

            運行這個客戶端可以看到:

            call dll

            call function in dll

            FuncInDll is called!

            global var in dll g_nDll =9

            call member function of class CDll_Object in dll

            ctor of CDll_Object

            function show in class CDll_Object

            可知,在客戶端成功的訪問了dll中的全局變量,并創建了dll中定義的C++對象,還調用了該對象的成員函數。

            中間的小結

            牢記一點,說到底,DLL是對應C語言的動態鏈接技術,在輸出C函數和變量時顯得方便快捷;而在輸出C++類、函數時需要通過各種手段,而且也并沒有完美的解決方案,除非客戶端也是c++

            記住,只有COM是對應C++語言的技術。

            下面開始對各各問題一一小結。

            顯式調用和隱式調用

            何時使用顯式調用?何時使用隱式調用?我認為,只有一個時候使用顯式調用是合理的,就是當客戶端不是C/C++的時候。這時是無法隱式調用的。例如用VB調用C++寫的dll。(VB我不會,所以沒有例子)

            Def__declspec(dllexport)

            其實def的功能相當于extern “C” __declspec(dllexport),所以它也僅能處理C函數,而不能處理重載函數。而__declspec(dllexport)__declspec(dllimport)配合使用能夠適應任何情況,因此__declspec(dllexport)是更為先進的方法。所以,目前普遍的看法是不使用def文件,我也同意這個看法。

            從其它語言調用DLL

            從其它編程語言中調用DLL,有兩個最大的問題,第一個就是函數符號的問題,前面已經多次提過了。這里有個兩難選擇,若使用extern “C”,則函數名稱保持不變,調用較方便,但是不支持函數重載等一系列c++功能;若不使用extern “C”,則調用前要查看編譯后的符號,非常不方便。

            第二個問題就是函數調用壓棧順序的問題,即__cdecl__stdcall的問題。__cdecl是常規的C/C++調用約定,這種調用約定下,函數調用后棧的清理工作是由調用者完成的。__stdcall是標準的調用約定,即這些函數將在返回到調用者之前將參數從棧中刪除。

            這兩個問題DLL都不能很好的解決,只能說湊合著用。但是在COM中,都得到了完美的解決。所以,要在Windows平臺實現語言無關性,還是只有使用COM中間件。

            總而言之,除非客戶端也使用C++,否則dll是不便于支持函數重載、類等c++特性的。DLLc函數的支持很好,我想這也是為什么windows的函數庫使用Cdll實現的理由之一。

            VC中編寫DLL

            VC中創建、編譯、鏈接dll是非常方便的,點擊fileàNewàProjectàWin32 Dynamic-Link Library,輸入dll名稱dll_InVC然后點擊確定。然后選擇A DLL that export some symbols,點擊Finish。即可得到一個完整的DLL

            仔細觀察其源代碼,是不是有很多地方似曾相識啊,哈哈!

            posted on 2010-07-20 18:33 saha 閱讀(395) 評論(0)  編輯 收藏 引用

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            常用鏈接

            留言簿

            文章分類

            文章檔案

            收藏夾

            搜索

            •  

            最新評論

            国内精品久久久久久久影视麻豆 | 久久精品国产72国产精福利| 99精品国产在热久久无毒不卡 | 国产激情久久久久影院| 久久精品国产第一区二区三区| 国内精品久久久久影院优 | 久久亚洲精品人成综合网| 狠狠色婷婷久久一区二区| 久久久久AV综合网成人| 天天久久狠狠色综合| 伊色综合久久之综合久久| 精品久久久久久久无码| 久久九九免费高清视频| 国产成人无码精品久久久性色| 久久精品这里热有精品| 久久无码专区国产精品发布| 久久无码av三级| 亚洲精品无码久久久久| 久久精品视屏| 国产精品青草久久久久婷婷| 久久精品青青草原伊人| 久久伊人精品青青草原日本| 久久精品国产亚洲77777| 色婷婷噜噜久久国产精品12p| 久久精品天天中文字幕人妻| 久久噜噜久久久精品66| 亚洲国产成人久久综合一 | 精品久久久久久亚洲精品| 久久久久久免费视频| 久久婷婷五月综合97色直播| 久久综合中文字幕| 久久免费小视频| 国产精品久久免费| 国产午夜精品久久久久免费视| 久久久噜噜噜久久中文字幕色伊伊| 久久精品成人| 国产精品久久久久久久久久免费| 九九久久自然熟的香蕉图片| 国色天香久久久久久久小说| 2021久久精品免费观看| 影音先锋女人AV鲁色资源网久久|