上次发了个动态库的静态调用例子,
现在改进了一下.使用动态调用.
Windows API中的所有函数都包含在DLL中。其中有三个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件);
在使用动态库的时候,往往提供两个文件:一个因入库和一个DLL。因入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。
静态调用的时候,需要连接.lib文件和动态库的开放.h文件.(extern"C"情况下可以不需要.lib) ,
而动态调用的时候完全不需要包含头文件等. 动态,需要时才加载所需要的.DLL文件.
因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件
如果我们在动态链接库中使用标准调用约定_stdcall,而在可执行程序中使用动态加载DLL,会发生名字重编,如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字,如:
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
为使编译器认识Add,Subtract,必须在之前使用两个声明:
extern int Add(int x,int y);
extern int Subtract(int x,int y);
可以使用标示符表示这两个函数是从动态链接库的.lib文件引用的,以生成效率更高的代码
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
这两段代码我们也可以在DLL中新建一个头文件放进去
好了,发代码:
dll.h
#ifdef DLL_API
#else
#define DLL_API extern"C" _declspec(dllimport)
#endif
DLL_API int _stdcall add(int a,int b);
DLL_API int _stdcall subtract(int a,int b);
dll.cpp
#define DLL_API extern"C" _declspec(dllexport)
#include "dll.h"
#include <stdio.h>
//extern"C"--C风格(否则为C++ 名字改编形式)
//C风格时,只可以调用全局函数,而不能调用类的成员函数 , 不用.lib????
//_declspec(dllexport)告诉编译器这个动态库,提高效率。
int _stdcall add(int a , int b){
return a+b;
}
int _stdcall subtract(int a,int b){
return a-b;
}
//int _stdcall add(int a,int b) 其中_stdcall表明 是Windows API标准调用约定 否则默认为C风格
//在有 _stdcall的情况下 extern"C"没有效果
//但是, .def文件却可以不受影响地阻止name manging
为了最终解决问题,我们可以新建一个模块文件Dll.def,以使得其他语言编制的程序也能使用我们的动态链接库。
dll.def
LIBRARY DLL
EXPORTS //即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的 add 和subtract
add
subtract
//以上三个文件一起编译就可以生成一个DLL.dll文件
//以下是测试代码:
TestDLL.cpp
/* 动态调用DLL*/
#include <stdio.h>
#include <windows.h>
// 若DLL没有定义_stdcall(winAPI标准调用约定)则为
//typedef int(*lpAddFun)(int, int); 默认情况下为C风格调用
typedef int(_stdcall *lpAddFun)(int, int); //宏定义函数指针类型
typedef int(_stdcall *lpSubtractDun)(int,int);
//int main(int argc, char *argv[])
int main()
{
HINSTANCE hDll; //DLL句柄
lpAddFun addFun; //函数指针
lpSubtractDun subtractFun;
hDll = LoadLibraryA("DLL.dll");
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll, "add");
subtractFun=(lpSubtractDun)GetProcAddress(hDll,"subtract");
if (addFun != NULL)
{
int aa=20;
int bb=6;
int result = addFun(aa, bb);
printf("<aa + bb> is %d /n", result);
int result1 = subtractFun(aa,bb);
printf("<aa - bb> is %d /n",result1);
}
FreeLibrary(hDll);
}
char ccc[256]="";
gets(ccc);
return 0;
}
当我们的动态链接库不再使用时可以调用FreeLibrary使动态链接库使用计数减1,当使用计数为零时,系统会收回模块资源
说明:
因为C++导出或导入动态链接库会发生名字的改编,如果不想发生名字改编,我们可以使用如下代码:
#define DLL_API extern "c" _declspec(dllexport)
这样编译器就不会进行名字改编,一个用C语言编写的客户端程序就可以调用这个用C++编写的动态链接库。其缺点是,不能导入类中的函数
如果函数使用标准调用约定_stdcall,即使使用了extern "c",此函数仍会发生改编
为了最终解决问题,我们可以新建一个模块文件Dll.def,以使得其他语言编制的程序也能使用我们的动态链接库。
添加代码
LIBRARY Dll2
EXPORTS //即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的Add //字符串
Subtract
EXPORTS 语句引入了一个由一个或多个 definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS 关键字可以在第一个定义所在的同一行上或在前一行上。.def 文件可以包含一个或多个 EXPORTS 语句。
导出 definitions 的语法为:
entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]
entryname 是要导出的函数名或变量名。这是必选项。如果导出的名称与 DLL 中的名称不同,则通过 internalname 指定 DLL 中导出的名称。例如,如果 DLL 导出函数 func1(),要将它用作 func2(),则应指定:
EXPORTS
func2=func1
@ordinal 允许指定是序号而不是函数名将进入 DLL 的导出表。这有助于最小化 DLL 的大小。.LIB 文件将包含序号与函数之间的映射,这使您得以像通常在使用 DLL 的项目中那样使用函数名。
可选的 NONAME 关键字允许只按序号导出,并减小结果 DLL 中导出表的大小。但是,如果要在 DLL 上使用 GetProcAddress,则必须知道序号,因为名称将无效。
可选的 PRIVATE 关键字禁止将 entryname 放到由 LINK 生成的导入库中。它对同样是由 LINK 生成的图像中的导出无效。
可选的 DATA 关键字指定导出的是数据,而不是代码。例如,可以导出数据变量,如下所示:
EXPORTS
i DATA