VC2005创建和加载.DLL文件的方法

来源:互联网 发布:高会军 争议 知乎 编辑:程序博客网 时间:2024/05/22 04:58

转自:http://blog.sina.com.cn/s/blog_6a0cb8bc0100uzhn.html

动态链接库(DLL)简介

  动态链接库(DLL)一直都是windows OS的基础.动态链接库通常都不能直接运行,也不能接受消息.他们是一些独立的文件,其中包含能被可执行程序或其他DLL调用来完成某项工作的函数,只有在其他模块调用动态链接库中的函数时,他才发挥作用。

静态库
  函数和数据被编译进一个二进制文件(通常是.LIB)中.在使用静态库的情况下,在编译连接可执行文件时,连接器从库中赋值这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE).当产品发布时,只需要发布这个可执行文件,并不需要发布被使用的.LIB.代码已经嵌入到源程序中.

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

DLL文件的创建
  先新建项目--Visual C++--Win32--Win32控制台应用程序--"确定"--选择"DLL"--完成
  【原】VC2005创建和加载.DLL文件的方法

【原】VC2005创建和加载.DLL文件的方法

这是工程中会自动添加一个与工程名称一样的.cpp文件

【原】VC2005创建和加载.DLL文件的方法

删除编译器自动产生的代码

然后自己添加需要实现的函数(包括函数体)

例如:

  int add(int a,int b){

     return a+b;

  }

  int subtract(int a,int b){

     return a-b;

  }

然后编译....

完成后会出现如下提示

【原】VC2005创建和加载.DLL文件的方法

此时直接点击取消即可,这是就能在工程目录中找到DLL文件了,但是此时没有.lib文件

【原】VC2005创建和加载.DLL文件的方法

DumpBin命令

  首先需要注意的是:应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出(_declspec(dllexport))的函数.为了查看一个DLL中有哪些导出的函数,可以使用VS中提供的命令Dumpbin来实现.

可以首先进入到目标DLL文件的目录下,然后运行Dumpbin命令

例如:

dumpbin -exports testDll.dll

【原】VC2005创建和加载.DLL文件的方法
这里没有看见任何与函数相关的信息,这说明testDll.dll中没有导出的函数

从DLL中导出函数

  为了让DLL导出一些函数,需要在每一个将要被导出的函数前面添加标识符:_declspec(dllexport)

于是需要修改原函数定义为

  _declspec(dllexport) int add(int a,int b){

     return a+b;

  }

  _declspec(dllexport) int subtract(int a,int b){

     return a-b;

  }

然后编译,通过dumpbin命令即可查看导出的函数了

【原】VC2005创建和加载.DLL文件的方法


此时又生成了两个新文件,包含引入库(testDll.lib)文件和输出库(testDll.exp)文件..lib中保存的就是相对应的.dll文件中导出的函数和变量的符号名,输出库不重要.

通过dumpbin命令查看

dumpbin -exports testDll.dll

【原】VC2005创建和加载.DLL文件的方法

这里多出来的一些输出信息,一共有4列

ordinal:"1"和"2"是导出函数的序号;

hint:列出的数字是提示码,该信息不重要.

RAV:列出的地址值是导出函数在DLL模块中的位置,也就是说,通过该地址值,可以在DLL中找到他们.

name:列出的是导出函数的名称,这里的函数名称比较奇怪,这是因为C++重载函数的原因,因为冲在函数有相同的函数名,为了加以区分,C++编译器在编译链接的时候会按照自己的规则篡改函数名称,这一过程称为"名字改编"

加载DLL链接库

  这里可以新建一个测试DLL的工程

  在主调函数的外部加入dll中函数的声明


extern int add(int a,int b);

extern int subtract(int a,int b);

这时候编译...

会出现三个错误,都是发生在链接阶段

【原】VC2005创建和加载.DLL文件的方法
看来是因为没有加入dll的原因,因为程序无法找到add和subtract的函数主体,没法调用

为了解决这个问题可以将目标原先创建的引入库(.lib)文件复制到Dlltest项目所在的目录下,这这文件中就包含了testDll.dll中的导出函数的符号名.

【原】VC2005创建和加载.DLL文件的方法

先找到准备移动的引入库文件

复制

【原】VC2005创建和加载.DLL文件的方法
引入完成后需要在VS2005工程属性中加入这个testDLL.lib文件,如下:

【原】VC2005创建和加载.DLL文件的方法

