理解动态库与静态库区别
静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。
静态链接库是什么?
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
将自己设计的类导出为二进制形式的可执行代码。静态链接库有两种形式
- MSVC编译器生成的文件后缀为 “.lib”
- MinGW编译器生成的文件后缀为 “.a”
动态链接库是什么?
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
总结
从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。
在Qt中生成和调用静态库
在QtCreator中按照如下步骤创建静态库,静态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。
选择静态链接库,用来创建静态库。
创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。
//mylib.h
#ifndef MYLIB_H
#define MYLIB_H
#include <QMessageBox>
class MyLib
{
public:
MyLib();
};
#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"
MyLib::MyLib()
{
QMessageBox::information(NULL , "Title" , "Hello World");
}
将该项目进行编译,生成两个文件:libMyLib.a和mylib.o,我们真正要用到的只需要libMyLib.a以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.a放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的静态链接库:
//UseLib.pro
LIBS += \
$$PWD/libMyLib.a
接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该静态链接库里的MyLib对象啦。
项目结构如下:
在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:
MyLib *lib = new MyLib();
编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们静态库MyLib中的构造函数中的弹窗~调用静态库成功!
在Qt中生成和调用动态库
在QtCreator中按照如下步骤创建动态库,动态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。
选择共享库,用来创建动态库。
创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,一个mylib_global.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。
//mylib.h
#ifndef MYLIB_H
#define MYLIB_H
#include <QMessageBox>
class MyLib
{
public:
MyLib();
};
#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"
MyLib::MyLib()
{
QMessageBox::information(NULL , "dll title" , "Hello World");
}
将该项目进行编译,生成三个文件:libMyLib.a、MyLib.dll和mylib.o,我们真正要用到的只需要MyLib.dll以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.dll放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的动态链接库:
//UseLib.pro
LIBS += -L$$PWD -lMyLib
接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该动态链接库里的MyLib对象啦。
项目结构如下:
在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:
MyLib *lib = new MyLib();
编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们动态库MyLib中的构造函数中的弹窗~调用动态库成功!
需要注意的是,如果要将应用程序发布,需要将动态链接库跟随主程序一起发布。
将dylib库嵌入MacOS应用的方法
当你的应用程序使用了第三方的动态库,或自己开发的动态库的时候,使用macdeployqt则会报错:
ERROR: no file at "/usr/lib/libXXXX.1.dylib"
用otool -L untitled.app/Contents/MacOS/untitled 可以看到输出中包含如下这一行。
libXXXX.1.dylib (compatibility version 1.0.0, current version 1.0.1)
这一行表示你的应用程序找这个动态库是相对路径的,即要求你的这个动态库在/usr/lib目录下或/usr/local/lib目录下。你双击编译出的用用程序提示无法打开,点击报告会显示为找不到库。其实是在/usr/lib目录下或/usr/local/lib目录下 找不到这个库。你手工放置库文件到这个目录即可双击运行。注意:你没有权限把库放到/usr/lib下,因此你放到/usr/local/lib即可。
注意在你动态库的xxx.pro文件中加入如下的配置。否则,双击应用程序的时候会到/usr/lib找,而不是在/usr/local/lib找。
unix {
target.path = /usr/local/lib
INSTALLS += target
}
为了发布出去的应用程序不再在/usr/local/lib目录下找对应的动态库。而是在bundle包(目录)中查找。从而用户复制你的bundle到“应用程序”目录即可直接运行。因此你需要修改应用程序记录动态库的路径。修改方法如下:
install_name_tool -change "libXXXX.1.dylib" "@rpath/xxxx/libXXXX.1.dylib" untitled.app/Contents/MacOS/untitled
命令表示,把bundle包里面的应用程序untitled储存的此库的路径从”libXXXX.1.dylib”改为”@rpath/xxxx/libXXXX.1.dylib”。
执行完此命令后,找到untitled这个编译好的程序右键“打开包内容/Show Package Contents”,然后跳转到bundle的包内部目录里面,切换到“Contents”目录下的Frameworks目录中,然后创建一个目录“xxxx”(自己起的名字)然后把你制作的动态库或第三方的动态库放到这个目录。保证库的名字和”@rpath/xxxx/libXXXX.1.dylib”写的库的名字对应上。
以上就都做好了,现在用otool工具检测一下应用程序untitled包含库的路径:
otool -L untitled.app/Contents/MacOS/untitled
就会变成以@rpath开头的相对路径了。这次你双击“untitled”程序,程序就不会报错说找不到库了。
然后执行以下命令打包为dmg安装包。
macdeployqt ./build-untitled-Desktop_Qt_5_12_3_clang_64bit-Release/untitled.app -dmg
使用到的工具介绍
- macdeployqt:qt的提供的工具,可以把应用程序依赖的动态库,查找出来并放到一个文件夹中。
- macdeployqt -dmg:在macdeployqt基础上,再打包生成dmg安装包
- otool -L:查看一个动态库或应用程序的依赖
- install_name_tool -change: 改变应用程序或库的依赖库路径。