把编译好的包含函数和变量的目标代码存储到文件中,在链接的时候让链接程序自动从文件中查找需要的代码。这个文件就是链接库,又可以分为静态链接库和动态链接库。
1. 静态链接库
链接程序从库中寻找需要的符号(函数和变量的名字),查找到就将其放入可执行文件,未查找到就报错。
使用静态库链接的程序:
(1)可执行文件中包含所有需要调用的函数代码;
(2)如果多个进程调用相同的库函数,内存中会存在多份库函数代码。
静态库lib文件其实是打包好的obj文件。
2.动态链接库
动态链接库在程序装载内存的时候才真正地把库函数代码链接进行确定它们的地址,并且在内存中只有一份代码,可以被加载到不同进程的不同地址。
生成动态链接库时一般包含3种文件:
(1).h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。
(2).lib文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误。动态链接库的lib文件与静态库不同,只包含被DLL导出的函数名称和位置,没有具体的实现,所以也叫导入库。非必需,因为可以通过其他方式实现导入库的功能,如在显式连接中,可以使用WIN 32 API函数LoadLibrary导入DLL文件,再使用GetProcAddress函数获得对应函数;或者在Qt中,使用QLibrary类。
(3).dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。
2.1 隐式链接与显式链接
动态链接库的调用分为隐式链接和显式链接。
2.1.1 显式链接
显式链接在程序需要用到库函数的时候再进行动态载入,需要在程序中通过某些函数来加载库和获得要使用的函数,所以是“显式”。
Windows程序有相应的函数帮助实现显式链接,Qt也有相应的类来实现。
显式链接的特点:
灵活性好,如果导入库或获得函数失败,程序设计的时候就可以考虑进行相应的处理。程序载入速度快,在程序运行过程中,在需要时才加载dll编译时不需要导入库,也不需要include头文件,只需要提供dll文件,但是要知道其中的函数信息(参数类型,返回类型)以供获取函数时使用导入时需要额外的代码(加载库和函数)如果在编译程序时并不能确定库和函数的名字,而是在运行时获得,就只能通过显式链接动态加载因为C++类的成员函数的实际名字是修饰过的,所以调用时要使用特殊方法(目前没有研究过)通过显式链接调用库的程序,在编译时是不会去管库文件的正确性的,只有运行时才能发现错误。
2.1.2 隐式链接
相应的,隐式链接中调用库函数时,就像调用本工程的函数,所以被称为“隐式”。
它的使用需要include头文件,配置库文件的查找路径,当然dll文件也必不可少。
头文件告诉编译器,程序中使用到的外部库函数的信息。当调用可执行文件的源代码被编译或被汇编时,DLL 函数调用在对象代码中生成一个外部函数引用。
若要解析此外部引用,应用程序必须与 DLL 的创建者所提供的导入库(.LIB文件或.a文件)链接。
隐式链接特点:
应用程序执行开始就载入dll,如果有很多dll,启动会较慢使用时比显式链接更方便,就像用本程序中的函数,不需要特殊处理如果载入时出现错误,整个程序就无法运行确保头文件和导入库文件正确才能正确编译调用类中的成员函数比较方便
3 实例
3.1 dll链接库工程
在Qt中新建工程testDll,选Library -> C++ Library -> shared Library
编辑头文件和源文件,在类中实现一个helloWorld方法,在类外实现一个add的普通函数
头文件testdll.h,还有一个工程自动生成的testdll_global.h不需要修改,或者可以合入testdll.h
#ifndef TESTDLL_H#define TESTDLL_H#include "testdll_global.h"class TESTDLLSHARED_EXPORT TestDLL{public:TestDLL();~TestDLL();void helloWorld();};extern "C" TESTDLLSHARED_EXPORT int add(int a,int b);/*C++为了支持函数的重载,会在编译时将函数的参数类型信息以及返回值类型信息加入到函数名中,这样代码中名字一样的重载函数,在经过编译后就互相区分开了,调用时函数名也经过同样的处理,就能找到对应的函数了。所以要使用使用extern "C"链接标记,否则C++编译器会产生一个修饰过的函数名,这样导出函数的名字将不再是helloworld,而是一个形如" ?helloWorld@TestDll@@UAEXXZ”的名字。*/#endif // TESTDLL_H
源文件testdll.cpp
#include "testdll.h"#include <iostream>TestDLL::TestDLL(){}TestDLL::~TestDLL(){}void TestDLL::helloWorld(){std::cout << "hello world!"<<std::endl;}int add(int a, int b){return (a+b);}
build即可,不用点run,否则会在build完之后提示你找不到可执行文件(因为是库工程,不会生成可执行文件)。完成后在debug路径下会生成testDLL.dll,还有libtestDLL.a(使用MinGw)或者testDll.lib(使用MSVC编译器)。
3.2 显式链接
只讲最简单的
通常Windows下程序显示调用dll的步骤分为三步(三个函数):LoadLibrary()、GetProcAdress()、FreeLibrary(),其中:
LoadLibrary() 函数用来载入指定的dll文件,加载到调用程序的内存中(DLL没有自己的内存!)
GetProcAddress() 函数检索指定的动态链接库(DLL)中的输出库函数地址,以备调用
FreeLibrary() 释放dll所占空间
而QT的QLibrary类显示链接调用DLL的步骤:load()、resolve(const char * symbol )、unload()和VC步骤类似
代码如下
#include <QLibrary>#include<iostream>typedef int (*Fun)(int,int); //定义函数指针,int add(int a,int b);int main(){QLibrary mylib("testdll.dll"); //声明dll文件if (mylib.load()) //判断加载是否成功{std::cout << "DLL loaded!"<<std::endl;Fun add = (Fun)mylib.resolve("add"); //链接到add函数if (add){std::cout << "Link to add Function is OK!"<<std::endl;int result = add(5,6);std::cout << result <<std::endl;}}else{std::cout << "DLL is not loaded!"<<std::endl;}mylib.unload ();return 0;}
Windows将遵循下面的搜索顺序来定位DLL:
包含EXE文件的目录
进程的当前工作目录
Windows系统目录(system/system32)。GetSystemDirectory 函数检索此目录的路径。
Windows目录.GetWindowsDirectory 函数检索此目录的路径。
列在Path环境变量中的一系列目录
只需要把dll文件放在debug文件夹下(与exe放一起),或者Qt的程序,放在debug/release文件夹的上一级目录也可以。头文件/库文件都不需要。
运行结果:
3.3 隐式链接
源文件需要include testdll.h,这个头文件放在工程目录中,和源文件放一起即可。调用函数和正常调用没有区别。
#include <QCoreApplication>#include "testdll.h"#include <iostream>int main(){std::cout << "program begin" << std::endl;std::cout << add(5,6) <<std::endl;TestDLL testdll;testdll.helloWorld();}
设置库的路径,使程序编译时能够找到。需要修改.pro 文件,增加库的路径
LIBS += -LD:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/ -llibtestDLL
固定形式,路径前的-L要大写, 之后库文件名前的-l小写,库文件名即.a 或.lib的文件名。
dll文件的放置,很坑
隐式链接时,可以设置从导入库文件的路径去寻找dll文件,如下图所示,add build library search path to PATH,也就是把导入库文件的路径设置到Path变量中。
查看Path变量,会增加在LIBS中添加的D:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/
程序也就会从该路径寻找dll文件。
BUG!!! 新建的工程add build library search path to PATH是默认勾选的,但是实际并没有添加,退出Qt再进入才会生效啊,所以运行程序的时候会出现找不到dll文件的情况。
此外,也可以自己放置dll文件,把路径手动加入上图中的Path中。
运行结果与显式链接一致
4 可能出现的问题
4.1 隐式链接
(1)如果没有指定库文件路径,程序编译时找不到库文件,会提示undefined reference 的错误,表示找不到要调用函数的引用。
(2)
LIBS += -LD:/Study/code/Qt/build-testDLL-Desktop_Qt_5_11_3_MinGW_32bit-Debug/debug/ -llibtestDLL
指定的是导入库文件。如果把库文件删除掉或者路径不对,编译程序时会报错,no such file or directory MinGW生成的是.a文件,MSVC是.lib文件。
(3)如果程序中调用了dll文件中的内容,却没有找到dll文件,程序不会运行,直接退出,如下图所示:
(4)如果没有调用,即使dll不存在,也不影响程序的运行。注释掉调用add函数的语句,再编译程序并执行:
参考
LIB和DLL的区别与使用
lib和dll文件的区别和联系
Qt DLL总结【一】-链接库预备知识