上两篇我们讲到如何使用C、C++语言编写静态库程序以及在.c和.cpp环境下的调用方式,今天我们来说说动态库程序的编写
一、动态库特点
a.运行进独立存在
b.不会链接到执行程序
c.使用时加载
二、与静态库的比较:
a.由于静态库是将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码体积会增在,动态库的代码只需要存在一份,
,其他程序通过函数地址使用,所以代码体积小,(不过现在的硬盘那么大,还怕这点体积,哈哈)
b.静态库发生变化后,新的代码需要重新链接嵌入到执行程序中,动态库发生变化后,如果库中函数的定义(或地址)未变化,其它使用DLL的程序不需要重新链接。
三、我们来说说动态库的创建哈
a.建立项目
b.添加库程序(Win32 Dynamic-link Library)
c.库程序导出,-提供给使用者库中的函数信息等(如果函数的信息,地址没有导出,别人就没法使用,我们写的dll也就没用啦)
库函数的导出方式:(声明导出1)使用_declspec(dllexport)导出函数,注意:动态库编译链接后,也会有LIB文件,是作为动态库函数映射使用,与静态库不完全相同。
(声明导出2)C的导出方式:extern "C" _declspec(dllexport) int Add(...);
(模块定义文件.def)
例如:
LIBRARY DLLFUNC;库名
EXPORTS ;库导出表
CDll_add @1 ;导出函数
d.库函数的使用 --隐式链接,1) 头文件和函数原型,可以在函数原型的定义前,增加declspec(dllimport).例如_declspec(dllimport) int Add(...);
如果库函数使用C格式导出,需要在函数定义增加extern "C"。
导入动态库的LIB文件, 在程序中使用函数
隐式链接的情况,DLL可以存放的路径:
(1).与执行文件中同一个目录下
(2).当前工作目录
(3).Windows目录
(4).Windows/System32
(5).Windows/System
(6).环境变量PATH指定目录
使用C编写动态库C环境调用
cdll.c 源文件
_declspec(dllexport) int CDll_add(int num1,int num2)<span style="white-space:pre"> </span>//此外如果不导出的话,在调用处编译通过不了
{
return num1+num2;
}
_declspec(dllexport) int CDll_sub(int num1,int num2)
{
return num1-num2;
}
使用C环境调用cdll.dll
usecdll.c
//隐式链接使用
#pragma comment(lib,"..\\cdll\\cdll.lib")
int main(void)
{
int num1 = 100;
int num2 = 1000;
int nSum = CDll_add(num1,num2);
int nSub = CDll_sub(num1,num2);
printf("nSum = %d,nSub = %d\r\n",nSum,nSub);
return 0;
}
使用C++编写动态库CPP环境调用
cppdll.cpp源文件
/*_declspec(dllexport) */int CppDll_add(int num1,int num2)<span style="white-space:pre"> </span>//即可就以声明导出,也可以通过模块定义文件,上面我们用过声明导出,现在我们使用模块定义文件
{
return num1+num2;
}
/*_declspec(dllexport) */int CppDll_sub(int num1,int num2)
{
return num1-num2;
}
/*_declspec(dllexport) */int CppDll_mul(int num1,int num2)
{
return num1*num2;
}
cppdll.def
LIBRARY cppdll
EXPORTS
CppDll_add @1
CppDll_sub @2
CppDll_mul @3
#include <iostream>
using namespace std;
_declspec(dllimport) int CppDll_add(int num1,int num2);<span style="white-space:pre"> </span>//以声明导入在动态库中导出函数
_declspec(dllimport) int CppDll_sub(int num1,int num2); //同上
_declspec(dllimport) int CppDll_mul(int num1,int num2);<span style="white-space:pre"> </span>//同上
#pragma comment(lib,"..\\cppdll\\cppdll.lib")
int main(void)
{
int num1 = 100;
int num2 = 1000;
cout<<"C++ Dll function use"<<endl;
int nSum = CppDll_add(num1,num2);
int nSub = CppDll_sub(num1,num2);
int nMul = CppDll_mul(num2,num1);
cout<<"nSum = "<<nSum<<" nSub = "<<nSub<<" nMul = "<<nMul<<endl;
return 0;
}
以上两种方式调用动态库我们均采用的隐式链接,接下来我们来采用显示链接的调用方法
显示链接动态库程序(显示调用时,dll库程序必须要使用模块定义文件导出)
1)定义函数指针类型
2)加载动态库
HMODULE LoadLibrary(
LPCTSTR lpFileName //动态库文件名或都全路径
); 返回DLL的实例句柄(HINSTANCE)
3)获取函数地址
FARPROC GetProcAddress(
HMODULE hModule, //DLL句柄
LPCSTR lpProcName//函数名称
);成功返回函数地址
4)使用函数,拿到了函数地址就可以使用啦哈哈
5)卸载动态库,使用记得释放自己加载的资源哈
BOOL FreeLibrary(
HMODULE hModule //DLL的实例句柄
);
看例子代码示例哈:
#include <cstdio>
#include <WINDOWS.H>
typedef int (*DLL_ADD)(int num1,int num2);
typedef int (*DLL_SUB)(int num1,int num2);
typedef int (*DLL_MUL)(int num1,int num2);
int main(void)
{
HINSTANCE hDll = LoadLibrary("cppdll.dll");
printf("hDll: %d\r\n",hDll);
DLL_ADD lpAddFun = (DLL_ADD)GetProcAddress(hDll,"CppDll_add");
printf("lpAddFun: %p\r\n",lpAddFun);
int nSum = lpAddFun(100,200);
printf("nSum = %d\r\n",nSum);
DLL_SUB lpSubFun = (DLL_SUB)GetProcAddress(hDll,"CppDll_sub");
printf("lpSubFun: %p\r\n",lpSubFun);
int nSub = lpSubFun(100,200);
printf("nSub = %d\r\n",nSub);
DLL_MUL lpMulFun = (DLL_MUL)GetProcAddress(hDll,"CppDll_mul");
printf("lpMulFun: %p\r\n",lpMulFun);
int nMul = lpMulFun(100,200);
printf("nMul = %d\r\n",nMul);
FreeLibrary(hDll);
return 0;
}
总结:两种链接方式对比:
1)在库函数定义不变的情况下:
隐式链接,由于库函数地址是在程序编译链接时设置,所以当动态库变化后,使用程序需要重新编译链接。
显示链接,由于库函数地址是在程序执行时,动态从中查询,所以库变化后,不需要重新编译链接。
2)动态库加载
隐式链接,动态库是在程序启动时就被加载,当DLL不存在,程序无法启动。
显式链接,动态库只在使用LoadLibrary函数时,才会被加载。