目录
动态库文件在程序开发中运用很常见,但C和C++代码生成动态库文件,以及在使用时均存在一些差异,本文对两者的差异进行了讲解,并通过具体的实例加以说明。实例均在Windows系统下进行,Linux系统下的实现一样。
一、动态库生成
1)C语言生成动态库
我们直接新建一个记事本文件,在文件中输入如下代码。写一个简单的加法函数,函数返回相加的结果,然后将记事本文件保存为myC.c文件,名称随意,扩展名为c。
cpp
#include<stdio.h>
int add(int a, int b)
{
return a+b;
}
然后使用win+r键 ,输入"cmd"打开命令窗口,使用cd /d E:\Tem,将工作目录切换到myC.c文件存放的E:\Tem路径下。如下图所示。
然后在命令行中输入:"gcc -shared -o myC.dll myC.c" ,执行该命令就可以在当前路径下得到动态库文件myC.dll。
2)c++类生成动态库
C++是面向对象的编程语言,代码文件一般包含.h头文件和.cpp文件。此处使用记事本分别新建两个文件,命名为myclass.h和myclass.cpp。在文件中输入以下代码,实现用一个整数创建一个类,将整数赋给成员变量,并可改变和返回该成员变量的值。
cpp
//头文件
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#ifdef BUILD_MYDLL
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
class MYDLL_API MyClass {
private:
int value;
public:
MyClass(int val);
~MyClass();
int getValue();
void setValue(int val);
};
// 封装 C 风格的接口函数
extern "C" {
MYDLL_API MyClass* CreateMyClass(int val);
MYDLL_API int GetValue(MyClass* obj);
MYDLL_API void SetValue(MyClass* obj, int val);
MYDLL_API void DestroyMyClass(MyClass* obj);
}
#endif
cpp
//cpp文件
// myclass.cpp
#include "myclass.h"
MyClass::MyClass(int val) : value(val) {}
MyClass::~MyClass() {}
int MyClass::getValue() {
return value;
}
void MyClass::setValue(int val) {
value = val;
}
extern "C" {
MYDLL_API MyClass* CreateMyClass(int val) {
return new MyClass(val);
}
MYDLL_API int GetValue(MyClass* obj) {
return obj->getValue();
}
MYDLL_API void SetValue(MyClass* obj, int val) {
obj->setValue(val);
}
MYDLL_API void DestroyMyClass(MyClass* obj) {
delete obj;
}
}
在命令窗口中执行命令"g++ -shared -o myclass.dll -DBUILD_MYDLL myclass.cpp -Wl,--out-implib=libmyclass.a",可以得到编译完成后的dll文件。
-
-shared
:指定生成共享库(DLL)。 -
-o myclass.dll
:指定输出的 DLL 文件名。 -
-DBUILD_MYDLL
:定义宏,使MYDLL_API
被定义为__declspec(dllexport)
,用于导出函数和类。 -
-Wl,--out-implib=libmyclass.a
:生成导入库文件。
二、动态库调用
1)Python调用DLL
ctypes
是 Python 标准库中的一个外部函数库,它提供了与 C 语言兼容的数据类型,允许调用动态链接库(DLL)中的函数。在python中具体实现代码如下,程序运行时需要将生成的myC.dll动态库复制到当前python程序文件所在路径下,否则需要在下面文件路径中使用绝对路径。
python
import ctypes
# 加载 myC.dll
dll = ctypes.CDLL("./myC.dll")
dll.add.argtypes = [ctypes.c_int, ctypes.c_int] # 定义函数参数和返回值类型
dll.add.restype = ctypes.c_int
a = dll.add(2, 11)
print(a)
print('--------------------')
2)QT调用DLL
QT调用DLL有动态和静态两种方式。
(1)动态调用
动态调用需要使用到QT的QLibrary
类,此处以调用C++生成的动态库为例,具体代码如下。使用QLibrary
类时,只需要在代码中用QLibrary
类加载相应的DLL文件,并通过定义函数指针的方式取出需要使用的DLL函数。
在此例中,调用DLL中的函数时,首先需要使用DLL中的创建对象函数创建一个类的实例,由于在调用时不能知道DLL类的结构,因此采用QObject *来接收创建对象的指针。
python
#include <QCoreApplication>
#include <QLibrary>
#include <QDebug>
#include <QObject>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLibrary myLib("E:/Tem/useDLL/myclass.dll");
if (myLib.load()) {
// 定义函数指针类型
typedef QObject* (*creatFunction)(int);
creatFunction create = (creatFunction)myLib.resolve("CreateMyClass");
if (create) {
// 调用函数
QObject* obj = create(3);//创建对象
typedef int (*getFunction)(QObject*);
getFunction get = (getFunction)myLib.resolve("GetValue");
int val = get(obj);
qDebug() << "The get is:" << val;
typedef void (*setFunction)(QObject*,int);
setFunction set = (setFunction)myLib.resolve("SetValue");
set(obj,100);
val = get(obj);
qDebug() << "The set is:" << val;
typedef void (*desFunction)(QObject*);
desFunction des = (desFunction)myLib.resolve("DestroyMyClass");
des(obj);
qDebug() << "obj has been deleted.";
} else {
qDebug() << "Failed to resolve the function.";
}
}
return a.exec();
}
上述代码执行结果:
(2)静态调用DLL
当使用静态调用方法时,需要将生成的DLL动态库和对应的.h头文件拷贝到工程目录下。然后在工程的.pro文件中添加代码"LIBS += -L../ -lmyclass",在.cpp文件中包含DLL的头文件,然后就可以正常使用DLL了。具体实现代码如下。
cpp
#include <QCoreApplication>
#include "myclass.h" //包含DDL的头文件
//#include <QLibrary>
#include <QDebug>
#include <QObject>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyClass my(3);
qDebug()<<my.getValue();
my.setValue(5);
qDebug()<<my.getValue();
return a.exec();
}
上述程序输出结果为:
三、存在的一些问题
1)python调用封装了类的DLL可能调用不成功
从DLL的生成可知,c和c++代码生成DLL在代码实现上是不一样的,c++代码需要使用到 extern "C"和"__declspec(dllexport)"。在使用python调用封装了c++类的动态库时,会出现ctypes.CDLL()加载动态库文件不成功的情况,经过测试发现是因为在DLL的函数中使用了new关键字实例化对象造成的,但是具体的原因不清楚。若改用直接申明对象的方式,将对象的指针返回,但是在python中传递该指针后调用函数时会存在访问非法,或访问不到类中变量问题。而同样的DLL文件在QT中调用是完全没有问题的。
2)DLL格式不匹配的问题
在调用DLL时可能存在格式不匹配的问题,这个可能是因为使用的gcc编译器版本不同,或者32位与64位不兼容的原因。应确保DLL库的编译工具与调用它的程序使用相同的gcc,避免兼容性问题。一些比较常用的64位gcc版本可以在这里下载。
四、总结
DLL动态库方便程序模块化开发,但是在进行跨语言调用时,最好只在DLL中封装相应的实现函数,而不要跨语言调用类的实现,避免一些不可预测的问题。