这时候再次编译的,则会成功的生成test2.exe可执行文件.也就是说,当应用程序需要调用某个动态链接库提供的函数时,在程序链接时只需要包含该动态链接库提供的输入库文件(.lib)就可以了.(函数的引入库.lib没有包含实际的代码,他只是用来为链接程序提供必要的信息,以便在可执行文件中建立动态链接时需要用到的重定位表)

仍然可以通过dumpbin查看可执行(.exe)文件的输入信息以及其加载DLL的信息

dumpbin -imports test2.exe

【原】VC2005创建和加载.DLL文件的方法

从图中的输入信息可以看到,test2.exe程序需要调用到的testDll.dll链接库中的两个函数:add和subtract,另外还可以从中看到一些需要用到的其他DLL文件以及其中所需要用到的函数,并不是dll中的所有函数这个文件都会用上

注意:这里是通过复制.lib的方法使程序编译成功的,另外一种方法是可以在VS2005项目属性中设置相应的属性完成对.lib文件的引用,这样就不用复制.lib文件到项目所在目录中了,上图:

【原】VC2005创建和加载.DLL文件的方法


然后继续像第一种方法一样在输入中加入testDll.lib即可

【原】VC2005创建和加载.DLL文件的方法

再次运行test2.exe程序,但发现程序会弹出如下图所示的错误对话框,提示无法找到testDll.dll文件

【原】VC2005创建和加载.DLL文件的方法

这涉及到.dll文件的加载顺序规则问题了,当引用程序运行的时候,系统将为它分配一个4GB的地址空间,然后加载模块会分析该应用程序的输入信息,从中找到该程序要访问动态链接库信息,然后在用户机器上搜索这些动态链接库,进而加载他们.搜索的顺序如下:

1.程序的运行目录,即.exe所在的目录:

例如

【原】VC2005创建和加载.DLL文件的方法
即.exe所在的目录

2.当前目录

3.系统目录:

  依次是--C:\WINNT\SYSTEM32;C:\WINNT\SYSTEM;C:\WINNT

4.path环境变量中所列出的路径

其实总结起来,就是按照path环境变量来处理.dll的加载规则的

利用_declspec(dllimport)声明外部.dll中的函数

除了使用extern关键字声明函数是外部定义的函数外,还可以使用_declspec(dllimport)来声明函数是从动态链接库中引入的.例如:

_declspec(dllimport) int add(int a,int b);

_declspec(dllimport) int subtract(int a,int b);

然后编译....

一样可以运行,与使用extern关键字这种方式比,使用_declspec(dllimport)声明的外部函数会告诉编译器该函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码.

完善DLL例子

一个dll实现之后通常都会交给客户程序,以便后者能够访问该dll.但是客户端程序如何知道该dll中有哪些导出函数呢?通常在编写动态链接库时都会提供一个头文件,在此文件中提供的dll导出函数原型的声明,以及函数的有关注释文档.这样可以在工程中添加一个头文件testDll.h

内部添加

_declspec(dllimport) int add (int a,int b);

_declspec(dllimport) int subtract(int a,int b);

这里注意testDll.h是给该dll的客户端,即调用该dll的程序使用,就是这里的test2.exe,因此在声明的函数头部使用的是"dllimport"关键字,向客户端程序表明他们是从动态链接库中导入的.

然后在test2工程中加入头文件testDll.h,在程序的头部加入#include"testDll.h"随即可以使用了

这里可以对testDll.h进行改造,使其不仅能够为调用动态链接库的客户端程序服务,同时也能够由动态链接库程序自身来使用:

#ifndef

#define DLL_API _delcspec(dllimport)

#endif

DLL_API int add(int a,int b);

DLL_API int subtract(int a,int b);

接下来,在动态链接库的源程序:testDll.cpp文件中首先利用#define指令定义DLL_API宏,然后在用#include"testDll.h"包含上述的头文件,之后在定义add和subtract函数时就不用再指定_declspec(dllimport)了,如下

#define DLL_API _declspec(dllexport)

#include "testDll.h"

int add(int a,int b);

int subtract(int a,int b);

在程序编译的时候头文件是不参与编译的,源文件单独编译.

从DLL中导出C++类

跟函数情况基本相同,是在class 和类名之间加入导出标识符DLL_API宏,这样就能导出整个类了.

但是像private和protected在外界是无法访问的,只有public能直接访问

#ifndef DLL_API

#define DLL_API _declspec(dllimport)

#else

#define DLL_API _declspec(dllexport)

class DLL_API Point{

    public:

         void output(int x,int y);

}

而其余的步骤与只包括函数的dll文件是一样的,即把一个class当做一个function处理即可!~

0 0