使用VC6.0创建一个空的动态链接库工程,建立一个空的文件,添加源文件
编写一个函数
如:int add (int a, int b)
{
return a+b;
}
int subtract (int a, int b)
{
return a-b;
}
这时,我们可以用VC6.0自带的工具dumpbin工具来查看此动态链接库导出的函数
当我们在cmd窗口下路径为C:/Program Files/Microsoft Visual Studio/MyProjects/Dll1/Debug>下执行dumpbin时出现以下错误:
C:/Program Files/Microsoft Visual Studio/MyProjects/KeyBoardDll/Debug>dumpbin
'dumpbin' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
这种情况是由于系统无法找到dumbpin而产生的错误,解决方法:
在C:/Program Files/Microsoft Visual Studio/VC98/Bin下找到VCVARS32.BAT,在cmd下执行此批处理,重新安装该工具执行环境即可,但仅在该cmd窗口下有小,关闭后必须重新执行该批处理
Dumpbin成功后如图:
C:/Program Files/Microsoft Visual Studio/MyProjects/Dll1/Debug>dumpbin -exports dll1.dll
这时cmd窗口仅显示:
Summary
7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text
没有导出的函数信息,这是为什么呢?
修改动态链接库函数:
_declspec(dllexport) int add (int a, int b)
{
return a+b;
}
_declspec(dllexport) int subtract (int a, int b)
{
return a-b;
}
重新执行dumpbin显示正确信息:
我们发现导出函数名很怪:
这里是不同的编译器会按照自己的规则把输出函数名字改变了,那么用其他编译器生成的程序在调用时会出现不能识别导出函数问题,该怎么办呢?
别慌,一会再讨论这个问题,下面我们先做一个简单的测试程序,
基于对话框的应用程序,放置2个按钮,一个为add,一个为subtract,添加命令消息响应函数,(仅拿add做实验)添加代码:
extern int add(int a,int b);
extern int subtract(int a,int b);//声明add方法是在外部的调用的函数
void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5+3=%d,",add(5,3));
MessageBox(str);
}
当我们在连接时出现3个错误:
DllTest1Dlg.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
DllTest1Dlg.obj : error LNK2001: unresolved external symbol "int __cdecl subtract(int,int)" (?subtract@@YAHHH@Z)
原因:连接器不知道add函数在什么地方实现,所以出错。
解决方法:
我们可以复制输入库文件Dll1.lib文件到测试程序目录,并在工程---设置---LINK,在Object/library modules下写上:Dll1.lib,再次编译,没有出现问题。
Dll1.lib输入库文件作用:并没有包含实代码,只是用来为我们的连接程序提供信息,以便在我们的可执行文件(.Exe文件)中建立动态连接时的要用到的重定位表,我们可以查看可执行文件的输入信息,仍然使用dumpbin
C:/Program Files/Microsoft Visual Studio/MyProjects/Dll1/Debug>dumpbin -imports dlltest.exe
可以看到有Dll1.dll的输出函数。
接下来我们总可以测试了吧,点击执行,出错!!!
说是找不到动态链接库,简单,我们把Dll1.dll复制到测试程序的目录下(工程目录和debug目录都可以),重新运行OK
这里,我们要提一下Dependency Walker了,它是VC6.0带的工具,我们可以在Microsoft
Visual C++ Tools中找到。它是图形化界面大大方便我们观察程序所依赖的DLL文件以及该Dll所依赖的其他DLL。
我们把Dll1.dll放到DllTest程序的debug目录下,用dependency Walker打开DllTest;
我们可以改变extern int add(int a,int b);
extern int subtract(int a,int b);
为:
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b)执行,没有错误。
这里我们为什么要这样呢,原因如下:
添加_declspec(dllimport)告诉编译器我们所引用的符号是从动态链接库的.lib文件中输入的,编译器可以生成运行效率更高的代码,所以我们都应该用这个来声明从动态链接库调用的函数。
这里我们可以思考一个问题,如果我们把自己编写好的动态链接库提供给他人使用,他们如何得知,动态链接库中导出了哪些函数,那么他们不得不用一些工具来猜测了。
所以,为了解决这种情况,我们一般增加DLL文件的头文件,其中包含了注释和导出函数,方便他人使用。
增加DLL头文件,在头文件中写入
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b)
在DllTest中注释掉_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b)
把Dll1.h复制到DllTest工程,并包含头文件include “Dll1.h”
当然,我们也可以改造Dll1.h让我们的Dll1.cpp也使用头文件
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
DLL1_API int add(int a,int b);
DLL1_API int subtract(int a,int b);
Dll1.cpp
#define DLL1_API _declspec(dllexport)
#include "Dll1.h"
DLL1_API int add(int a,int b)
{
return a+b;
}
DLL1_API int subtract(int a,int b)
{
return a-b;
}
还记得我们用dumpbin察看Dll1.dll时其中的导出函数名吗?它的名字非常的奇怪,如果我们使用其他的编译器来使用Dll1.dll文件时会出错,因为每种编译器按照不同的格式来规定其导出函数名字。如果我们不让函数名改变是不是就可以使我们的Dll1.dll变得通用了。
其实是可以的。在导出函数前加上cetern “C”如extern “C” _declspec(dllimport) int add()
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern “C” _declspec(dllimport)
#endif
DLL1_API int add(int a,int b);
DLL1_API int subtract(int a,int b);
Dll1.cpp
#define DLL1_API extern “C”_declspec(dllexport)
#include "Dll1.h"
DLL1_API int add(int a,int b)
{
return a+b;
}
DLL1_API int subtract(int a,int b)
{
return a-b;
}
但是,在我们导出函数的调用约定改变时,即使加上extern “C”,导出函数名也会发生改变。
我们在导出函数加上 _stdcall 即pascal调用
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern “C” _declspec(dllimport)
#endif
DLL1_API int _stdcall add(int a,int b);
DLL1_API int _stdcall subtract(int a,int b);
Dll1.cpp
#define DLL1_API extern “C”_declspec(dllexport)
#include "Dll1.h"
DLL1_API int _stdcall add(int a,int b)
{
return a+b;
}
DLL1_API int _stdcall subtract(int a,int b)
{
return a-b;
}
这时,我们用dumpbin察看,函数名仍然发生改变:
还有其他的方法能够使Dll导出的函数名不发生改变吗?答案是有的
重新创建一个Dll2工程
Dll2.cpp
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}
添加Dll2.def文件
LIBRARY Dll2
EXPORTS
add
subtract
这时用dumpbin察看,没有发生改变
这里,我们使用def文件即模块定义文件,并把它增加到Dll2工程中,
LIBRARY Dll2 指定我们动态链接库的内部名称,必须和Dll2.dll名相同。这句定义并不是必须的。
EXPORTS
add
subtract 指定要到处那些函数,并指定到处符号名,使我们导出的函数名不变。
更多EXPORTS用法查阅MSDN
以上均为隐式调用动态链接库,那么怎么动态的加载一个动态链接库呢?我们将继续探讨。