zoukankan      html  css  js  c++  java
  • dll相关(转载)

    part1

    Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在 前面的 例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中 引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。
    根据编写规范,Windows必 须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意 味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

          我们来看一个DllMain函数的例子 :

         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;
    }

          DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被 调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH, 以switch语句列出。
    来仔细解读一下DllMain的函数头BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。
    APIENTRY被定义为 __stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;
    进 程中的每个DLL模块被全局唯 一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32 中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。

            执行下列代码:

            hDll = LoadLibrary("..\\Debug\\dllTest.dll");
            if (hDll != NULL)

            {
                    addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
                    //MAKEINTRESOURCE 直接使用导出文件中的序号
                   if (addFun != NULL)
                   {
                           int result = addFun(2, 3);
                           printf("\ncall add in dll:%d", result);
                   }
                  FreeLibrary(hDll);

             }
    我们看到输出顺序为:
    process attach of dll
    call add in dll:5
    process detach of dll
    这一输出顺序验证了DllMain被调用的时机。
    代 码中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通过.def文件中为add函数指定的顺序号访问add函数,具体体现在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义为(节选自winuser.h):

           #define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
           #define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
           #ifdef UNICODE
                  #define MAKEINTRESOURCE MAKEINTRESOURCEW
           #else
                  #define MAKEINTRESOURCE MAKEINTRESOURCEA

    part2

    1  DLL的进入/退出函数
    1.1  DllMain简介
    跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。以“DllMain”为关键字,来看看MSDN帮助文档怎么介绍这个函数的。
    The DllMain function is an optional method of entry into a dynamic-link library (DLL)。(简要翻译:对于一个Dll模块,DllMain函数是可选的。)这句话很重要,很多初学者可能都认为一个动态链接库肯定要有DllMain 函数。其实不然,像很多仅仅包含资源信息的DLL是没有DllMain函数的。
    1.2 何时调用DllMain
           系统是在什么时候调用DllMain函数的呢?静态链接时,或动态链接时调用LoadLibrary和FreeLibrary都会调用DllMain函 数。DllMain的第三个参数fdwReason指明了系统调用Dll的原因,它可能是DLL_PROCESS_ATTACH、 DLL_PROCESS_DETACH、DLL_THREAD_ATTACH和DLL_THREAD_DETACH。以下从这四种情况来分析系统何时调用 了DllMain。            
    1.2.1 DLL_PROCESS_ATTACH
           大家都知道,一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接和动态链接的LoadLibrary或者LoadLibraryEx。
           当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为 DLL_PROCESS_ATTACH。这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者 LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同 进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
           可参考DllMainTest的DLL_PROCESS_ATTACH_Test函数。
    1.2.2 DLL_PROCESS_DETACH
           当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason
    值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。
           那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:
           ◆FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)
           ◆进程结束而解除DLL映射,当然实在进程结束前还没有这个解除DLL的映射的情况。(如果进程的终结是因为调用了TerminateProcess,系 统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)
           注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用 DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保没有清理那些没有成功初始化的东西。
           可参考DllMainTest的DLL_PROCESS_DETACH_Test函数。
    1.2.3 DLL_THREAD_ATTACH
           当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。
    新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。
    注 意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,是不再用 DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值 DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。
    1.2.4 DLL_THREAD_DETACH
           如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文 件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
           注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。
    1.3  为DllMain换名
    在 早期的SDK版本中,DllMain是叫做DllEntryPoint。其实有一件鲜为人知的事:一个Dll的入口函数名是可以可以自己定义的。下面我将 以VC++6.0为例来演示如何更改。首先要说明一点,虽然DllMain可以换成其他函数名,但函数的参数和返回值必须和DllMain一样。而且这个 函数要为__stdcall类型(DllMain本身也是__stdcall类型)。
    打开VC++菜单Project\Settings \Link tab\ Output in the Category box,如下图,在Entry-point symbol中输入要替换DllMain的函数名(当然这个函数名是你程序中已经实现的函数)。Entry-point symbol是干么的呢?可以以关键字“Entry-point symbol”搜索MSDN帮助文档查看,搜索时,打钩“仅搜索标题”会更快定位。
     
             按OK后,如果马上编译的话会出现如下错误:
    LIBCMTD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
    Debug/Dll.dll : fatal error LNK1120: 1 unresolved externals
    打 开VC++菜单Project\Settings\C/C++选项卡,如下图,在Project Options:末尾的地方添加”/D”(图中蓝色高亮的地方),要注意位置,我试了,要把/D放到/GZ后面也会链接错误,我也不懂为什么,^_^。按 OK,再次编译,成功。大家可以自己测试下到底有没有更改成功,什么,如果测试?打出调式信息啊。
     
    1.4 DisableThreadLibraryCalls
    看帮助就知道它是干么用的:
    The DisableThreadLibraryCalls function disables the DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications for the dynamic-link library (DLL) specified by hLibModule. This can reduce the size of the working code set for some applications.

    part3

    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的函数即可成功。

    extern “C”

    使用extern “C”关键字实际上相当于一个编译器的开关,它可以将c++语言的函数编译为c语言的函数名称。即保持编译后的函数符号名等于源代码中的函数名称。

    隐式调用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"//看到没有,这就是我们增加的头文件

    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

    仔细观察其源代码,是不是有很多地方似曾相识啊,哈哈!

    
  • 相关阅读:
    二分类下的混淆矩阵
    多项式的回归
    使用变换来提升单回归准确度的一个反例
    使用OpenOffice.org将各类文档转为PDF
    22 Best Sites To Download Free Sprites
    给Libgdx的ShapeRenderer开启抗锯齿
    How get a String Width in Libgdx?
    JS实现IE下打印和打印预览
    spring-mvc不拦截静态资源的配置
    This Android SDK requires Android Developer Toolkit version 23.0.0 or above
  • 原文地址:https://www.cnblogs.com/cecwxf/p/2039478.html
Copyright © 2011-2022 走看看