C++ QT 如何生成dll提供給python使用
我通常在開發的過程中不會考慮跨語言調用的問題,最近遇到了:需要在通過python調用C++ QT生成的dll;
以下是基於QT Creator 的一个生成 device.dll
(供 C++ 使用)和 testlib.dll
(通过 C 接口供 Python 调用)項目的詳細方案:
一、项目结构设计
建议使用 subdirs
模板管理多个子项目:
MyProject/
├── MyProject.pro # 主项目文件(subdirs 模板)
├── device/ # device.dll 的 C++ 实现
│ ├── device.pro
│ ├── device.h
│ └── device.cpp
└── testlib/ # testlib.dll 的 C 接口包装
├── testlib.pro
├── testlib.h
└── testlib.cpp
二、主项目配置 (MyProject.pro
)
TEMPLATE = subdirs
SUBDIRS += device testlib # 定义子项目顺序
CONFIG += ordered # 确保构建顺序
三、device.dll
的配置 (device/device.pro
)
1. 项目文件
TEMPLATE = lib # 生成动态库
TARGET = device # 输出名为 device.dll
CONFIG += shared # 强制生成动态库
DEFINES += DEVICE_LIB # 定义导出宏
# 输出目录
DESTDIR = $$PWD/../../bin # 假设输出到项目根目录的 bin 文件夹
!exists($$DESTDIR): QMAKE_MKDIR += $$DESTDIR
# 源文件
SOURCES += device.cpp
HEADERS += device.h
2. 头文件 device.h
(符号导出)
#ifndef DEVICE_H
#define DEVICE_H
#include <QtCore/QtGlobal>
#ifdef DEVICE_LIB
#define DEVICE_EXPORT Q_DECL_EXPORT
#else
#define DEVICE_EXPORT Q_DECL_IMPORT
#endif
class DEVICE_EXPORT Device {
public:
Device();
int getValue() const;
void setValue(int value);
private:
int m_value;
};
#endif // DEVICE_H
3. 实现文件 device.cpp
#include "device.h"
Device::Device() : m_value(0) {}
int Device::getValue() const {
return m_value;
}
void Device::setValue(int value) {
m_value = value;
}
四、testlib.dll
的配置 (testlib/testlib.pro
)
1. 项目文件
TEMPLATE = lib # 生成动态库
TARGET = testlib # 输出名为 testlib.dll
CONFIG += shared # 强制生成动态库
DEFINES += TESTLIB_EXPORT # 定义导出宏
# 依赖 device 项目
LIBS += -L$$PWD/../../bin -ldevice # 链接 device.dll 的导入库(device.lib)
INCLUDEPATH += $$PWD/../device # 包含 device 头文件
# 输出目录
DESTDIR = $$PWD/../../bin
!exists($$DESTDIR): QMAKE_MKDIR += $$DESTDIR
# 源文件
SOURCES += testlib.cpp
HEADERS += testlib.h
2. 头文件 testlib.h
(C 接口导出)
#ifndef TESTLIB_H
#define TESTLIB_H
#ifdef _WIN32
#ifdef TESTLIB_EXPORT
#define TESTLIB_API __declspec(dllexport)
#else
#define TESTLIB_API __declspec(dllimport)
#endif
#else
#define TESTLIB_API
#endif
#ifdef __cplusplus
extern "C" { // 确保 C 语言兼容性
#endif
// 定义 C 接口函数
TESTLIB_API void* create_device();
TESTLIB_API void destroy_device(void* obj);
TESTLIB_API int get_device_value(void* obj);
TESTLIB_API void set_device_value(void* obj, int value);
#ifdef __cplusplus
}
#endif
#endif // TESTLIB_H
3. 实现文件 testlib.cpp
#include "testlib.h"
#include "../device/device.h"
// 创建 Device 对象
TESTLIB_API void* create_device() {
return new Device();
}
// 销毁 Device 对象
TESTLIB_API void destroy_device(void* obj) {
delete static_cast<Device*>(obj);
}
// 获取值
TESTLIB_API int get_device_value(void* obj) {
return static_cast<Device*>(obj)->getValue();
}
// 设置值
TESTLIB_API void set_device_value(void* obj, int value) {
static_cast<Device*>(obj)->setValue(value);
}
五、Python 调用 testlib.dll
的示例
1. Python 代码 (test.py
)
import ctypes
# 加载 DLL
testlib = ctypes.WinDLL("./bin/testlib.dll", winmode=0x8)
# 定义函数原型
testlib.create_device.restype = ctypes.c_void_p
testlib.destroy_device.argtypes = [ctypes.c_void_p]
testlib.get_device_value.argtypes = [ctypes.c_void_p]
testlib.get_device_value.restype = ctypes.c_int
testlib.set_device_value.argtypes = [ctypes.c_void_p, ctypes.c_int]
# 使用 C 接口
device = testlib.create_device()
testlib.set_device_value(device, 42)
print("Value:", testlib.get_device_value(device)) # 输出: 42
testlib.destroy_device(device)
六、关键注意事项
-
符号导出:
- C++ 类需要
Q_DECL_EXPORT/Q_DECL_IMPORT
或__declspec(dllexport/dllimport)
。 - C 接口函数必须用
extern "C"
包裹,避免名称修饰(Name Mangling)。
- C++ 类需要
-
构建顺序:
- 确保
device.dll
先于testlib.dll
构建,因为后者依赖前者。
- 确保
-
运行时依赖:
- 将
device.dll
和testlib.dll
放在同一目录(如bin
),或添加到系统PATH
。
- 将
-
编译器兼容性:
- 若使用 MinGW,生成的导入库为
.dll.a
(需通过-L
和-l
链接)。 - 若使用 MSVC,生成的导入库为
.lib
。
- 若使用 MinGW,生成的导入库为
七、完整项目树和输出
构建完成后,目录结构如下:
MyProject/
├── bin/
│ ├── device.dll
│ ├── device.lib (MSVC) 或 libdevice.dll.a (MinGW)
│ ├── testlib.dll
│ └── testlib.lib (MSVC) 或 libtestlib.dll.a (MinGW)
├── device/
│ └── ... # 源码
└── testlib/
└── ... # 源码
通过以上配置,你可以实现:
device.dll
供 C++ 项目使用。testlib.dll
通过 C 接口暴露功能,供 Python 调用。
八、注意事項
我嘗試在QT 6.3.1 + python 3.12.7版本實現了以上的功能,需要注意的是以下几点:
- 使用的dll和他的依賴項相對位置
- testlib = ctypes.WinDLL(“./bin/testlib.dll”, winmode=0x8)可以使得testlib.dll在對應的位置和路徑去搜索依賴dll,這樣對於load有幫助
- 我曾經嘗試過pybind11 + 生成C++庫的方式,但是QT和python都包含了對slots的定義,這個導致編譯無法通過
- python運行環境和編譯器需要注意,我在VS Code的環境能夠運行的代碼,在spyder上並無法運行,即使使用了同一個環境和解釋器
- 這種做法的弊端
- 這種做法需要將C++的函數或者類包裝為C,然後python調用C的接口, 還需要定義python的使用函數的接口參數和返回結果的類型。稍不注意就會出錯,使用體驗很不好。