动态链接库dll的介绍,编写和调用

1. DLL的简介

DLL 是 Dynamic Link Library 的缩写,译为“动态链接库”。DLL也是一个被编译过的二进制程序,可以被其他程序调用,但与 exe不同,DLL不能独立运行,必须由其他程序调用载入内存。DLL 中封装了很多函数,只要知道函数的入口地址,就可以被其他程序调用。DLL是相对于静态链接库的。

静态链接库: 函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

动态链接库在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件和一个DLL (.dll)文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质上的区别,对一个DLL来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。这时,在发布产品时,除了发布可执行文件以外,同时还要发布该程序将要调用的动态链接库。

2. DLL的编写

 DLL 程序的入口函数是 DllMain(),就像 DOS 程序的入口函数是 main()、Win32 程序的入口函数是 WinMain()一样。前面我们一直在讲的就是DOS程序。DllMain() 函数的原型为:

BOOL APIENTRY DllMain( 
HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved );

其中:

  • hModule 表示本DLL程序的句柄。
  • ul_reason_for_call
    表示DLL当前所处的状态,例如DLL_PROCESS_ATTACH表示DLL刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示DLL刚刚从一个进程中卸载。
  • lpReserved 表示一个保留参数,目前已经很少使用。

2.1.先利用Vs2012新建一个 Win32 Dynamic-Link Library 类型的工程,工程取名为 dllDemo,并选择“An empty Dll project”选项,即创建一个空的动态链接库工程。然后,为该工程添加 一个C源文件 main.c,并在其中编写完成加法运算和减法运算的函数,代码如下所示

 #include <windows.h>  // 也可以#include <objbase.h>
#include <stdio.h>  
_declspec(dllexport) int add(int a, int b){ //读者要记住,应用程序如果想要访问某个DLL中的函数,
    /*那么该函数必须是已经被导出的函数。为了导出一些函数,需要在函数前面添加标识符 _declspec(dllexport)。*/
    return a+b; 
} 
_declspec(dllexport)int sub(int a, int b){  
    return a-b;
}  
BOOL APIENTRY DllMain(    
    HANDLE hModule,     
    DWORD  ul_reason_for_call,   
    LPVOID lpReserved ){  
        if(ul_reason_for_call == DLL_PROCESS_ATTACH)
        {     
            printf("\nCongratulations! DLL is loaded!\n");   
        }
        return (TRUE);
                                          }

2.2 然后利用Build命令生成dllDemo这一动态链接库程序。之后,在该工程的Debug目录下, 可以看到有一个dllDemo.dll文件,这就是生成的动态链接库文件。

 

 对于一个不知道的dll文件,我们想查询里面所包含的导出函数,怎么处理?

(1)首先在dos下,运行vs安装目录下的VC文件夹中包含的 VCVARS32.bat这个批处理文件 (2)切换到dll所在工程文件Debug目录下, 输入dumpbin -exports dllDemo.dll 命令,然后回车,即可查看DLL中的导出函数 如本例:   

注意红色方框标出的信息: ordinal hint RVA name 1 0 000110D7 add 2 1 000110EB sub在这段信息中,”ordinal” 列列出的信息 ‘1’ 和 ‘2’ 是导出函数的序号;”hint” 列列出的数字是提示码,该信息不重要;”RVA” 列列出的地址值是导出函数在DLL模块中的位置,也就是说,通过该地址值,可以在DLL中找到它们;最后一列 “name” 列出的是导出函数的名称。

 

3. DLL的加载及函数的调用

链接库有两种加载方式:隐式加载和显示加载

  • 隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
  • 显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验

隐式加载(以C语言为例,说明过程。)

新建项目文件及源文件和头文件

头文件:dllDemo.h

#ifndef _DLLDEMO_H
#define _DLLDEMO_H 
_declspec(dllexport) int add(int, int);
_declspec(dllexport) int sub(int, int); 
#endif

源文件:main.c

#include <stdio.h>
#include <stdlib.h>
#include "dll.h" 
#pragma comment(lib, "D:\\works_phd\\CODE_phd2014\\C\\files\\dllDemo.lib") 
 
int main()
{  
    int a=10, b=5; 
    printf("a+b=%d\n", add(a, b)); 
    printf("a-b=%d\n",sub(a, b)); 
    system("pause");
    return 0;
}

lib 文件包含DLL导出的函数和变量的符号名,只是用来为链接程序提供必要的信息,以便在链接时找到函数或变量的入口地址;.dll 文件才包含实际的函数和数据。所以首先需要将 dllDemo.lib 引入到当前项目。 #pragma comment(lib, “D:\\works_phd\\CODE_phd2014\\C\\files\\dllDemo.lib”) 实现此目的。 此外,还需要将上面生成的dll文件复制并粘贴到当前工程目录下。

显式加载:
main函数:
#include <stdio.h>
#include <stdlib.h>
#include<windows.h> // 必须包含 windows.h 
typedef int (*FUNADDR)(int a, int b); // 指向函数的指针 
int main()
{  
    int x=10, y=5;  
    HMODULE dllDemo = LoadLibrary("D:\\works_phd\\CODE_phd2014\\C\\files\\dllDemo.dll"); //获取dll或者exe文件句柄;HMODULE可以更换为HINSTANCE
    //如果代码将引号包含的文件当字符串处理,修改项目属性,将“字符集”设置为“使用多字符集”
    FUNADDR add, sub; 
    if(dllDemo)
    { 
        add = (FUNADDR)GetProcAddress(dllDemo, "add"); //获取函数的地址
        sub = (FUNADDR)GetProcAddress(dllDemo, "sub");
    }
    else
    {
    printf("Fail to load DLL!\n");
    system("pause"); 
    exit(1);
    } 
    printf("a+b=%d\n", add(x, y)); //调用函数
    printf("a-b=%d\n", sub(x, y));  
    system("pause");
    return 0;
    FreeLibrary(dllDemo); 
}
Updated: 2014-11-08 — am11:32

Leave a Reply

  Subscribe  
Notify of