精华内容
下载资源
问答
  • 动态链接库DLL

    千次阅读 2009-01-20 14:44:00
    动态链接库DLL一、介绍动态链接库DLL(Dynamic Link Library)是Windows的组成要素之一,是包含共享函数库的二进制文件,可以被多个应用程序同时使用。DLL也是包含了一系列类、函数、变量或其它资源的库文件。一些...

    动态链接库DLL



    一、介绍
    动态链接库DLL(Dynamic Link Library)是Windows的组成要素之一,是包含共享函数库的二进制文件,可以被多个应用程序同时使用。DLL也是包含了一系列类、函数、变量或其它资源的库文件。

    一些常用的资源如图标、位图、字符串和对话框之类的资源,做到DLL中,可为多个应用程序共享。这就是纯资源DLL,也即只包含资源的DLL,其资源可以是字符串、数组、图标、位图、声音、视频或对话框等。

    可用DLL实现程序的不同语言界面。

    动态链接库DLL提供了一种共享数据和代码的方便途径。由于多个应用程序可共享同一DLL的函数,故使用DLL可节省磁盘空间和内存空间。也便于程序功能的扩展和升级。还便于多语言开发、模块化设计。

    VC、C++ Builder、Delphi等都可以编写DLL文件。不同语言编写的程序可共享同一个DLL程序。

    DLL不能单独运行,只能由可执行文件或其它DLL文件调用。

    二、DLL的特性
    1、DLL不是独立运行的程序,而是应用程序的一部分,只能由所属的程序调用。

    2、如没有DLL说明文档,则难以调用DLL。因为DLL只包括函数的名字,不包括函数的参数和调用方法等信息。
    使用VC的dumpbin(bin目录下)或Delphi的TDump,可查看DLL包括哪些函数,但不能看到具体信息。

    三、调用DLL的方式
    1、静态调用DLL
    静态调用需要LIB库文件和DLL文件。程序编译时需用LIB文件,系统编译时DLL会编译进程序。应用程序发布时,不再需要LIB文件。

    注:DLL的编写与编程语言无关,只需遵守DLL接口规范。

    2、动态调用DLL
    如没有与DLL相关联的LIB文件,则须使用动态调用。也必须知道函数返回值的类型和传递的参数个数、类型和顺序。

    使用动态调用的应用程序在调用DLL中的导出函数前,必须使用loadlibrary()函数加载DLL,并得到一个模块句柄,然后使用该句柄调用getprocaddress()函数获得所需调用的导出函数的指针,通过该指针调用DLL中的导出函数。

    使用动态调用DLL的应用程序不需要相应的LIB文件。

    使用完毕后,还需要调用freelibrary()函数释放加载的DLL。

     三、DLL问题
    1、如何了解某应用程序使用哪些DLL文件
    右键单击该应用程序并选择快捷菜单中的“快速查看”命令,在随后出现的“快速查看”窗口的“引入表”一栏中你将看到其使用DLL文件的情况。

    2、如何知道DLL文件被几个程序使用
    运行Regedit,进入HKEY_LOCAL_MACHINESoftwareMicrosrftWindowsCurrentVersionSharedDlls子键查看,其右边窗口中就显示了所有DLL文件及其相关数据,其中数据右边小括号内的数字就说明了被几个程序使用,(2)表示被两个程序使用,(0)则表示无程序使用,可将其删除。

    3、如何解决DLL文件丢失的情况
    有时在卸载文件时会提醒你删除某个DLL文件可能会影响其他应用程序的运行。所以当你卸载软件时,就有可能误删共享的DLL文件。一旦出现丢失DLL文件的情况,如果你能确定其名称,可以在Sysbckup(系统备份文件夹)中找到该DLL文件,将其复制到System文件夹中。如果不行,在电脑启动时又总是出现“***dll文件丢失……”的提示框,你可以在“开始/运行”中运行Msconfig,进入系统配置实用程序对话框以后,单击选择“System.ini”标签,找出提示丢失的DLL文件,使其不被选中,这样开机时就不会出现错误提示。

    展开全文
  • 动态链接库 dll

    2013-12-12 20:57:48
     链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行时链接。装载时链接是指该动态链接库是在程序装入时进行加载链接的,而运行时链接是指该动态链接库是在程序运行时执行...

    【转自:http://blog.csdn.net/zgb881020/article/details/4039682

       链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行时链接。装载时链接是指该动态链接库是在程序装入时进行加载链接的,而运行时链接是指该动态链接库是在程序运行时执行LoadLibrary(或LoadLibraryEx,下同)函数动态加载的。因此,由于动态链接库有这两种链接方式,所以在编写使用DLL的程序时,就有了两种可选方案。

     
        可能有人会问“为什么需要装载时链接?直接静态链接不就行了吗?”,这是模块化程序设计的需要。试想,如果你开发一个很大的程序,并且经常需要更新。如果你选择静态链接,那么每次更新就必须更新整个exe文件,而如果你把需要经常更新的模块做成dll,那么只需要更新这个文件即可,每次程序运行时加载这个更新的文件即可。
     
        在进入编写DLL程序之前,先介绍一些相关知识。
     
        VC支持三种DLL,它们分别是Non-MFC DLL、MFC Regular DLL、MFC Extension DLL。由于本文只讲解API编程,所以这里只对第一种DLL进行介绍,后面两种DLL将在另外的文章中介绍。
     
        动态链接库的标准后缀是.DLL,当然也可以使用其它任意后缀名。但使用.DLL后缀的好处是:一是,很直观表明该文件的性质;二是,只有后缀为.DLL的动态链接库才能被Windows自动地加载,而其它后缀的动态链接库只能通过LoadLibrary显示式加载。
     
        动态链接库的用途:一是作为动态函数库使用,另一个常用的方式是作为动态资源库。当然,没有绝对的划分,比如你的动态函数库时也可能有资源,但动态资源库一般不会有函数。
     
        另两个重要的、需要区分的概念是:对象库(Object Library)和导入库(Import Library)。对象库是指普通的库文件,比如C运行时库libc.lib;而导入库是一种比较特殊的对象库文件,与一个动态链接库相对应。它们都有后缀.lib,并且都仅在程序编译链接时使用,被链接器用来解析函数调用。然而,导入库不包含代码,它只为链接器提供动态链接库的信息,以便于链接器对动态链接库中的对象作恰当地链接。
     
        动态链接库的查找规则。如果在使用时没有指定动态链接库的路径,则Windows系统按如下顺序搜索该动态链接库:使用该动态链接库的.exe文件所在目录、当前目录、Windows系统目录、Windows目录、环境变量%PATH%中的路径下的目录。
     
       
        DLL内的函数划分为两种类型:(1)导出函数,可供应用程序调用;(2) 内部函数(普通函数),只能在DLL程序内使用,应用程序无法调用它们。同样的划分适用于数据对象。
     
        在DLL中,要导出某个对象(函数或者数据),声明方式有两种:一种是利用关键字__declspec(dllexport);另一种方式是采用模块定义文件(.def)。另外,还可以通过链接选项/EXPORT指定导出。应该优先选用第一种方式,但.def文件方式在某些情况下是必须的。
     
        下面,我们分别介绍动态链接库的的制作、发布、使用及相关技术,重点介绍装载时链接和运行时链接的使用方法。在介绍运行时链接时,引入了模块定义文件(.def),详细介绍了其在DLL制作过程中的作用及使用方法。另外,还介绍了DLL中全局变量的导出、DLL中的数据共享和资源DLL的制作及使用。
     
    动态链接库的制作及装载时链接
     
        首先,打开VC6.0,创建一个名为DLLTest的空工作区。然后,创建一个名为DLL_Lib的Win32 Dynamic-Link Library工程,注意将该工程添加到刚创建的工作区DLLTest中,并且将该工程保存在工作区的目录下(不建子目录)。然后,在该工程中,加入这下面两个文件:
     

    /*
     * dll_lib.h
     */
    #ifndef DLL_LIB_H
    #define DLL_LIB_H

    #ifdef __cplusplus
    #define EXPORT extern "C" __declspec (dllexport)
    #else
    #define EXPORT __declspec (dllexport)
    #endif

    EXPORT int WINAPI GetMax(int a,int b);

    #endif

     

    /*
     * dll_lib.c
     */

    #include<windows.h>
    #include<stdio.h>
    #include"dll_lib.h"

    int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
    {
        switch (fdwReason)    
        {    
        case DLL_PROCESS_ATTACH:
            printf("> process attach of dll/n");
            break;
            
        case DLL_THREAD_ATTACH:
            printf("> thread attach of dll/n");
            break;
            
        case DLL_THREAD_DETACH:
            printf("> thread detach of dll/n");
            break;
            
        case DLL_PROCESS_DETACH:
            printf("> process detach of dll/n");
            break;
        }

        return TRUE;
    }

    int GetMax(int a,int b)
    {
        return a > b ? a : b;
    }

        接着,再创建一个Win32 Console Application工程DLL_Test,同样将该工程加入先前的DLLTest工作区中,并直接保存在该工作区目录下。然后向工程DLL_Test加入下面的文件:

    /*
     * testMain.c
     */

    #include<windows.h>
    #include<stdio.h>
    #include"dll_lib.h"

    int main()
    {
        int a = 2;
        int b = 3;
        printf(" max(2, 3) = %d/n", GetMax(2, 3));

        return 0;
    }

        此时,工作差不多做完了,但还需进行一下设置。在Project|Settings里,把两个工程里的General标签里的Intermediate files和Output files都设置为Debug。这样确保两个工程的输出文件在一个目录中,以便后面动态库链接时的查找。另外,设置DLL_Test为活动工程(Project|Set Active Project),设置DLL_Test依赖于DLL_Lib(Project|Dependencies)。此时,就可以编译运行了。运行结果为:

    > process attach of dll
     max(2, 3) = 3
    > process detach of dll
    Press any key to continue

        下面对上面的代码和结果进行分析。

        在dll_lib.h中,EXPORT宏实质上就是一个导出函数所需要的关键字。__declspec (dllexport)是Windows扩展关键字的组合,表示DLL里的对象的存储类型关键字。extern "C"用于C++程序使用该函数时的函数声明的链接属性。WINAPI是宏定义,等价于__stdcall。下面列出Windows编程中常见的几种有关调用约定的宏,它们都是与__stdcall和__cdecl有关的(from windef.h):

        #define CALLBACK   __stdcall     // 用于回调函数
        #define WINAPI     __stdcall     // 用于API函数
        #define WINAPIV    __cdecl
        #define APIENTRY   WINAPI     
        #define APIPRIVATE __stdcall
        #define PASCAL     __stdcall

    另外,关于__stdcall:如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用约定声明为__stdcall方式,WINAPI、CALLBACK都采用这种方式,而C/C++缺省的调用方式却为__cdecl。__stdcall方式与__cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionName@number ,而__cdecl调用约定仅在输出函数名前面加下划线,形如_functionName。(小技巧:如何查看这些符号?写一个程序,只提供函数的声明而不给定义,就可以看到链接器给出的符号了)

        因此,在前面例子中,该DLL声明了一个导出函数GetMax,其连接属性采用CALLBACK(即__stdcall)。另外,请注意,例子中的宏EXPORT会根据是在C程序还是在C++程序中被调用选择相应的连接方式。在定义导出函数时,不需要EXPORT宏,只需要在函数声明时使用即可。

        DllMain函数在DLL载入和卸载时被调用。它的第一个参数是DLL句柄,第三个参数保留。第二个参数用来区分该DLLMain函数是在什么情况下被调用的,如程序所示。如果初始化成功,则DllMain应该返回一个非零值。如果返回零值将导致程序停止运行(你可以修改上面例子中的DllMain的返回值为0,将看到相应的出错结果)。如果在你的DLL程序中没有编写DllMain函数,那么在执行该DLL时,系统将引入一个不做任何操作的缺省DllMain函数版本。

       
        在前面的例子中,给出了DLL的制作及使用。注意,我们在使用DLL时,直接关联了两个工程。如果你想把自己制作的DLL提供给别人使用,而又不想提供源代码,那应该怎么做呢?
     
        由文章最开始的分析知,要达到这个目的,只需要提供给DLL用户三个文件即可:.h文件,.lib文件和.dll文件。当然,对于dll_lib库,我们只需要提供dll_lib.h, dll_lib.lib, dll_lib.dll三个文件即可。
     
        用户应该怎么使用些文件呢?我们利用前面的工程进行介绍。首先将前面两个工程的依赖关系去掉,并设置DLL_Test工程为当前活动工程。先编译下下试试,你会发现,编译器在链接时会发生错误,提示不能完成GetMax函数的链接。然后,找到Project|Settings|Link|Object/Library Modules,往里加入库文件debug/dll_lib.lib。再次链接,OK!运行,结果跟最先的结果一模一样。小结:(1)库用户在调用DLL的导出函数的文件中包含库头文件;(2)将与.dll对应的.lib库文件加入工程的链接库中;(3)在.exe文件所在目录中放入一份.dll文件的拷贝。当然,如果是已经发布的.exe程序使用的.dll需要更新,此时只需要将.dll替换原来的.dll即可。
     
    运行时链接
     
        前面介绍了DLL的制作及相关技术和它的装载时链接,下面介绍运行时链接的方法。还是接着利用前面的例子,需要做一点小小的修改:把DLL_Lib工程里的GetMax函数的WINAPI调用约定暂时先去掉(后面将说明为什么这样做),然后编译该工程。然后,将testMain函数作如下修改:
     

    /*
     * testMain.c
     */

    #include<windows.h>
    #include<stdio.h>

    typedef int (* PGetMax)(int,int);

    int main()
    {
        int a = 2;
        int b = 3;
        
        HINSTANCE hDll;  // DLL句柄 
        PGetMax pGetMax; // 函数指针
        
        hDll = LoadLibrary(".//Debug//DLL_lib.dll");
        if (hDll==NULL){
            printf("Can't find library file /"dll_lib.dll/"/n");
            exit(1);
        }
        
        pGetMax = (PGetMax)GetProcAddress(hDll,"GetMax");
        if (pGetMax==NULL){
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }

        printf(" max(2, 3) = %d/n", pGetMax(2, 3));
        
        FreeLibrary(hDll);

        return 0;
    }

    此时,不再需要动态的.h文件和.lib文件,只需要提供.dll文件即可。在具体使用时,先用LoadLibrary加载Dll文件,然后用GetProcAddress寻找函数的地址,此时必须提供该函数的在Dll中的名字(不一定与函数名相同)。

        然后编译链接、运行,结果与前面的运行结果相同。

        下面将解释,为什么前面要去掉WINAPI调用约定(即采用默认的__cdecl方式)。我们可以先看看DLL_Lib.dll里面的链接符号。在cmd中运行命令:
        dumpbin /exports DLL_Lib.dll
    得到如下结果:

    Dump of file f:/code/DLLTest/Debug/Dll_lib.dll

    File Type: DLL

      Section contains the following exports for DLL_Lib.dll

               0 characteristics
        4652C3B1 time date stamp Tue May 22 18:19:29 2007
            0.00 version
               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

                 0 0000100A GetMax

      Summary

            4000 .data
            1000 .idata
            3000 .rdata
            2000 .reloc
           28000 .text

    可以看到GetMax函数在编译后在Dll中的名字仍为GetMax,所以在前面的程序中使用的是:
        pGetMax =(PGetMax)GetProcAddress(hDll,"GetMax");

        然后,我们把WINAPI添加回去,重新编译DLL_Lib工程。运行刚才的DLL_Test程序,运行出错,结果如下:
    > process attach of dll
    Can't find function "GetMax"
    > process detach of dll
    Press any key to continue

        显然,运行失败原因是因为没有找到GetMax函数。再次运行命令:dumpbin /exports DLL_Lib.dll,结果如下(部分结果):

               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

              1    0 0000100A _GetMax@8

    从上面dumpbin的输出看,GetMax函数在WINAPI调用约定方式下在DLL里的名字与源码中的函数定义时的名字不再相同,其导出名是"_GetMax@8"。此时,你把testMain.c中的函数指针类型声明和函数查找语句作如下修改:
        typedef int (WINAPI* PGetMax)(int, int);
        pGetMax = (PGetMax)GetProcAddress(hDll, "_GetMax@8");
    再次编译链接,然后运行,发现结果又正确了。

        现在找到了问题所在。很显然,这种修改方式并不适用,而默认生成的名字又不是我们所想要的。那么该怎么解决这个问题呢?这就需要用到.def文件来解决。

    模块定义文件(.def)

        模块定义文件(.def文件)是一个描述DLL的各种属性的文件,可以包含一个或多个模块定义语句。如果你不使用关键字__declspec(dllexport)关键字导出DLL中的函数,那么DLL就需要一个.def文件。

        一个最小的.def文件必须包含下面的模块定义语句:
        (1)文件中第一个语句必须是LIBRARY语句。该语句标记该.def文件属于哪个DLL。语法形式为:LIBRARY <DLL名>。
        (2)EXPORTS语句列表。第一个导出语句的形式为:entryname[=internalname] [@ordinal],列出DLL中要导出的函数的名字和可选的序号(ordinal value)。要导出的函数名可以是程序源码中的函数名,也可以定义新的函数别名(但后面必须紧跟[=<原函数名>]);序号必须在范围1到N之间且不能重复,其中N是DLL中导出的函数个数。因此,EXPORTS语句语法形式为:
        EXPORTS
            <funcName1>[=<InternalName1] [@<num1>]
            <funcName2>[=<InternalName2] [@<num2>]
            ;...
        (3)虽然不是必须的,一个.def文件也常常包含DESCRIPTION语句,用来描述该DLL的用途之类,语法形式为:
        DESCRIPTION "<Description about the purpose of this DLL.>"
        (4)在任意位置,可以包含注释语句,以分号(;)开始。

        例如,在本文中后面将用到的.def文件为:

    ; DLL_Lib.def

    LIBRARY DLL_Lib     ; the dll name
    DESCRIPTION "Learn how to use the dll."

    EXPORTS
        GetMax @1
        Max=GetMax @2   ; alias name of GetMax

    ; Ok, over

        现在,让我们回到DLL_Lib工程,修改GetMax函数的声明,把EXPORT去掉,重新编译该工程。然后,运行dumpbin命令,我们发现此时没有导出函数。再将上面的DLL_Lib.def文件添加进DLL_Lib工程,再次编译,并运行dumpbin命令,得到如下结果(引用部分结果):

              1 ordinal base
              2 number of functions
              2 number of names

       ordinal hint RVA      name

             1    0 0000100A GetMax
             2    1 0000100A Max

        正如我们所预期的,有两个导出函数GetMax和Max。注意,此时源码中的GetMax函数的导出名不再是默认的“_GetMax@8”。另外,需要注意的是,两个导出函数有相同的相对虚拟地址(RVA),也说明了两个导出名实质是同一个函数的不同名字而已,都是源码中GetMax函数的导出名。

        现在,回到DLL_Test工程,修改testMain.c文件内容如下:

    /*
     * testMain.c
     */

    #include<windows.h>
    #include<stdio.h>

    typedef int (WINAPI* PGetMax)(int,int);

    int main()
    {
        int a = 2;
        int b = 3;
        
        HINSTANCE hDll; // DLL句柄 
        PGetMax pGetMax; // 函数指针
        
        hDll = LoadLibrary(".//Debug//DLL_lib.dll");
        if (hDll==NULL){
            printf("Can't find library file /"dll_lib.dll/"/n");
            exit(1);
        }
        
        pGetMax = (PGetMax)GetProcAddress(hDll,"GetMax");
        if (pGetMax==NULL){
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }
        printf(" GetMax(2, 3) = %d/n", pGetMax(2, 3));

        pGetMax = (PGetMax)GetProcAddress(hDll,"Max");
        if (pGetMax==NULL){
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }
        printf(" Max(2, 3) = %d/n", pGetMax(2, 3));
        
        FreeLibrary(hDll);
        return 0;
    }

        编译链接、运行,结果如下:

    > process attach of dll
     GetMax(2, 3) = 3
     Max(2, 3) = 3
    > process detach of dll
    Press any key to continue

        运行结果正如前面分析的那样,GetMax和Max都得到了相同的结果。

        到这里,我们解决了DLL导出函数名在各种调用约定下的默认名可能不同于源码中函数名的问题。此时,你就可以制作跟Windows的自带API函数库相同的库了:使用__stdcall调用约定以满足Windows下的任何语言都可以调用DLL库,同时使用函数名作为导出名,以方便用户使用DLL里的函数。

    导出全局变量
     
        前面我们介绍了DLL中的函数的导出方法,这里也介绍一下DLL中全局变量的导出。
     
        首先需要明确的是,当多个应用程序同时使用同一个DLL时,系统中只有一个DLL实例(这里主要指代码段,一般不包含数据段)。也就是说,如果没有特殊处理,DLL中的数据都是每个使用DLL的应用都保留一份副本的(但是,可以根据需要实现DLL数据的共享,后面进行介绍)。因此,使用DLL的各应用程序之间不会发生干扰。
     
        要导出DLL中的全局变量,方法与导出函数基本一样。只是,在定义.def文件时,在EXPORTS定义语句之后用DATA标识符表明这是变量。例如:g_oneNumber DATA或者 g_oneNumber @3 DATA
     
        在使用DLL中导出的全局变量时,对于前面DLL的两种链接方式,有不同的方法。其中,对于运行时链接的DLL,其使用方法与函数一样(流程:LoadLibrary, GetProcAddress),只是在使用时要知道这是一个变量的地址,而不再是一个函数的地址即可(其实,用dumpbin工具查看DLL的导出列表,会发现导出的数据也被当作函数计数)。 对于装载时链接,要导入DLL中的变量,有点与函数不一样的地方,那就是必须显示地用关键字__declspec(dllimport)导入DLL中的变量。例如,在使用前面的g_oneNumber前,应先导入:__declspec(dllimport) extern int g_oneNumber。然后,其它与函数的使用方法无异。
     
    共享DLL中的数据
     
        有时,可能需要在使用DLL的多个应用之间共享DLL的数据,而默认情况下,DLL的数据是每个应用拥有一份副本的。要实现这个需求,就需要做些特殊处理。
     
        首先,定义一个数据段,里面有需要共享的变量,并要初始化这些变量。然后设置该数据段为共享即可,比较简单。例如,要在DLL中共享int型变量g_oneNumber,那么应按如下方式定义该变量:
    #pragma data_seg ("shared")       
    int g_oneNumber = 0;
    #pragma data_seg ()
     
    #pragma comment(linker,"/SECTION:shared,RWS")
     
        对上面的代码做些解释:#pragma data_seg ("shared")创建了一个数据段,命名为Shared;#pragma data_seg()标记该数据段的结束;它们之间定义的是该数据段中的变量。注意:这里对变量的初始化是必须的,否则,编译器会把未初始化的变量放在普通的未初始化数据段,而不是在共享的数据段。
        #pragma comment(linker, "SECTION:shared,RWS")告诉链接器shared数据段具有RWS属性。这里的RWS是指Read、Write和Shared三个属性。也可以在IDE中设置工程属性:在Settings|Link|Project Options中,添加链接参数:/SECTION:shared,RWS。
     
    资源DLL的制作及使用
     
       DLL 是仅包含资源(如图标、位图、字符串和对话框)的 DLL。使用纯资源 DLL 是在多个程序之间共享同一组资源的好方法。提供其资源被针对多种语言进行本地化的应用程序也是一种好方法。
        若要创建纯资源 DLL,请创建一个新的 Win32 DLL(非 MFC)项目,并将资源添加到此项目。 
        在“新建项目”对话框中选择“Win32 项目”,并在“Win32 项目向导”中指定 DLL 项目类型。 
        为 DLL 创建一个包含资源(如字符串或菜单)的新资源脚本,并保存该 .rc 文件。如果该.rc文件包含位图用记事本打开它,可以看到下面这样的一段 
    DIB_BKGD_HF             BITMAP            /"bkgd_**.bmp/"
    DIB_BKGD_GT  BITMAP    /"bkgd_***.bmp/"
    DIB_BKGD_BF  BITMAP    /"bkgd_*****.bmp/" 
        这些就是针对位图的申明,将相应的位图添加到这个里面就行了。 
        在“项目”菜单上单击“添加现有项”,然后在项目中插入这个新的 .rc 文件。 
        指定 /NOENTRY 链接器选项。/NOENTRY 防止链接器将 _main 引用链接到 DLL 中;此选项是创建纯资源 DLL 所必需的。 
    生成 DLL。
        使用纯资源 DLL 的应用程序应调用 LoadLibrary 来显式链接到 DLL。若要访问资源,请调用一般函数 FindResource 和 LoadResource,这两个函数对任何类型的资源都有效,或调用下列资源特定的函数之一: 
    FormatMessage 
    LoadAccelerators 
    LoadBitmap 
    LoadCursor 
    LoadIcon 
    LoadMenu 
    LoadString 
    使用完资源后,应用程序应调用 FreeLibrary。 
    下面部份是说明一下资源中的BMP如何动态加载并显示 
        hmodule = LoadLibrary(/"ExtendDLL.dll/");
        HDC   m_hdcMem;
        m_hdcMem = CreateCompatibleDC(hdc);
        BITMAP bm;
        RECT rect; 
        HBITMAP    hBitmap =  LoadBitmap(hmodule,/"DIB_BKGD_HF/");   //MAKEINTRESOURCE(IDB_BITMAP1)
        //HBITMAP    hBitmap = ( HBITMAP )LoadImage( hmodule, /"DIB_BKGD_HF/",IMAGE_BITMAP,0,0,LR_DEFAULTSIZE);
        GetObject( hBitmap, sizeof BITMAP, &bm);
        SelectObject( m_hdcMem, hBitmap);
        GetClientRect( hWnd, &rect);
        //::SetStretchBltMode(hdc,COLORONCOLOR); [Page]
        //::StretchBlt(hdc, rect.left, rect.top, rect.right, rect.bottom, m_hdcMem, 0, 0, bm.bmWidth, bm.bmHeight,SRCCOPY);
        BitBlt(hdc,0,0,bm.bmWidth,bm.bmHeight,m_hdcMem,0,0,SRCCOPY);
        ReleaseDC( hWnd, m_hdcMem );
        FreeLibrary(hmodule);

    

    展开全文
  • 动态链接库 dll

    2009-09-10 10:17:00
    链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行...因此,由于动态链接库有这两种链接方式,所以在编写使用DLL的程序时,就有了两种可选方案。 可能有人会问“为什么需要装载

    链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行时链接。装载时链接是指该动态链接库是在程序装入时进行加载链接的,而运行时链接是指该动态链接库是在程序运行时执行LoadLibrary(或LoadLibraryEx,下同)函数动态加载的。因此,由于动态链接库有这两种链接方式,所以在编写使用DLL的程序时,就有了两种可选方案。

     
        可能有人会问“为什么需要装载时链接?直接静态链接不就行了吗?”,这是模块化程序设计的需要。试想,如果你开发一个很大的程序,并且经常需要更新。如果你选择静态链接,那么每次更新就必须更新整个exe文件,而如果你把需要经常更新的模块做成dll,那么只需要更新这个文件即可,每次程序运行时加载这个更新的文件即可。
     
        在进入编写DLL程序之前,先介绍一些相关知识。
     
        VC支持三种DLL,它们分别是Non-MFC DLL、MFC Regular DLL、MFC Extension DLL。由于本文只讲解API编程,所以这里只对第一种DLL进行介绍,后面两种DLL将在另外的文章中介绍。
     
        动态链接库的标准后缀是.DLL,当然也可以使用其它任意后缀名。但使用.DLL后缀的好处是:一是,很直观表明该文件的性质;二是,只有后缀为.DLL的动态链接库才能被Windows自动地加载,而其它后缀的动态链接库只能通过LoadLibrary显示式加载。
     
        动态链接库的用途:一是作为动态函数库使用,另一个常用的方式是作为动态资源库。当然,没有绝对的划分,比如你的动态函数库时也可能有资源,但动态资源库一般不会有函数。
     
        另两个重要的、需要区分的概念是:对象库(Object Library)和导入库(Import Library)。对象库是指普通的库文件,比如C运行时库libc.lib;而导入库是一种比较特殊的对象库文件,与一个动态链接库相对应。它们都有后缀.lib,并且都仅在程序编译链接时使用,被链接器用来解析函数调用。然而,导入库不包含代码,它只为链接器提供动态链接库的信息,以便于链接器对动态链接库中的对象作恰当地链接。
     
        动态链接库的查找规则。如果在使用时没有指定动态链接库的路径,则Windows系统按如下顺序搜索该动态链接库:使用该动态链接库的.exe文件所在目录、当前目录、Windows系统目录、Windows目录、环境变量%PATH%中的路径下的目录。
     
       
        DLL内的函数划分为两种类型:(1)导出函数,可供应用程序调用;(2) 内部函数(普通函数),只能在DLL程序内使用,应用程序无法调用它们。同样的划分适用于数据对象。
     
        在DLL中,要导出某个对象(函数或者数据),声明方式有两种:一种是利用关键字__declspec(dllexport);另一种方式是采用模块定义文件(.def)。另外,还可以通过链接选项/EXPORT指定导出。应该优先选用第一种方式,但.def文件方式在某些情况下是必须的。
     
        下面,我们分别介绍动态链接库的的制作、发布、使用及相关技术,重点介绍装载时链接和运行时链接的使用方法。在介绍运行时链接时,引入了模块定义文件(.def),详细介绍了其在DLL制作过程中的作用及使用方法。另外,还介绍了DLL中全局变量的导出、DLL中的数据共享和资源DLL的制作及使用。
     
    动态链接库的制作及装载时链接
     
        首先,打开VC6.0,创建一个名为DLLTest的空工作区。然后,创建一个名为DLL_Lib的Win32 Dynamic-Link Library工程,注意将该工程添加到刚创建的工作区DLLTest中,并且将该工程保存在工作区的目录下(不建子目录)。然后,在该工程中,加入这下面两个文件:
     
    /*
     * dll_lib.h
     */
    #ifndef DLL_LIB_H
    #define DLL_LIB_H

    #ifdef __cplusplus
    #define EXPORT extern "C" __declspec (dllexport)
    #else
    #define EXPORT __declspec (dllexport)
    #endif

    EXPORT int WINAPI GetMax(int a, int b);

    #endif
     


    /*
     * dll_lib.c
     */
    #include <windows.h>
    #include <stdio.h>
    #include "dll_lib.h"

    int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
    {
        switch (fdwReason)   
        {   
        case DLL_PROCESS_ATTACH:
            printf("> process attach of dll/n");
            break;
           
        case DLL_THREAD_ATTACH:
            printf("> thread attach of dll/n");
            break;
           
        case DLL_THREAD_DETACH:
            printf("> thread detach of dll/n");
            break;
           
        case DLL_PROCESS_DETACH:
            printf("> process detach of dll/n");
            break;
        }

        return TRUE;
    }

    int GetMax(int a, int b)
    {
        return a > b ? a : b;
    }
     


        接着,再创建一个Win32 Console Application工程DLL_Test,同样将该工程加入先前的DLLTest工作区中,并直接保存在该工作区目录下。然后向工程DLL_Test加入下面的文件:

    /*
     * testMain.c
     */
    #include <windows.h>
    #include <stdio.h>
    #include "dll_lib.h"

    int main()
    {
        int a = 2;
        int b = 3;
        printf(" max(2, 3) = %d/n", GetMax(2, 3));

        return 0;
    }
     


        此时,工作差不多做完了,但还需进行一下设置。在Project|Settings里,把两个工程里的General标签里的Intermediate files和Output files都设置为Debug。这样确保两个工程的输出文件在一个目录中,以便后面动态库链接时的查找。另外,设置DLL_Test为活动工程(Project|Set Active Project),设置DLL_Test依赖于DLL_Lib(Project|Dependencies)。此时,就可以编译运行了。运行结果为:

    > process attach of dll
     max(2, 3) = 3
    > process detach of dll
    Press any key to continue

        下面对上面的代码和结果进行分析。

        在dll_lib.h中,EXPORT宏实质上就是一个导出函数所需要的关键字。__declspec (dllexport)是Windows扩展关键字的组合,表示DLL里的对象的存储类型关键字。extern "C"用于C++程序使用该函数时的函数声明的链接属性。WINAPI是宏定义,等价于__stdcall。下面列出Windows编程中常见的几种有关调用约定的宏,它们都是与__stdcall和__cdecl有关的(from windef.h):

        #define CALLBACK   __stdcall     // 用于回调函数
        #define WINAPI     __stdcall     // 用于API函数
        #define WINAPIV    __cdecl
        #define APIENTRY   WINAPI     
        #define APIPRIVATE __stdcall
        #define PASCAL     __stdcall

    另外,关于__stdcall:如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用约定声明为__stdcall方式,WINAPI、CALLBACK都采用这种方式,而C/C++缺省的调用方式却为__cdecl。__stdcall方式与__cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionName@number ,而__cdecl调用约定仅在输出函数名前面加下划线,形如_functionName。(小技巧:如何查看这些符号?写一个程序,只提供函数的声明而不给定义,就可以看到链接器给出的符号了)

        因此,在前面例子中,该DLL声明了一个导出函数GetMax,其连接属性采用CALLBACK(即__stdcall)。另外,请注意,例子中的宏EXPORT会根据是在C程序还是在C++程序中被调用选择相应的连接方式。在定义导出函数时,不需要EXPORT宏,只需要在函数声明时使用即可。

        DllMain函数在DLL载入和卸载时被调用。它的第一个参数是DLL句柄,第三个参数保留。第二个参数用来区分该DLLMain函数是在什么情况下被调用的,如程序所示。如果初始化成功,则DllMain应该返回一个非零值。如果返回零值将导致程序停止运行(你可以修改上面例子中的DllMain的返回值为0,将看到相应的出错结果)。如果在你的DLL程序中没有编写DllMain函数,那么在执行该DLL时,系统将引入一个不做任何操作的缺省DllMain函数版本。

       
        在前面的例子中,给出了DLL的制作及使用。注意,我们在使用DLL时,直接关联了两个工程。如果你想把自己制作的DLL提供给别人使用,而又不想提供源代码,那应该怎么做呢?
     
        由文章最开始的分析知,要达到这个目的,只需要提供给DLL用户三个文件即可:.h文件,.lib文件和.dll文件。当然,对于dll_lib库,我们只需要提供dll_lib.h, dll_lib.lib, dll_lib.dll三个文件即可。
     
        用户应该怎么使用些文件呢?我们利用前面的工程进行介绍。首先将前面两个工程的依赖关系去掉,并设置DLL_Test工程为当前活动工程。先编译下下试试,你会发现,编译器在链接时会发生错误,提示不能完成GetMax函数的链接。然后,找到Project|Settings|Link|Object/Library Modules,往里加入库文件debug/dll_lib.lib。再次链接,OK!运行,结果跟最先的结果一模一样。小结:(1)库用户在调用DLL的导出函数的文件中包含库头文件;(2)将与.dll对应的.lib库文件加入工程的链接库中;(3)在.exe文件所在目录中放入一份.dll文件的拷贝。当然,如果是已经发布的.exe程序使用的.dll需要更新,此时只需要将.dll替换原来的.dll即可。
     
    运行时链接
     
        前面介绍了DLL的制作及相关技术和它的装载时链接,下面介绍运行时链接的方法。还是接着利用前面的例子,需要做一点小小的修改:把DLL_Lib工程里的GetMax函数的WINAPI调用约定暂时先去掉(后面将说明为什么这样做),然后编译该工程。然后,将testMain函数作如下修改:
     
    /*
     * testMain.c
     */
    #include <windows.h>
    #include <stdio.h>

    typedef int (* PGetMax)(int, int);

    int main()
    {
        int a = 2;
        int b = 3;
       
        HINSTANCE hDll;  // DLL句柄
        PGetMax pGetMax; // 函数指针
       
        hDll = LoadLibrary(".//Debug//DLL_lib.dll");
        if (hDll == NULL) {
            printf("Can't find library file /"dll_lib.dll/"/n");
            exit(1);
        }
       
        pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax");
        if (pGetMax == NULL) {
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }

        printf(" max(2, 3) = %d/n", pGetMax(2, 3));
       
        FreeLibrary(hDll);

        return 0;
    }
     


    此时,不再需要动态的.h文件和.lib文件,只需要提供.dll文件即可。在具体使用时,先用LoadLibrary加载Dll文件,然后用GetProcAddress寻找函数的地址,此时必须提供该函数的在Dll中的名字(不一定与函数名相同)。

        然后编译链接、运行,结果与前面的运行结果相同。

        下面将解释,为什么前面要去掉WINAPI调用约定(即采用默认的__cdecl方式)。我们可以先看看DLL_Lib.dll里面的链接符号。在cmd中运行命令:
        dumpbin /exports DLL_Lib.dll
    得到如下结果:
    Dump of file f:/code/DLLTest/Debug/Dll_lib.dll

    File Type: DLL

      Section contains the following exports for DLL_Lib.dll

               0 characteristics
        4652C3B1 time date stamp Tue May 22 18:19:29 2007
            0.00 version
               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

              1    0 0000100A GetMax

      Summary

            4000 .data
            1000 .idata
            3000 .rdata
            2000 .reloc
           28000 .text
     


    可以看到GetMax函数在编译后在Dll中的名字仍为GetMax,所以在前面的程序中使用的是:
        pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax");

        然后,我们把WINAPI添加回去,重新编译DLL_Lib工程。运行刚才的DLL_Test程序,运行出错,结果如下:
    > process attach of dll
    Can't find function "GetMax"
    > process detach of dll
    Press any key to continue

        显然,运行失败原因是因为没有找到GetMax函数。再次运行命令:dumpbin /exports DLL_Lib.dll,结果如下(部分结果):

               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

              1    0 0000100A _GetMax@8
     


    从上面dumpbin的输出看,GetMax函数在WINAPI调用约定方式下在DLL里的名字与源码中的函数定义时的名字不再相同,其导出名是"_GetMax@8"。此时,你把testMain.c中的函数指针类型声明和函数查找语句作如下修改:
        typedef int (WINAPI* PGetMax)(int, int);
        pGetMax = (PGetMax)GetProcAddress(hDll, "_GetMax@8");
    再次编译链接,然后运行,发现结果又正确了。

        现在找到了问题所在。很显然,这种修改方式并不适用,而默认生成的名字又不是我们所想要的。那么该怎么解决这个问题呢?这就需要用到.def文件来解决。

    模块定义文件(.def)

        模块定义文件(.def文件)是一个描述DLL的各种属性的文件,可以包含一个或多个模块定义语句。如果你不使用关键字__declspec(dllexport)关键字导出DLL中的函数,那么DLL就需要一个.def文件。

        一个最小的.def文件必须包含下面的模块定义语句:
        (1)文件中第一个语句必须是LIBRARY语句。该语句标记该.def文件属于哪个DLL。语法形式为:LIBRARY <DLL名>。
        (2)EXPORTS语句列表。第一个导出语句的形式为:entryname[=internalname] [@ordinal],列出DLL中要导出的函数的名字和可选的序号(ordinal value)。要导出的函数名可以是程序源码中的函数名,也可以定义新的函数别名(但后面必须紧跟[=<原函数名>]);序号必须在范围1到N之间且不能重复,其中N是DLL中导出的函数个数。因此,EXPORTS语句语法形式为:
        EXPORTS
            <funcName1>[=<InternalName1] [@<num1>]
            <funcName2>[=<InternalName2] [@<num2>]
            ;...
        (3)虽然不是必须的,一个.def文件也常常包含DESCRIPTION语句,用来描述该DLL的用途之类,语法形式为:
        DESCRIPTION "<Description about the purpose of this DLL.>"
        (4)在任意位置,可以包含注释语句,以分号(;)开始。

        例如,在本文中后面将用到的.def文件为:

    ; DLL_Lib.def

    LIBRARY DLL_Lib     ; the dll name
    DESCRIPTION "Learn how to use the dll."

    EXPORTS
        GetMax @1
        Max=GetMax @2   ; alias name of GetMax

    ; Ok, over
     


        现在,让我们回到DLL_Lib工程,修改GetMax函数的声明,把EXPORT去掉,重新编译该工程。然后,运行dumpbin命令,我们发现此时没有导出函数。再将上面的DLL_Lib.def文件添加进DLL_Lib工程,再次编译,并运行dumpbin命令,得到如下结果(引用部分结果):

              1 ordinal base
              2 number of functions
              2 number of names

       ordinal hint RVA      name

             1    0 0000100A GetMax
             2    1 0000100A Max
     


        正如我们所预期的,有两个导出函数GetMax和Max。注意,此时源码中的GetMax函数的导出名不再是默认的“_GetMax@8”。另外,需要注意的是,两个导出函数有相同的相对虚拟地址(RVA),也说明了两个导出名实质是同一个函数的不同名字而已,都是源码中GetMax函数的导出名。

        现在,回到DLL_Test工程,修改testMain.c文件内容如下:


    /*
     * testMain.c
     */
    #include <windows.h>
    #include <stdio.h>

    typedef int (WINAPI* PGetMax)(int, int);

    int main()
    {
        int a = 2;
        int b = 3;
       
        HINSTANCE hDll; // DLL句柄
        PGetMax pGetMax; // 函数指针
       
        hDll = LoadLibrary(".//Debug//DLL_lib.dll");
        if (hDll == NULL) {
            printf("Can't find library file /"dll_lib.dll/"/n");
            exit(1);
        }
       
        pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax");
        if (pGetMax == NULL) {
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }
        printf(" GetMax(2, 3) = %d/n", pGetMax(2, 3));

        pGetMax = (PGetMax)GetProcAddress(hDll, "Max");
        if (pGetMax == NULL) {
            printf("Can't find function /"GetMax/"/n");
            exit(1);
        }
        printf(" Max(2, 3) = %d/n", pGetMax(2, 3));
       
        FreeLibrary(hDll);
        return 0;
    }
     


        编译链接、运行,结果如下:

    > process attach of dll
     GetMax(2, 3) = 3
     Max(2, 3) = 3
    > process detach of dll
    Press any key to continue

        运行结果正如前面分析的那样,GetMax和Max都得到了相同的结果。

        到这里,我们解决了DLL导出函数名在各种调用约定下的默认名可能不同于源码中函数名的问题。此时,你就可以制作跟Windows的自带API函数库相同的库了:使用__stdcall调用约定以满足Windows下的任何语言都可以调用DLL库,同时使用函数名作为导出名,以方便用户使用DLL里的函数。

    导出全局变量
     
        前面我们介绍了DLL中的函数的导出方法,这里也介绍一下DLL中全局变量的导出。
     
        首先需要明确的是,当多个应用程序同时使用同一个DLL时,系统中只有一个DLL实例(这里主要指代码段,一般不包含数据段)。也就是说,如果没有特殊处理,DLL中的数据都是每个使用DLL的应用都保留一份副本的(但是,可以根据需要实现DLL数据的共享,后面进行介绍)。因此,使用DLL的各应用程序之间不会发生干扰。
     
        要导出DLL中的全局变量,方法与导出函数基本一样。只是,在定义.def文件时,在EXPORTS定义语句之后用DATA标识符表明这是变量。例如:g_oneNumber DATA 或者 g_oneNumber @3 DATA。
     
        在使用DLL中导出的全局变量时,对于前面DLL的两种链接方式,有不同的方法。其中,对于运行时链接的DLL,其使用方法与函数一样(流程:LoadLibrary, GetProcAddress),只是在使用时要知道这是一个变量的地址,而不再是一个函数的地址即可(其实,用dumpbin工具查看DLL的导出列表,会发现导出的数据也被当作函数计数)。 对于装载时链接,要导入DLL中的变量,有点与函数不一样的地方,那就是必须显示地用关键字__declspec(dllimport)导入DLL中的变量。例如,在使用前面的g_oneNumber前,应先导入:__declspec(dllimport) extern int g_oneNumber。然后,其它与函数的使用方法无异。
     
    共享DLL中的数据
     
        有时,可能需要在使用DLL的多个应用之间共享DLL的数据,而默认情况下,DLL的数据是每个应用拥有一份副本的。要实现这个需求,就需要做些特殊处理。
     
        首先,定义一个数据段,里面有需要共享的变量,并要初始化这些变量。然后设置该数据段为共享即可,比较简单。例如,要在DLL中共享int型变量g_oneNumber,那么应按如下方式定义该变量:
    #pragma data_seg ("shared")       
    int g_oneNumber = 0;
    #pragma data_seg ()
     
    #pragma comment(linker,"/SECTION:shared,RWS")
     
        对上面的代码做些解释:#pragma data_seg ("shared")创建了一个数据段,命名为Shared;#pragma data_seg()标记该数据段的结束;它们之间定义的是该数据段中的变量。注意:这里对变量的初始化是必须的,否则,编译器会把未初始化的变量放在普通的未初始化数据段,而不是在共享的数据段。
        #pragma comment(linker, "SECTION:shared,RWS")告诉链接器shared数据段具有RWS属性。这里的RWS是指Read、Write和Shared三个属性。也可以在IDE中设置工程属性:在Settings|Link|Project Options中,添加链接参数:/SECTION:shared,RWS。
     
    资源DLL的制作及使用
     
       DLL 是仅包含资源(如图标、位图、字符串和对话框)的 DLL。使用纯资源 DLL 是在多个程序之间共享同一组资源的好方法。提供其资源被针对多种语言进行本地化的应用程序也是一种好方法。
        若要创建纯资源 DLL,请创建一个新的 Win32 DLL(非 MFC)项目,并将资源添加到此项目。 
        在“新建项目”对话框中选择“Win32 项目”,并在“Win32 项目向导”中指定 DLL 项目类型。 
        为 DLL 创建一个包含资源(如字符串或菜单)的新资源脚本,并保存该 .rc 文件。如果该.rc文件包含位图用记事本打开它,可以看到下面这样的一段 
    DIB_BKGD_HF             BITMAP            /"bkgd_**.bmp/"
    DIB_BKGD_GT  BITMAP    /"bkgd_***.bmp/"
    DIB_BKGD_BF  BITMAP    /"bkgd_*****.bmp/" 
        这些就是针对位图的申明,将相应的位图添加到这个里面就行了。 
        在“项目”菜单上单击“添加现有项”,然后在项目中插入这个新的 .rc 文件。 
        指定 /NOENTRY 链接器选项。/NOENTRY 防止链接器将 _main 引用链接到 DLL 中;此选项是创建纯资源 DLL 所必需的。 
    生成 DLL。
        使用纯资源 DLL 的应用程序应调用 LoadLibrary 来显式链接到 DLL。若要访问资源,请调用一般函数 FindResource 和 LoadResource,这两个函数对任何类型的资源都有效,或调用下列资源特定的函数之一: 
    FormatMessage 
    LoadAccelerators 
    LoadBitmap 
    LoadCursor 
    LoadIcon 
    LoadMenu 
    LoadString 
    使用完资源后,应用程序应调用 FreeLibrary。 
    下面部份是说明一下资源中的BMP如何动态加载并显示 
        hmodule = LoadLibrary(/"ExtendDLL.dll/");
        HDC   m_hdcMem;
        m_hdcMem = CreateCompatibleDC(hdc);
        BITMAP bm;
        RECT rect; 
        HBITMAP    hBitmap =  LoadBitmap(hmodule,/"DIB_BKGD_HF/");   //MAKEINTRESOURCE(IDB_BITMAP1)
        //HBITMAP    hBitmap = ( HBITMAP )LoadImage( hmodule, /"DIB_BKGD_HF/",IMAGE_BITMAP,0,0,LR_DEFAULTSIZE);
        GetObject( hBitmap, sizeof BITMAP, &bm);
        SelectObject( m_hdcMem, hBitmap);
        GetClientRect( hWnd, &rect);
        //::SetStretchBltMode(hdc,COLORONCOLOR); [Page]
        //::StretchBlt(hdc, rect.left, rect.top, rect.right, rect.bottom, m_hdcMem, 0, 0, bm.bmWidth, bm.bmHeight,SRCCOPY);
        BitBlt(hdc,0,0,bm.bmWidth,bm.bmHeight,m_hdcMem,0,0,SRCCOPY);
        ReleaseDC( hWnd, m_hdcMem );
        FreeLibrary(hmodule);


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zgb881020/archive/2009/03/31/4039682.aspx

    展开全文
  • Qt生成调用动态链接库dll

    千次阅读 2019-01-12 22:36:41
    这个文件就是链接库,又可以分为静态链接库和动态链接库。 1. 静态链接库 链接程序从库中寻找需要的符号(函数和变量的名字),查找到就将其放入可执行文件,未查找到就报错。 使用静态库链接的程序: (1)可...

    把编译好的包含函数和变量的目标代码存储到文件中,在链接的时候让链接程序自动从文件中查找需要的代码。这个文件就是链接库,又可以分为静态链接库和动态链接库。

    1. 静态链接库

    链接程序从库中寻找需要的符号(函数和变量的名字),查找到就将其放入可执行文件,未查找到就报错。

    使用静态库链接的程序:

    (1)可执行文件中包含所有需要调用的函数代码;

    (2)如果多个进程调用相同的库函数,内存中会存在多份库函数代码。

    静态库lib文件其实是打包好的obj文件。

    2.动态链接库

    动态链接库在程序装载内存的时候才真正地把库函数代码链接进行确定它们的地址,并且在内存中只有一份代码,可以被加载到不同进程的不同地址。

    生成动态链接库时一般包含3种文件:

    (1).h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。

    (2).lib文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误。动态链接库的lib文件与静态库不同,只包含被DLL导出的函数名称和位置,没有具体的实现,所以也叫导入库非必需,因为可以通过其他方式实现导入库的功能,如在显式连接中,可以使用WIN 32 API函数LoadLibrary导入DLL文件,再使用GetProcAddress函数获得对应函数;或者在Qt中,使用QLibrary类。

    (3).dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。

    2.1 隐式链接与显式链接

    动态链接库的调用分为隐式链接和显式链接。

    2.1.1 显式链接

    显式链接在程序需要用到库函数的时候再进行动态载入,需要在程序中通过某些函数来加载库和获得要使用的函数,所以是“显式”。

    Windows程序有相应的函数帮助实现显式链接,Qt也有相应的类来实现。

    显式链接的特点:

    1. 灵活性好,如果导入库或获得函数失败,程序设计的时候就可以考虑进行相应的处理。
    2. 程序载入速度快,在程序运行过程中,在需要时才加载dll
    3. 编译时不需要导入库,也不需要include头文件,只需要提供dll文件,但是要知道其中的函数信息(参数类型,返回类型)以供获取函数时使用
    4. 导入时需要额外的代码(加载库和函数)
    5. 如果在编译程序时并不能确定库和函数的名字,而是在运行时获得,就只能通过显式链接动态加载
    6. 因为C++类的成员函数的实际名字是修饰过的,所以调用时要使用特殊方法(目前没有研究过)
    7. 通过显式链接调用库的程序,在编译时是不会去管库文件的正确性的,只有运行时才能发现错误。

    2.1.2 隐式链接

    相应的,隐式链接中调用库函数时,就像调用本工程的函数,所以被称为“隐式”。

    它的使用需要include头文件,配置库文件的查找路径,当然dll文件也必不可少。

    头文件告诉编译器,程序中使用到的外部库函数的信息。当调用可执行文件的源代码被编译或被汇编时,DLL 函数调用在对象代码中生成一个外部函数引用。

    若要解析此外部引用,应用程序必须与 DLL 的创建者所提供的导入库(.LIB文件或.a文件)链接。

    隐式链接特点:

    1. 应用程序执行开始就载入dll,如果有很多dll,启动会较慢
    2. 使用时比显式链接更方便,就像用本程序中的函数,不需要特殊处理
    3. 如果载入时出现错误,整个程序就无法运行
    4. 确保头文件和导入库文件正确才能正确编译
    5. 调用类中的成员函数比较方便

    3 实例

    3.1 dll链接库工程

    在Qt中新建工程testDll,选Library -> C++ Library -> shared Library

    编辑头文件和源文件,在类中实现一个helloWorld方法,在类外实现一个add的普通函数

    头文件testdll.h,还有一个工程自动生成的testdll_global.h不需要修改,或者可以合入testdll.h

    #ifndef TESTDLL_H
    #define TESTDLL_H
    
    #include "testdll_global.h"
    
    
    class TESTDLLSHARED_EXPORT TestDLL
    {
    
    public:
        TestDLL();
        ~TestDLL();
        void helloWorld();
    };
    
    extern "C" TESTDLLSHARED_EXPORT int add(int a,int b);
    /*C++为了支持函数的重载,会在编译时将函数的参数类型信息以及返回值类型信息加入到函数名中,
    这样代码中名字一样的重载函数,在经过编译后就互相区分开了,调用时函数名也经过同样的处理,
    就能找到对应的函数了。
    所以要使用使用extern "C"链接标记,否则C++编译器会产生一个修饰过的函数名,
    这样导出函数的名字将不再是helloworld,而是一个形如" ?helloWorld@TestDll@@UAEXXZ”的名字。*/
    
    #endif // TESTDLL_H
    

    源文件testdll.cpp

    #include "testdll.h"
    #include <iostream>
    
    
    TestDLL::TestDLL()
    {
    }
    
    TestDLL::~TestDLL()
    {
    }
    
    void TestDLL::helloWorld()
    {
        std::cout << "hello world!"<<std::endl;
    }
    
    int add(int a, int b)
    {
        return (a+b);
    }
    

     build即可,不用点run,否则会在build完之后提示你找不到可执行文件(因为是库工程,不会生成可执行文件)。完成后在debug路径下会生成testDLL.dll,还有libtestDLL.a(使用MinGw)或者testDll.lib(使用MSVC编译器)。

    3.2 显式链接

    只讲最简单的

    通常Windows下程序显示调用dll的步骤分为三步(三个函数):LoadLibrary()、GetProcAdress()、FreeLibrary(),其中:

             LoadLibrary() 函数用来载入指定的dll文件,加载到调用程序的内存中(DLL没有自己的内存!)

             GetProcAddress() 函数检索指定的动态链接库(DLL)中的输出库函数地址,以备调用

             FreeLibrary() 释放dll所占空间 

     而QT的QLibrary类显示链接调用DLL的步骤:load()、resolve(const char * symbol )、unload()和VC步骤类似

     代码如下

    #include <QLibrary>
    #include<iostream>
    
    typedef int (*Fun)(int,int); //定义函数指针,int add(int a,int b);
    
    int main()
    {
        QLibrary mylib("testdll.dll");  //声明dll文件
    
        if (mylib.load())  //判断加载是否成功
        {
            std::cout << "DLL  loaded!"<<std::endl;
            Fun add = (Fun)mylib.resolve("add"); //链接到add函数
            if (add)
                {
                    std::cout << "Link to add Function is OK!"<<std::endl;
                    int result = add(5,6);
                    std::cout << result <<std::endl;
                }
        }
    
        else
        {
            std::cout << "DLL is not loaded!"<<std::endl;
        }
    
        mylib.unload ();
        return 0;
    }

    Windows将遵循下面的搜索顺序来定位DLL:
    包含EXE文件的目录
    进程的当前工作目录
    Windows系统目录(system/system32)。GetSystemDirectory 函数检索此目录的路径。
    Windows目录.GetWindowsDirectory 函数检索此目录的路径。
    列在Path环境变量中的一系列目录

    只需要把dll文件放在debug文件夹下(与exe放一起),或者Qt的程序,放在debug/release文件夹的上一级目录也可以。头文件/库文件都不需要。

    运行结果:

     3.3 隐式链接

    源文件需要include testdll.h,这个头文件放在工程目录中,和源文件放一起即可。调用函数和正常调用没有区别。

    #include <QCoreApplication>
    #include "testdll.h"
    #include <iostream>
    int main()
    {
        std::cout << "program begin" << std::endl;
        std::cout << add(5,6) <<std::endl;
        TestDLL testdll;
        testdll.helloWorld();
    }

     设置库的路径,使程序编译时能够找到。需要修改.pro 文件,增加库的路径

    LIBS += -LD:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/ -llibtestDLL
    

     固定形式,路径前的-L要大写, 之后库文件名前的-l小写,库文件名即.a 或.lib的文件名。

    dll文件的放置,很坑

    隐式链接时,可以设置从导入库文件的路径去寻找dll文件,如下图所示,add build library search path to PATH,也就是把导入库文件的路径设置到Path变量中。

    查看Path变量,会增加在LIBS中添加的D:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/

    程序也就会从该路径寻找dll文件。

    BUG!!! 新建的工程add build library search path to PATH是默认勾选的,但是实际并没有添加,退出Qt再进入才会生效啊,所以运行程序的时候会出现找不到dll文件的情况。

    此外,也可以自己放置dll文件,把路径手动加入上图中的Path中。

    运行结果与显式链接一致

    4 可能出现的问题

    4.1 隐式链接

    (1)如果没有指定库文件路径,程序编译时找不到库文件,会提示undefined reference 的错误,表示找不到要调用函数的引用。

    (2)

    LIBS += -LD:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/ -llibtestDLL

    指定的是导入库文件。如果把库文件删除掉或者路径不对,编译程序时会报错,no such file or directory   MinGW生成的是.a文件,MSVC是.lib文件。

     

    (3)如果程序中调用了dll文件中的内容,却没有找到dll文件,程序不会运行,直接退出,如下图所示:

    (4)如果没有调用,即使dll不存在,也不影响程序的运行。 注释掉调用add函数的语句,再编译程序并执行:

     

    参考

    LIB和DLL的区别与使用

    lib和dll文件的区别和联系

    Qt DLL总结【一】-链接库预备知识

    展开全文
  • C#调用动态链接库DLL

    千次阅读 2017-06-24 09:41:25
    1.概述动态链接库(Dynamic Linked Library):将写好的函数存在库中,以供其他程序开发调用,调用方式为“动态的”。 Windows为应用程序提供了丰富的函数调用,这些函数调用都包含在动态链接库中。其中有3个最重要的...
  • LabVIEW调用函数返回指针的动态链接库DLL引言创建动态链接库LabVIEW中调用DllMian.dll结束语 引言 LabVIEW通过调用库函数节点可以调用C/C++生成的动态链接库。首先在VC/CVI/Matlab等语言中设计好完成计算处理任务的...
  • 在说动态链接库DLL之前,我们要知道什么是库,库有哪些作用,以及如何判别何时用库?首先,我们要说明一般编译器的工作步骤,我们用fortran编写程序的一般步骤为:写代码阶段-&gt; 把程序转为目标文件(*.obj)...
  • "VC-基础:VC++动态链接库DLL编程深入浅出" 1.概论  先来阐述一下dll(dynamic linkable library)的概念,你可以简单的把dll看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上...
  • Windows动态链接库DLL的使用

    千次阅读 2012-11-14 10:34:37
    windows程序设计使用动态链接库可以有效的分隔大型项目的模块,DLL里面主要提供函数的调用接口(函数名)供其他的外部引用程序调用,调用者在完全不知道动态链接库中的实现方式的情况下,仍然能根据其提供的函数名,...
  • 【专题】C#调用动态链接库DLL

    千次阅读 2019-02-23 09:29:40
    动态链接库(Dynamic Linked Library):将写好的函数存在库中,以供其他程序开发调用,调用方式为“动态的”。  Windows为应用程序提供了丰富的函数调用,这些函数调用都包含在动态链接库中。其中有3个最重要的DLL,...
  • c# 封装动态链接库dll

    千次阅读 2017-08-08 20:54:11
    前天学习了下将自己的方法封装进dll,同时在其他的项目里引用封装的dll,并调用dll里的方法。同时还试探了下将Windows应用程序封装进dll(Winform),下面详细介绍。 一、建立 类库 将方法封装进dll 在VS里新建一...
  • C语言动态链接库DLL的加载

    千次阅读 2016-03-08 00:50:00
    静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据...动态链接库有两种加载方式:隐式加载和显示加载。 隐式加载又叫载入时加载,指在主程序载入内存时搜索D
  • C#调用C++编写的动态链接库dll文件

    千次阅读 2013-09-12 09:32:23
    C#调用C++编写的COM DLL封装时会出现两个问题: 1. 数据类型转换问题 2. 指针或地址参数传送问题    首先是数据类型转换问题。因为C#是.NET语言,利用的是.NET的基本数据类型,所以实际上是将C++的数据...
  • VC++动态链接库(DLL)编程

    千次阅读 2015-10-27 23:23:30
    1. 概论 先来阐述一下 DLL(Dynamic Linkable ...静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意, lib 中的指令都被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL
  • BCB如何编写,调用动态链接库DLL

    千次阅读 2005-03-01 14:36:00
    一 编写动态链接库DLL DLL简称动态链接库,是Windows中程序的重要组成部分。想象一下,一个程序需要多人共同完成开发,怎么个共同法?这时我们就要考虑把程序分为好几个模块,团队每一个成员开发一个模块。问题来了...
  • 动态链接库DLL)为模块化应用程序提供一种方式,使得更新和重用应用程序更加方便。注意只有在其它模块调用动态链接库中的函数时,动态链接库才会发挥作用。 另外,动态链接库是代码重用的绝佳方式,我们可以不必...
  • VC++动态链接库dll)编程视频教学

    万人学习 2016-12-26 12:24:02
    动态链接库的开发编译属于VC++程序员技能。 本课程学习之后能够理解动态链接库原理,学会编译静态库、动态库,学会通过lib和头文件链接动态库,学会直接通过代码访问dll中函数
  • C#中如何调用动态链接库DLL

    千次阅读 2016-11-29 22:42:56
    每种编程语言调用DLL的方法都不尽相同,在此只对用C#调用DLL的方法进行介绍。首先,您需要了解什么是托管,什么是非托管。一般可以认为:非托管代码主要是基于win 32平台开发的DLL,activeX的组件,托管代码是基于.net...
  • 动态链接库DLL

    千次阅读 2016-11-16 08:37:11
    链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行时...因此,由于动态链接库有这两种链接方式,所以在编写使用DLL的程序时,就有了两种可选方案。    可能有人会问“为
  • .h头文件 .lib动态链接库文件 .dll 动态链接库 .h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。 附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和...
  • LabVIEW调用DLL动态链接库

    千次阅读 2020-02-04 21:27:01
    LabVIEW调用DLL动态链接库 示例一 通过调用DLL实现数组求和。输入一个10个元素的数组,返回全部元素之和。 1.生成DLL VS中选择创建动态链接库项目 在头文件和源文件文件夹分别创建相应的.h .cpp文件 在test.h中...
  • VC与Matlab混合编程之调用动态链接库dll——<二> 1、是先建立 matlab 的 m 文件。 ellipsefit.m 其包含:function [Xc,Yc,A,B,Phi,P]=ellipsefit(x,y) 2.MCC命令编译生成得到h、dll、lib等文件 在matlab命令行...
  • VC++动态链接库(DLL)编程深入浅出

    千次阅读 2014-09-28 22:02:00
    在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。  静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若...
  • modbus rtu通信协议串口通讯动态链接库DLL(以下简称DLL),是为满足工业通信需要,针对工业领域要求上位机对PLC、工业仪表通讯实时采集与控制的组态编程而设计。本DLL是采用Delphi语言开发的标准串口通讯库,具有...
  • C#编写动态链接库类库dll文件

    万次阅读 2017-04-16 18:22:34
    1、DLL 即:动态链接库  DLL是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数...
  • 1、 不能调DLL里的Sub编译错误:不能找到方法或数据成员.把ActiveX DLL工程添加到标准EXE工程后就不会出错了..单独打开标准EXE工程调用生成的DLL就出错, 2、. 调用自己编写的DLL为什么不用像调API函数那样如:Public ...
  • 1.概论  先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿...在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。  静态链接库与动

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 29,707
精华内容 11,882
关键字:

动态链接库dll设计