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

            逛奔的蝸牛

            我不聰明,但我會(huì)很努力

               ::  :: 新隨筆 ::  ::  :: 管理 ::

            DLL的優(yōu)點(diǎn)
            簡單的說,dll有以下幾個(gè)優(yōu)點(diǎn):

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

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

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

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

            最簡單的dll并不比c的helloworld難,只要一個(gè)DllMain函數(shù)即可,包含objbase.h頭文件(支持COM技術(shù)的一個(gè)頭文件)。若你覺得這個(gè)頭文件名字難記,那么用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是每個(gè)dll的入口函數(shù),如同c的main函數(shù)一樣。DllMain帶有三個(gè)參數(shù),hModule表示本dll的實(shí)例句柄(聽不 懂就不理它,寫過windows程序的自然懂),dwReason表示dll當(dāng)前所處的狀態(tài),例如DLL_PROCESS_ATTACH表示dll剛剛被 加載到一個(gè)進(jìn)程中,DLL_PROCESS_DETACH表示dll剛剛從一個(gè)進(jìn)程中卸載。當(dāng)然還有表示加載到線程中和從線程中卸載的狀態(tài),這里省略。最 后一個(gè)參數(shù)是一個(gè)保留參數(shù)(目前和dll的一些狀態(tài)相關(guān),但是很少使用)。

            從上面的程序可以看出,當(dāng)dll被加載到一個(gè)進(jìn)程中時(shí),dll打印"Dll is attached!"語句;當(dāng)dll從進(jìn)程中卸載時(shí),打印"Dll is detached!"語句。

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

            cl /c dll_nolib.cpp

            這條命令會(huì)將cpp編譯為obj文件,若不使用/c參數(shù)則cl還會(huì)試圖繼續(xù)將obj鏈接為exe,但是這里是一個(gè)dll,沒有main函數(shù),因此會(huì)報(bào)錯(cuò)。不要緊,繼續(xù)使用鏈接命令。

            Link /dll dll_nolib.obj

            這條命令會(huì)生成dll_nolib.dll。

            注意,因?yàn)榫幾g命令比較簡單,所以本文不討論nmake,有興趣的可以使用nmake,或者寫個(gè)bat批處理來編譯鏈接dll。

            加載DLL(顯式調(diào)用)
            使用dll大體上有兩種方式,顯式調(diào)用和隱式調(diào)用。這里首先介紹顯式調(diào)用。編寫一個(gè)客戶端程序: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;

            }

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

            Cl dll_nolib_client.cpp

            并執(zhí)行dll_nolib_client.exe,得到如下結(jié)果:

            Dll is attached!

            dll loaded!

            Dll is detached!

            以上結(jié)果表明dll已經(jīng)被客戶端加載過。但是這樣僅僅能夠?qū)ll加載到內(nèi)存,不能找到dll中的函數(shù)。

            使用dumpbin命令查看DLL中的函數(shù)
            Dumpbin命令可以查看一個(gè)dll中的輸出函數(shù)符號(hào)名,鍵入如下命令:

            Dumpbin –exports dll_nolib.dll

            通過查看,發(fā)現(xiàn)dll_nolib.dll并沒有輸出任何函數(shù)。

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

            Def文件
            首先寫一個(gè)帶有輸出函數(shù)的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;

            }

            這個(gè)dll的def文件如下:dll_def.def

            ;

            ; dll_def module-definition file

            ;

            LIBRARY         dll_def.dll

            DESCRIPTION     '(c)2007-2009 Wang Xuebin'

            EXPORTS

                            FuncInDll @1 PRIVATE

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

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

            Cl /c dll_def.cpp

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

            再調(diào)用dumpbin查看生成的dll_def.dll:

            Dumpbin –exports dll_def.dll

            得到如下結(jié)果:

            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

            會(huì)發(fā)現(xiàn)該dll輸出了函數(shù)FuncInDll。

            顯式調(diào)用DLL中的函數(shù)
            寫一個(gè)dll_def.dll的客戶端程序:dll_def_client.cpp

            #include <windows.h>

            #include <iostream.h>

            int main(void)

            {

                //定義一個(gè)函數(shù)指針

                typedef void (* DLLWITHLIB )(void);

                //定義一個(gè)函數(shù)指針變量

                DLLWITHLIB pfFuncInDll = NULL;

                //加載我們的dll

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

                if (NULL != hinst)

                {

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

                }

                //找到dll的FuncInDll函數(shù)

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

                //調(diào)用dll里的函數(shù)

                if (NULL != pfFuncInDll)

                {

                   (*pfFuncInDll)();  

                }

                return 0;

            }

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

            編譯鏈接這個(gè)客戶端程序,并執(zhí)行會(huì)得到:

            dll loaded!

            FuncInDll is called!

            這表明客戶端成功調(diào)用了dll中的函數(shù)FuncInDll。

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

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

            __declspec(dllexport) void FuncInDll (void)

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

            Cl /c dll_withlib.cpp

            Link /dll dll_withlib.obj

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

            1    0 00001000 ?FuncInDll@@YAXXZ

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

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

            dumpbin命令結(jié)果:

            1    0 00001000 FuncInDll

            這樣,顯式調(diào)用時(shí)只需查找函數(shù)名為FuncInDll的函數(shù)即可成功。

            extern “C”
            使用extern “C”關(guān)鍵字實(shí)際上相當(dāng)于一個(gè)編譯器的開關(guān),它可以將c++語言的函數(shù)編譯為c語言的函數(shù)名稱。即保持編譯后的函數(shù)符號(hào)名等于源代碼中的函數(shù)名稱。

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

            下面是一個(gè)隱式調(diào)用的例子:dll包含兩個(gè)文件dll_withlibAndH.cpp和dll_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://看到?jīng)]有,這就是我們?cè)黾拥念^文件

            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

            在進(jìn)行隱式調(diào)用的時(shí)候需要在客戶端引入頭文件,并在鏈接時(shí)指明dll對(duì)應(yīng)的lib文件(dll只要有函數(shù)輸出,則鏈接的時(shí)候會(huì)產(chǎn)生一個(gè)與dll同名 的lib文件)位置和名稱。然后如同調(diào)用api函數(shù)庫中的函數(shù)一樣調(diào)用dll中的函數(shù),不需要顯式的LoadLibrary和 GetProcAddress。使用最為方便。客戶端代碼如下:dll_withlibAndH_client.cpp

            #include "dll_withLibAndH.h"

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

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

            int main(void)

            {

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

                return 0;

            }

            __declspec(dllexport)和__declspec(dllimport)配對(duì)使用
            上面一種隱式調(diào)用的方法很不錯(cuò),但是在調(diào)用DLL中的對(duì)象和重載函數(shù)時(shí)會(huì)出現(xiàn)問題。因?yàn)槭褂胑xtern “C”修飾了輸出函數(shù),因此重載函數(shù)肯定是會(huì)出問題的,因?yàn)樗鼈兌紝⒈痪幾g為同一個(gè)輸出符號(hào)串(c語言是不支持重載的)。

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

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

            所有的源代碼包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp。源代碼可以在下載的包中找到。你可以編譯鏈接并運(yùn)行試試。

            值得關(guān)注的是DLL1和DLL2中都使用的一個(gè)編碼方法,見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_EXPORTS和DLL_DLL2_API,可以確保DLL端的函數(shù)用 __declspec(dllexport)修飾,而客戶端的函數(shù)用__declspec(dllimport)修飾。當(dāng)然,記得在編譯dll時(shí)加上參數(shù) /D “DLL_DLL2_EXPORTS”,或者干脆就在dll的cpp文件第一行加上#define DLL_DLL2_EXPORTS。

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

            DLL中的全局變量和對(duì)象
            解決了重載函數(shù)的問題,那么dll中的全局變量和對(duì)象都不是問題了,只是有一點(diǎn)語法需要注意。如源代碼所示: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個(gè)符號(hào):

            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,類的構(gòu)造函數(shù),F(xiàn)uncInDll函數(shù),全局變量g_nDll和類的成員函數(shù)show。下面是客戶端代碼:dll_object_client.cpp

            #include "dll_object.h"

            #include <iostream.h>

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

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

            int main(void)

            {

                cout<<"call dll"<<endl;

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

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

                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;

            }

            運(yùn)行這個(gè)客戶端可以看到:

            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中的全局變量,并創(chuàng)建了dll中定義的C++對(duì)象,還調(diào)用了該對(duì)象的成員函數(shù)。

            中間的小結(jié)
            牢記一點(diǎn),說到底,DLL是對(duì)應(yīng)C語言的動(dòng)態(tài)鏈接技術(shù),在輸出C函數(shù)和變量時(shí)顯得方便快捷;而在輸出C++類、函數(shù)時(shí)需要通過各種手段,而且也并沒有完美的解決方案,除非客戶端也是c++。

            記住,只有COM是對(duì)應(yīng)C++語言的技術(shù)。

            下面開始對(duì)各各問題一一小結(jié)。

            顯式調(diào)用和隱式調(diào)用
            何時(shí)使用顯式調(diào)用?何時(shí)使用隱式調(diào)用?我認(rèn)為,只有一個(gè)時(shí)候使用顯式調(diào)用是合理的,就是當(dāng)客戶端不是C/C++的時(shí)候。這時(shí)是無法隱式調(diào)用的。例如用VB調(diào)用C++寫的dll。(VB我不會(huì),所以沒有例子)

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

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

            第二個(gè)問題就是函數(shù)調(diào)用壓棧順序的問題,即__cdecl和__stdcall的問題。__cdecl是常規(guī)的C/C++調(diào)用約定,這種調(diào)用約定 下,函數(shù)調(diào)用后棧的清理工作是由調(diào)用者完成的。__stdcall是標(biāo)準(zhǔn)的調(diào)用約定,即這些函數(shù)將在返回到調(diào)用者之前將參數(shù)從棧中刪除。

            這兩個(gè)問題DLL都不能很好的解決,只能說湊合著用。但是在COM中,都得到了完美的解決。所以,要在Windows平臺(tái)實(shí)現(xiàn)語言無關(guān)性,還是只有使用COM中間件。

            總而言之,除非客戶端也使用C++,否則dll是不便于支持函數(shù)重載、類等c++特性的。DLL對(duì)c函數(shù)的支持很好,我想這也是為什么windows的函數(shù)庫使用C加dll實(shí)現(xiàn)的理由之一。

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


            轉(zhuǎn)自:http://hi.baidu.com/xuyuqiang/blog/item/4913698b74062cd6fc1f106c.html

            posted on 2009-08-30 12:46 逛奔的蝸牛 閱讀(7818) 評(píng)論(0)  編輯 收藏 引用 所屬分類: C/C++Qt
            久久久久久a亚洲欧洲aⅴ| 亚州日韩精品专区久久久| 久久综合久久自在自线精品自| 7777精品久久久大香线蕉 | 久久综合伊人77777| 亚洲国产成人精品久久久国产成人一区二区三区综 | 久久99精品九九九久久婷婷| 欧美精品丝袜久久久中文字幕| 狠狠综合久久综合88亚洲| 国产ww久久久久久久久久| 亚洲国产精品无码久久九九| 国产一区二区精品久久| 久久亚洲精品无码aⅴ大香| 狠狠狠色丁香婷婷综合久久五月| 亚洲国产成人久久笫一页| 国产99精品久久| 久久国产免费直播| 欧美亚洲另类久久综合婷婷| 久久99中文字幕久久| 99精品久久久久久久婷婷| 国产亚洲精久久久久久无码AV| 国产亚洲精久久久久久无码| 久久天天躁狠狠躁夜夜avapp| 久久国产成人午夜aⅴ影院 | 欧美日韩成人精品久久久免费看| 精品国产乱码久久久久久1区2区| 国产精品久久久久久久久久影院| 国内精品久久久久久不卡影院| av无码久久久久久不卡网站| 午夜精品久久久久久中宇| 久久亚洲AV无码精品色午夜| 日韩精品无码久久一区二区三| 久久精品18| 国内精品久久久久久久亚洲| 午夜不卡888久久| 国产 亚洲 欧美 另类 久久| 国产99久久久国产精品~~牛| 国产精品一区二区久久 | 日韩精品无码久久一区二区三| 国产精品久久久久一区二区三区 | 精品国产一区二区三区久久久狼|