链接库动态链接库详细介绍
作者:子幽 | 来源:互联网 | 2021-08-16 07:08
静态链接库.lib和动态链接库.dll。其中动态链接库在被使用的时候,通常还提供一个.lib,称为引入库,它主要提供被Dll导出的函数和符号名称,使得链接的时候能够找到dll中对应的函数映射
windows中,链接库分为两种类型:静态链接库.lib和动态链接库.dll。其中动态链接库在被使用的时候,通常还提供一个.lib,称为引入库,它主要提供被Dll导出的函数和符号名称,使得链接的时候能够找到dll中对应的函数映射。
静态链接库和动态链接库的作用相似,都是提供给其他程序进行调用的资源。其中,动态链接库的调用方法分隐式调用(静态导入调用)和显示调用(动态导入调用)。
编译环境:
Microsoft Visual Stdio 2010
--------------------------------------------------------------------------------
DLL导出符号
例,首先生成一个dll1.dll和dll1.lib
代码如下:
// DLL1工程,dll1.cpp
// _declspec(dllexport)为导出符号
_declspec(dllexport) int add(int a, int b)
{
return a + b;
}
利用微软的depends工具查看dll1.dll,导出的符号如下:
代码如下:
// DllTest工程,DllTest.cpp
#include
using namespace std;
int main(void)
{
// extern int add(int a, int b);
// _declspec(dllimport)是导入声明,这种方式比上面的方式更有效,同时编译器能边编译出更加高效的代码。
_declspec(dllimport) int add(int a, int b);
cout <getchar();
return 0;
}
编译链接,提示链接错误 error LNK2019: unresolved external symbol "__declspec(dllimport) int cdecl add(int,int)" (__imp_?add@@YAHHH@Z) referenced in function _main,很明显的编译器在编译的时候,把add函数也给重命名了,并且和上面用depends查看的一样。意思是没有找到这个符号的定义。
添加代码后如下:(注意,我这里两个工程的输出目录都是在和解决方案同目录的debug下,为了避免每次修改都重新拷贝lib文件,直接使用相对路径声明。)
代码如下:
// DllTest工程,DllTest.cpp
#include
using namespace std;
#pragma comment(lib, "../debug/dll1.lib") // 显示的声明要链接dll1.lib,隐式调用
int main(void)
{
// extern int add(int a, int b);
// _declspec(dllimport)是导入声明,这种方式比上面的方式更有效,同时编译器能边编译出更加高效的代码。
_declspec(dllimport) int add(int a, int b);
cout <getchar();
return 0;
}
编译运行后,使用depends工具对DllTest.exe查看其依赖的输入信息如下:
代码如下:
// DLL1工程,dll1.h
#ifndef DLL1_API
#define DLL1_API _declspec(dllimport)
#endif
// 以上代码表示,如果在包含该头文件之前,没有定义DLL1_API宏,那么后面所有DLL1_API宏都展开为_declspec(dllimport),即导入。
// 因为通常情况下客户端不会去定义这个宏(当然,假设这个宏不会被客户端中其他文件定义),所以客户端使用该头文件的时候,都是用于导入。
DLL1_API int add(int a, int b);
代码如下:
// DLL1工程,dll1.cpp
#define DLL1_API _declspec(dllexport)
// 注意上面这行,在头文件被包含前,先定义了DLL1_API这个宏,使得头文件中DLL1_API都被展开为_declspec(dllexport)了,从而声明函数作为导出。
#include "dll1.h"
// 在头文件中进行了导出声明的函数,就不用再声明导出了。
int add(int a, int b)
{
return a + b;
}
相应的,TestDll工程中包含.h文件后,也不用再去申明了。
代码如下:
// DllTest工程,DllTest.cpp
#include
using namespace std;
#include "../dll1/dll1.h" // 包含该头文件之后,后面就不需要再申明了
#pragma comment(lib, "../debug/dll1.lib") // 显示的声明要链接dll1.lib,隐式调用
int main(void)
{
cout <getchar();
return 0;
}
以上基本解释了为什么通常引用dll的时候都有一个头文件,并且头文件内有很多#ifndef之类的东东了。
--------------------------------------------------------------------------------
动态链接库导出类 当然,动态链接库也能导出类,要注意的是声明的方式为class DLL1_API CSample,而不是DLL1_API class CSample。
同时,要注意导出类的同时,其所有成员函数也已经导出,但是仍然遵循类成员变量访问权限限制。
如果单独导出类的成员函数(声明方式和全局函数一样),那么在客户端可以实例化类对象,并调用导出的成员函数,不能调用没导出的成员函数(即使是public的)。
--------------------------------------------------------------------------------
改编了的符号名 在导出符号时,讲过C++会对函数名进行改编,以支持函数重载。那么就会存在一个问题,如果使用不用的C++编译器(导致编译出的符号名不同)或者客户端使用C编译器调用,就会出现LNK2019这样的链接错误,找不到符号。这个问题很大的限制了DLL的使用范围。
解决方法1: 使用extern “C”(注意这个C一定要大写)前置申明,表明函数是以C的方式编译链接的。C方式编译连接导出的函数不会改编符号名,因而可以避免上述问题。
代码如下:
// DLL1工程,dll1.h
#ifndef DLL1_API
#define DLL1_API extern "C" _declspec(dllimport)
#endif
// 以上代码表示,如果在包含该头文件之前,没有定义DLL1_API宏,那么后面所有DLL1_API宏都展开为_declspec(dllimport),即导入。
// 因为通常情况下客户端不会去定义这个宏(当然,假设这个宏不会被客户端中其他文件定义),所以客户端使用该头文件的时候,都是用于导入。
DLL1_API int add(int a, int b);
//class CSample
//{
//public:
// DLL1_API int substract(int a,int b);// 这种情况下,导出类成员函数,编译不能通过的
//};
这里要注意.h和.cpp中都要加上extern “C”,使得导入和导出都用C编译方式。
代码如下:
// DLL1工程,dll1.cpp
#define DLL1_API extern "C" _declspec(dllexport)
// 注意上面这行,在头文件被包含前,先定义了DLL1_API这个宏,使得头文件中DLL1_API都被展开为_declspec(dllexport)了,从而声明函数作为导出。
#include "dll1.h"
// 在头文件中进行了导出声明的函数,就不用再声明导出了。
int add(int a, int b)
{
return a + b;
}
//
//int CSample::substract(int a,int b)
//{
// return a - b;
//}
然后用depends进行查看:
代码如下: