1、链接库概念
静态链接库和动态链接库介绍
我们可以创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从文件中查找符合要求的函数和变量进行链接,整个查找过程根本不需要我们操心。
这个文件叫做 “库(Libary)”,平时我们把编译好的目标代码存储到“库”里面,要用的时候链接程序帮我们从库里面找出来。
静态链接库:
在早期库的组织形式相对简单,里面的目标代码只能够进行静态链接,所以我们称为“静态库”,静态库的结构比较简单,其实就是把原来的目标代码放在一起,链接程序根据每一份目标代码的符号表查找相应的符号(函数和变量的名字),找到的话就把该函数里面需要定位的进行定位,然后将整块函数代码放进可执行文件里,若是找不到需要的函数就报错退出。
静态库的两个特点:
1、链接后产生的可执行文件包含了所有需要调用的函数的代码,因此占用磁盘空间较大。
2、如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。
动态链接库:
动态链接库就是为了解决这些问题而诞生的技术,顾名思义,动态链接的意思就是在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码。
动态库的代码必须满足这样一种条件:能够被加载到不同进程的不同地址,所以代码要经过特别的编译处理,我们把这种经过特别处理的代码叫做“位置无关代码(Position independed Code .PIC)”.
根据载入程序何时确定动态代码的逻辑地址,可以把动态装载分为两类。
1、静态绑定(static binding)
使用静态绑定的程序一开始载入内存的时候,载入程序就会把程序所有调用到的动态代码的地址算出确定下来,这种方式使程序刚运行的初始化时间较长,不过旦完成动态装载,程序的运行速度就很快。
2、动态绑定(dynamic binding)
使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态绑定的程序。
平时默认进行链接的标准 C/C++ 函数就是动态库。
最近看了不少Qt的DLL例子,总结一下如何创建和调用QT 动态链接库。
先讲一下对QT动态链接库的调用方法,主要包括:
1、显式链接DLL,调用DLL的全局函数,采用Qt的QLibrary方法
2、显示链接DLL,调用DLL中类对象、成员函数。(通过对象即可实现类成员函数的调用)
①用虚函数表的方法,这也是COM使用的方法,利用Qt的QLibrary技术调用;
②用GetProcAddress直接调用。
③用Qt的QPluginLoader类直接调用生成的DLL插件类对象
3、隐式链接DLL:也是采用Qt的Qlibrary方法
关于这种三种方法,下面详细叙说
详细分类叙述
前提:两个项目文件目录
1、TestDLL项目:testdll_global.h,TestDll.h,TestDll.cpp
2、TestMain exe应用项目:main.cpp
testdll_global.h 文件源代码一直不变
#ifndef TESTDLL_GLOBAL_H
#define TESTDLL_GLOBAL_H
#include <QtCore/qglobal.h>
#ifdef TESTDLL_LIB
# define TESTDLL_EXPORT Q_DECL_EXPORT
#else
# define TESTDLL_EXPORT Q_DECL_IMPORT
#endif
#endif // TESTDLL_GLOBAL_H
DLL的显式链接在某些时候比隐式链接具有更大的灵活性。比如,如果在运行时发现DLL无法找到,程序可以显示一个错误信息并能继续运行。当你想为你的程序提供插件服务时,显式链接也很有用处
1、采用显示链接,调用DLL中全局函数,只需要一个TestDLL.dll。
通常Windows下程序显示调用dll的步骤分为三步(三个函数):LoadLibrary()、GetProcAdress()、FreeLibrary()
其中,LoadLibrary() 函数用来载入指定的dll文件,加载到调用程序的内存中(DLL没有自己的内存!)
GetProcAddress() 函数检索指定的动态链接库(DLL)中的输出库函数地址,以备调用
FreeLibrary() 释放dll所占空间
而QT的QLibrary类显示链接调用DLL的步骤:load()、resolve(const char * symbol )、unload()和VC步骤类似
TestDll.dll项目中的TestDLL.h源码
#ifndef TESTDLL_H
#define TESTDLL_H
#include "testdll_global.h"
class TESTDLL_EXPORT TestDll
{
public:
TestDll();
~TestDll();
private:
};
extern "C" TESTDLL_EXPORT void helloWorld();
extern "C" TESTDLL_EXPORT int add(int a,int b);
#endif // TESTDLL_H
TestDll.dll项目中的TestDLL.cpp源码
#include <iostream>
#include "TestDll.h"
TestDll::TestDll()
{
}
TestDll::~TestDll()
{
}
void helloWorld()
{
std::cout << "hello,world!";
}
int add(int a,int b)
{
return a + b;
}
注:2)必须使用extern "C"链接标记,否则C++编译器会产生一个修饰过的函数名,这样导出函数的名字将不再是helloworld,而是一个形如" ?helloWorld@TestDll@@UAEXXZ”的名字。为什么名字不是helloworld呢?这是因为C++为了支持函数的重载,会在编译时将函数的参数类型信息以及返回值类型信息加入到函数名中,这样代码中名字一样的重载函数,在经过编译后就互相区分开了,调用时函数名也经过同样的处理,就能找到对应的函数了。详细可以看这篇文章 动态链接库(Dynamic Link Library)学习笔记
TestMain项目 main.cpp
#include <QtCore/QCoreApplication>
#include <iostream>
#include <QLibrary>
typedef int (*Fun)(int,int); //定义函数指针,int add(int a,int b);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLibrary mylib("TestDll.dll"); //声明所用到的dll文件
int result;
//判断是否正确加载
if (mylib.load())
{
std::cout << "DLL load is OK!"<<std::endl;
//调用外部函数 add()
Fun add = (Fun)mylib.resolve("add");
//是否成功连接上 add() 函数
if (add)
{
std::cout << "Link to add Function is OK!"<<std::endl;
//这里函数指针调用dll中的 add() 函数
result = add(5,6);
std::cout << result;
}
else
std::cout << "Link to add Function failed!!"<<std::endl;
}
//加载失败
else
std::cout << "DLL is not loaded!"<<std::endl;
return a.exec();
}
2、采用显示链接,调用C++类中的类对象、成员函数
如果你想导出并显式链接一组C++类中的成员函数又该怎么办呢?这里有两个问题。第一是C++成员函数名是经过修饰的(即使指定extern "C"标记也是这样);第二是C++不允许将指向成员函数的指针转换成其它类型。这两个问题限制了C++类的显式链接。下面介绍两种方法来解决这个问题:
①用虚函数表的方法,这也是COM使用的方法,利用Qt的QLibrary技术调用;
②用GetProcAddress直接调用。
③用Qt的QPluginLoader类直接调用生成的DLL插件类对象
①虚函数表的方法,QLibrary 技术调用
TestDll.h代码
#ifndef TESTDLL_H
#define TESTDLL_H
#include "testdll_global.h"
class TESTDLL_EXPORT TestDll
{
public:
TestDll();
virtual~TestDll();
virtual void helloWorld(); //类成员函数
private:
};
extern "C" TESTDLL_EXPORT TestDll* getTestDll(); //获取类TestDll的对象
#endif // TESTDLL_H
TestDll.cpp源码
#include <iostream>
#include "TestDll.h"
TestDll::TestDll()
{
}
TestDll::~TestDll()
{
}
void TestDll::helloWorld()
{
std::cout << "hello,world!";
}
TestDll* getTestDll()
{
return new TestDll();
}
TestMain项目中的main.cpp源码
#include <QtCore/QCoreApplication>
#include <iostream>
#include <QLibrary>
#include "../TestDll/TestDll.h" //头文件还是需要加的,否则无法解析TestDll类
typedef TestDll* (*GetTestDll)();//定义函数指针,获取类TestDLL对象;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLibrary mylib("TestDll.dll"); //声明所用到的dll文件
int result;
//判断是否正确加载
if (mylib.load())
{
GetTestDll getTestDll = (GetTestDll)mylib.resolve("getTestDll");
if(getTestDll)
{
TestDll *testDll = getTestDll();
testDll->helloWorld();
delete testDll;
}
}
//加载失败
else
std::cout << "DLL is not loaded!"<<std::endl;
return a.exec();
}
这个方法的使用得用户可以很容易地为你的程序制作插件。它的缺点是创建对象的内存必须在dll中分配
②用GetProcAddress直接调用类对象中的成员函数
这个方法,我没测试,对我没对大作用,还得用def导出DLL函数,有兴趣的就参考一下这篇文章。DLL中类的显式链接
③用Qt的QPluginLoader类直接调用生成的DLL插件类对象
这个方法,我单独写一篇总结,请看QPluginLoader的简单小例子VS2008+Qt 使用QPluginLoader访问DLL
3、采用隐式链接方法,通过QLibrary类对DLL中类对象、全局函数的调用
TestDll.h
#ifndef TESTDLL_H
#define TESTDLL_H
#include "testdll_global.h"
class TESTDLL_EXPORT TestDll
{
public:
TestDll();
~TestDll();
void helloWorld(); //类成员函数
private:
};
extern "C" TESTDLL_EXPORT int add(int a,int b); //自定义的外部函数
#endif // TESTDLL_H
TestDll.cpp源码
#include <iostream>
#include "TestDll.h"
TestDll::TestDll()
{
}
TestDll::~TestDll()
{
}
void TestDll::helloWorld()
{
std::cout << "hello,world!";
}
int add(int a,int b)
{
return a + b;
}
TestMain项目中的main.cpp ,需要稍微配置头文件和lib文件
1、在项目中主程序引入TestDll.h头文件,
2、配置项目属性:加入TestDLL.lib的文件目录,在Linker/General/Additional Library Diretories里面选择TestDll.lib的文件目录D:\VSWorkSpace\Test\Debug
3、配置项目属性:加入TestDll.lib文件,在Linker/Input/Additional Dependencies 中加入 TestDll.lib
main.cpp源码
#include <QtCore/QCoreApplication>
#include <iostream>
#include <QLibrary>
#include "../TestDll/TestDll.h"
//引入TestDll.lib文件,和上面的2,3步工作同理
//#pragma comment(lib, "../Debug/TestDll.lib")
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int result = add(5,6);
std::cout << result;
TestDll dll;
dll.helloWorld();
return a.exec();
}
开发环境:VS2008、Qt4.7.4,附件有源码可供下载
最近在总结如何访问DLL中的类对象及其成员函数,其中一种方法利用Qt的QPluginLoader类就可以方便快捷的访问DLL中的类,以及其中的类成员函数。
文件结构如下图:
解决方案名:TestPlugin
1、Qt的Library项目(PluginDll):PlugInterface.h、PluginInstance.h、PluginInstance.cpp
2、Qt的Console Application项目(PluginConsole):main.cpp、PlugInterface.h(从上面的项目拷贝过来的)
程序建立步骤
步骤一、在VS里新建立一个DLL项目,名称PluginDll。
- 把自动生成的三个代码文件(PluginDll.h、PluginDll_global.h、PluginDll.cpp)直接删掉,这三个是Qt自己的建立Qt Dll的推荐方法,我们暂时用不到。
- 自己新建如下三个文件PlugInterface.h、PluginInstance.h、PluginInstance.cpp
备注:如果步骤一建立成一个exe应用项目,也可以通过改两个项目配置参数实现,生成DLL。
一是修改Configuration Properties/General/Configuration type的值,将Application (.exe)改成Dynamic Library (.dll) ,
二是修改Configuration Properties/Linker/General/Output File 的值,将$(OutDir)\$(ProjectName).exe改成$(OutDir)\$(ProjectName).dll
// PlugInterface.h
//接口类
#ifndef PLUGINTERFACE_H
#define PLUGINTERFACE_H
#include <QtPlugin>
class PlugInterface
{
public:
virtual ~PlugInterface(){}
//接口中的成员函数必须是纯虚函数
virtual void testPlugin() = 0;
virtual void helloWorld() = 0;
};
QT_BEGIN_NAMESPACE
//这个宏用声明接口
//Q_DECLARE_INTERFACE(接口类名,接口标识符)
Q_DECLARE_INTERFACE(PlugInterface, "PlugInterfaceDll/1.0");
QT_END_NAMESPACE
#endif // PLUGINTERFACE_H
//接口标识符必须唯一,所以一般加个版本号1.
//This macro is normally used right after the class definition for ClassName, in a header file.
//If you want to use Q_DECLARE_INTERFACE with interface classes declared in a namespace,
//then you have to make sure the Q_DECLARE_INTERFACE is not inside a namespace though.
// PluginInstance.h
//业务类的头文件
#ifndef PLUGININSTANCE_H
#define PLUGININSTANCE_H
#include "PlugInterface.h"
#include <QObject>
//PluginInstance必须继承QObject类,才能使用如下Qt的方法
//PluginInstance还得继承接口PlugInterface
class PluginInstance : public QObject, PlugInterface
{
Q_OBJECT
//Q_INTERFACES宏定义告诉Qt MOC,PluginInstance类继承了插件PlugInterface
//将该接口注册到Qt的meta-object system
Q_INTERFACES(PlugInterface)
public:
PluginInstance();
~PluginInstance();
//业务成员1
void testPlugin();
//业务成员2
void helloWorld();
};
#endif // PLUGININSTANCE_H
// PluginInstance.cpp
//业务类成员函数定义
#include "PluginInstance.h"
#include <QtCore>
PluginInstance::PluginInstance()
{
}
PluginInstance::~PluginInstance()
{
}
void PluginInstance::testPlugin()
{
qDebug() << "test";
}
void PluginInstance::helloWorld()
{
qDebug() << "helloWorld";
}
//这个宏用来导出动态链接库
//Q_EXPORT_PLUGIN2(插件名, 类名)
//Q_EXPORT_PLUGIN2("PluginDll", PluginInstance)
Q_EXPORT_PLUGIN2("PluginInstance", PluginInstance)
//插件名最好和TARGET有关,而我们PluginDll项目转换成Qt项目的.pro文件,target = PluginDll
//所以说这里名字不影响编程,只是为了方便理解,让插件名和项目名一致最好。
//对于一个Qt插件来说,最好只有一个Q_EXPORT_PLUGIN2宏定义,并且最好在实现的文件里出现,而非头文件中。
步骤二、在VS里新建立一个ApplicationL项目,名称PluginConsole。
- 将前面PluginDll项目中的接口头文件PlugInterface.h拷贝到PluginConsole项目中;
- 因为plugin.dll已经在解决方案的Debug文件中了,暂时就不需要拷贝到PluginConsole项目中来了。如果没有需要拷贝过来才能引用
在main.cpp中通过QPluginLoader调用PluginDll.dll
#include <QtCore/QCoreApplication>
#include "PlugInterface.h"
#include <QPluginLoader>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
PlugInterface *plugObject;
//不知道为啥,路径我写成下面就出错呢?编译可以通过,运行时会提示异常
//QPluginLoader pluginLoader("PluginDll.dll");
//加载插件
QPluginLoader pluginLoader("../Debug/PluginDll.dll");
//
QObject *plugin = pluginLoader.instance();
if (plugin)
{
qDebug() << "instance success";
//使用qobject_cast将QObject类型转换成我们想要的PlugInterface类对象
plugObject= qobject_cast<PlugInterface *>(plugin);
plugObject->helloWorld();
plugObject->testPlugin();
}
else
qDebug() << "instance fail";
return a.exec();
}
综合了网上很多的例子,很难直接运行,最后自己总结出来的例子在附件上,可以直接运行。
那些参考链接,就不一一引用了,在此谢谢各位的帮助!