编译 QtHttpServer 并封装为 C 接口动态库:解决 QCoreApplication 依赖问题
编译 QtHttpServer 模块
首先,从 GitHub 拉取代码并切换到对应分支:
git clone https://github.com/qt-labs/qthttpserver.git
cd qthttpserver
git checkout 5.15
git submodule update --init --recursive
然后使用 Qt Creator 打开工程并进行编译。
最简单的 Qt 控制台程序
一个最基础的 Qt 程序通常是一个没有 UI、在控制台运行的 HelloWorld。其核心代码结构如下:
#include <QtCore>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
// qDebug() << "Hello World";
return app.exec();
}
使用 QtHttpServer
QtHttpServer 目前尚未集成在 Qt 的主库中(预计在 Qt 6 会正式加入)。因此,我们需要自行下载并编译。项目地址:https://github.com/qt-labs/qthttpserver
编译与安装
如果你使用的是 Qt 5,建议切出 5.15 分支。编译成功后,需要手动将生成的头文件和动态库拷贝到 Qt 的安装路径下,以便其他项目引用。
以下是以 Linux 环境为例的拷贝参考(请根据实际路径修改):
# 进入编译输出目录
cd build-qthttpserver-Desktop_Qt_5_12_6_GCC_64bit-Release/
# 拷贝库文件
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/
# 拷贝 CMake 配置文件
cd cmake/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/cmake/
# 拷贝 pkgconfig
cd ..
cd pkgconfig/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/pkgconfig/
# 拷贝头文件
cd ..
cd ..
cd include/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/include/
# 拷贝模块配置文件
cd ..
cd mkspecs
cd modules
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/mkspecs/modules/
启动 QtHttpServer
在 Qt 官方博客 introducing-qt-http-server 中,介绍了一个最基本的 QHttpServer 用法:
#include <QtCore>
#include <QtHttpServer>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
QHttpServer httpServer;
httpServer.route("/", []() {
return "Hello world";
});
httpServer.listen(QHostAddress::Any, 9527);
return app.exec();
}
程序启动后,访问 http://127.0.0.1:9527 即可看到 “Hello world”。如果需要支持 POST 方法并接收数据,可以参考如下代码:
httpServer.route("/post-body", "POST", [] (const QHttpServerRequest &request) {
return request.body();
});
更多关于路由的用法,可以参考这篇博客:qhttpserver-routing-api。
封装主服务类
为了更好地管理 HttpServer,我们将其封装在 MainSVC 类中,作为整体服务的入口。
MainSVC 头文件
// FileName: mainsvc.h
class MainSVC : public QObject
{
Q_OBJECT
public:
MainSVC(QObject *parent=nullptr);
~MainSVC();
void start();
};
实现文件
注意:为了保证 QtHttpServer 实例在服务运行期间一直有效,这里将其定义为静态变量或类成员。
// FileName: mainsvc.cpp
#include <QHttpServer>
#include "mainsvc.h"
static QtHttpServer http_server;
MainSVC::MainSVC()
{
http_server.route("/", []() {
return "BigBookPlus Server Test.";
});
}
MainSVC::~MainSVC()
{
}
void MainSVC::start()
{
int port = 9527;
http_server.listen(QHostAddress::Any, port);
}
在动态链接库中使用 Qt 组件并提供 C 语言 API
关于 QApplication / QCoreApplication 的坑
当你在一个非 Qt 的 C++ 工程中调用基于 Qt 开发的动态库时,经常会遇到类似“必须先创建 QApplication/QCoreApplication 才能运行”的报错。
其原因在于:
- QCoreApplication:管理非 GUI 程序的事件循环(Event Loop)。
- QApplication:继承自
QCoreApplication,管理 GUI 相关的资源(如 QWidget)。
Qt 的信号槽机制、网络模块等都极度依赖 exec() 开启的事件循环。在动态库场景下,我们需要在主线程之外手动维护这个循环。
解决方案:全局对象与子线程事件循环
在动态库内部,我们可以定义一个全局的 QCoreApplication 对象,并为其提供伪造的 argc/argv:
int argc = 1;
char param[] = "test";
char *argv[1] = { param };
static QCoreApplication a(argc, argv);
服务初始化与同步
我们定义一个 init 函数,在其中创建服务对象并调用 a.exec()。由于 exec() 是阻塞的,我们必须使用 QtConcurrent::run 在子线程中启动它。
为了确保调用者在 svc_init 返回时,服务已经完全初始化,我们引入 QMutex 进行同步控制。
完整实现:
// FileName: svc_c_api.cpp
#include <QtCore>
#include <QtConcurrent>
#include "svc_c_api.h"
#include "mainsvc.h"
int argc = 1;
char param[] = "test";
char *argv[1] = { param };
static QCoreApplication a(argc, argv);
static QMutex init_lock;
MainSVC* main_svc = nullptr;
static int init(void)
{
init_lock.lock();
main_svc = new MainSVC(Q_NULLPTR);
init_lock.unlock();
return a.exec(); // 开启事件循环
}
int svc_init(void)
{
QFuture<void> future = QtConcurrent::run(init);
QThread::msleep(100); // 确保子线程已经运行并锁定 mutex
init_lock.lock(); // 等待 main_svc 初始化完成
init_lock.unlock();
return 0;
}
bool svc_start()
{
if (main_svc != nullptr) {
main_svc->start();
return true;
}
return false;
}
C 语言接口文件
为了让非 C++ 或非 Qt 环境(如纯 C 或 Python)能够调用,我们需要使用 extern "C" 导出接口。
// FileName: svc_c_api.h
#ifndef SVC_C_API_H_
#define SVC_C_API_H_
#if defined(_MSC_VER)
# if defined(SVC_LIB)
# define SVC_QTLIB_EXPORT __declspec(dllexport)
# else
# define SVC_QTLIB_EXPORT __declspec(dllimport)
# endif
#else
# define SVC_QTLIB_EXPORT
#endif
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
SVC_QTLIB_EXPORT int svc_init();
SVC_QTLIB_EXPORT bool svc_start();
#ifdef __cplusplus
}
#endif
#endif
构建配置 (CMake)
推荐使用 CMake 来构建项目。通过设置 CMAKE_AUTOMOC 等选项,CMake 可以自动处理 Qt 的元对象编译。
cmake_minimum_required(VERSION 3.8.0)
project(test-svc VERSION 0.1.0 LANGUAGES CXX)
# 设置 Qt 路径(根据实际环境修改)
set(Qt5_DIR D:/Qt/Qt5.12.6/5.12.6/msvc2017_64/lib/cmake/Qt5)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 自动处理 MOC, RCC, UIC
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
add_definitions(-D SVC_LIB)
find_package(Qt5 COMPONENTS Core Network Concurrent HttpServer REQUIRED)
set(USED_QT_LIBRARIES Qt5::Core Qt5::Network Qt5::Concurrent Qt5::HttpServer)
aux_source_directory(src SVC_SRCS)
add_library(snn-svc SHARED ${SVC_SRCS})
target_link_libraries(snn-svc ${USED_QT_LIBRARIES})
总结
通过在动态库中静态初始化 QCoreApplication 并在后台线程运行事件循环,我们可以完美地在非 Qt 项目中利用 Qt 的强大功能(如 QtHttpServer)。这种方式既保留了 Qt 信号槽的便利性,又通过 C 接口实现了良好的解耦。
欢迎留言探讨。