动态链接库的加载方式:1.隐式加载 和 2.显示加载
进步的点滴的博客
http://blog.sina.com.cn/u/1698831304
一、先做一个隐式加载的例子:
1)用MFC向导新建一个Win32 Dynamic-Link Library的工程项目,名字叫做Dll1,
并且新建一个Dll1的C++源文件
2)在Dll1.cpp文件中,编写完成加法与减法运算的函数add()和substract()
3)编译之后,我们可以在工程目录的Debug文件夹中发现一个Dll1.dll的文件,
这就是动态链接库文件。
现在已经有了一个动态链接库,那么怎么让其他程序访问它呢?
首先,动态链接库中的文件必须被导出后才能被其它程序访问。
我们可以用VC自带的工具Dumpbin来查看那些动态链接库的函数被导出了。
可在命令行下进入Debug所在目录,输入以下命令
dumpbin -exports dll.dll
有些时候由于某种安装原因,dumpbin被认为是无效命令,可以到VC的安装目录
..\Microsoft Visual Studio\VC98\Bin\下
找到VCVARS32.bat并在命令行运行,之后就能执行dumpbin命令了。
输入dumpbin -exports Dll1.dll 就可以查看函数的导出状况了,
不过我们没有看到任何导出的函数。
怎样才能让我们自己编写的函数被导出呢?
只要在每个函数定义的前面加上_declspec(dllexport)标记就行了,如下所示:
_declspec(dllexport) int Add(int x,int y)
{
return x+y;
}
_declspec(dllexport) int Subtract(int x,int y)
{
return x-y;
}
加了_declspec(dllexport)以后再编译一下,就会再生一个*.lib引入库文件和一个*.exp文件,里面包含的是导出的函数或者变量的符号名。而exp文件是一个输出库文件。
这时我们再一次在命令提示符下面输入命令:dumpbin -exports Dll1.dll
就会有下面的结果出现
然后,新建一个基于对话框的MFC AppWizard 测试工程,在对话框上添加Add与SubTract按钮,
在这两个按钮中,分别调用动态链接库中所写的两个函数。
不过为了使编译器认识动态链接库的Add,Subtract,必须在之前使用两个声明:
extern int Add(int x,int y);
extern int Subtract(int x,int y);
接下来就可以在两个按钮中调用动态链接库的函数了,代码如下:
void CDllTestDlg::OnBtnAdd()
{
CString str;
str.Format("3+5=&d",Add(3,5));
MessageBox(str);
}
void CDllTestDlg::OnBtnSubtract()
{
CString str;
str.Format("5-3=%d",Subtract(5,3));
MessageBox(str);
}
注意:在编译之前需要将前面编译好的Dll1.dll和Dll1.lib拷贝到DllTest项目目录下,
同时按下F7,在工程设置中的Link选项卡的Object/library modules中
(在project--->setting--->link--->Object/Library Modules)填写Dll1.lib,
否则虽然声明了动态链接库的两个函数,在编译的时候不会出错,
但是在链接的时候会找不到这两个函数,从而报错。
如果要查看DllTest.exe文件信息,使用命令行dumpbin -imports dlltest.exe
上面说过,在调用动态链接库的两个函数之前,应该事先用extern声明一下,
其实也可以用_declspec(dllimport)标识符声明,所以我们可以用
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
取代
extern int Add(int x,int y);
extern int Subtract(int x,int y);
而且用这种方法有个好处,_declspec(dllimport)会告诉编译器要引用的函数是从*.lib文件中输入的,
这可以让编译器生成效率更高的代码。
---------------------------------------------------------------------------------
二、我们在编写动态链接库的时候,往往会提供一个包含导出函数的原型声明的头文件,
可以将这个头文件提供给其他使用动态链接库的开发人员,
他们就可以通过这个头文件获得导出函数的信息以及相关的说明文档。
下面在动态链接库工程中添加一个这样的头文件,步骤如下:
1)增加一个Dll1.h的头文件
2)事实上,这个头文件是提供给客户端使用的,所以我们把声明动态链接库两个函数的任务放到这里
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
这样,我们只要在DllTest工程中#include这个头文件,就声明了动态链接库的两个函数。
3)可以对这个头文件改进一下,让它既可以为客户端使用,也可以为动态链接库本身服务。
首先在Dll1.h中添加一个条件编译指令,代码如下:
在Dll.cpp文件,首先定义一个宏,#define DLL_API _declspec(dllexport)
然后包含这个头文件:#include "Dll.h"
接着去掉函数定义前的_declspec(dllexport)标识符,
代码如下:
着重介绍一下这个技巧,在程序编译的时候,头文件是不参与编译的,源文件预编译的时候,如果碰到#include <xxx.h>,就把xxx.h中的文本内容全部复制到相应的位置,在编译Dll1.cpp文件时,首先定义DLL1_API宏,将其定义为_declspec(dllexport),然后碰到Dll1.h头文件,将其展开,由于已经定义了Dll1_API,所以后面的两个函数的声明都是_declspec(dllexport),表示为导出函数,之后这个Dll交给其他程序使用,先包含头文件,只要这个函数没有定义DLL1_API,则对于其他程序,这两个函数都是_declspec(dllimport),表示导入函数。
4)接下来导出整个类,代码:
注意:在客户端对类成员函数的访问,仍然受制于访问权限
定义类的时候在Class 与 point之间加了DLL_API就会导出整个类。
类的实现:
接下来我们在测试工程中新建一个按钮,调用动态链接库函数,代码如下:
---------------------------------------------------------------------------------
三、我们自己编写的到这里已经可以在C++程序中正常运行了,但是如果把这个动态链接库拿给其他语言用,比如拿给C语言调用,又会出问题,问题就在于C++导出或导入动态链接库会发生名字的改编,
在C语言中导入动态链接库时,不会正确地将名字改编回来,于是会找不到函数。
那么有没有办法使编译的时候不发生名字改编呢?
只要在定义DLL_API宏的时候,加上一句extern "C" (C要大写)就行了,如下:
#define DLL_API extern "c" _declspec(dllexport)
这样编译器就不会进行名字改编,导出的函数跟我们自己写的一模一样,
一个用C语言编写的客户端程序就可以调用这个用C++编写的动态链接库。
注意:用extern "c"不让名字发生改编有两个缺点是,
一是不能导出类中的函数,只能导出全局函数;
二是如果函数使用标准调用约定_stdcall,即使用了extern "c",函数仍会发生改编。
---------------------------------------------------------------------------------
四、上面的方法可以实现编译时不发生名字改编,但是有两个缺点,特别是使用_stdcall时,
仍然会发生名字改编。下面介绍一种模块定义文件的方式来解决这个问题:
1)接下来新建一个动态链接库文件,文件名为Dll2.cpp文件代码为:
2)新建一个模块文件Dll.def(可以先新建一个文本文档,然后将它改成Dll2.def,
注意连扩展名也要改),将其加入要项目工程当中。
3)在VC编辑器中对Dll2.def进行编辑,添加代码
LIBRARY Dll2:指定动态链接库的内部名称,这句不是必须的
EXPORTS :即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的
Add与Subtract:字符串
EXPORTS 语句引入了一个由一个或多个 definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS 关键字可以在第一个定义所在的同一行上或在前一行上。
*.def 文件可以包含一个或多个 EXPORTS 语句。
4)下面在DllTest工程中显式地加载这个动态链接库,并且测试一下Dll2。
动态加载动态链接库要用到LoadLibrary函数;
接下来在MFC文件中改写按钮void CDllTestDlg::OnBtnAdd() 代码:
注意:1.因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件
2.如果我们在动态链接库中使用标准调用约定_stdcall来导出函数,在模块定义文件方式下,导出的函数名字不会发生改编。但是在客户端的可执行程序中,定义函数指针类型时也要采用_stdcall调用约定:typedef int(_stdcall * ADDPROC)(int a , int b );
3.动态调用动态链接库时,最好不要发生名字改编,不然函数名字发生改编之后我们不知道函数名字就无法根据函数的名字进行调用了。当然如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字,如:
ADDPROC Add=(ADDPROC)GetProcAddress( hInst,MAKEINTRESOURCE (1));