Integrating QtHttpServer into a Dynamic Library with C APIs: Handling QCoreApplication Dependencies
Compiling the QtHttpServer Module
First, clone the source code and switch to the appropriate branch:
git clone https://github.com/qt-labs/qthttpserver.git
cd qthttpserver
git checkout 5.15
git submodule update --init --recursive
Then, open the project in Qt Creator and build it.
A Minimal Qt Console Application
A basic Qt console application typically consists of a non-GUI “Hello World” structure. The core logic looks like this:
#include <QtCore>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
// qDebug() << "Hello World";
return app.exec();
}
Using QtHttpServer
QtHttpServer is currently a Qt Labs project and is not yet part of the official Qt 5 core libraries (it is expected to be fully integrated in Qt 6). Therefore, you need to compile it manually. Repository: https://github.com/qt-labs/qthttpserver
Build and Installation
If you are using Qt 5, use the 5.15 branch. After a successful build, you must manually copy the headers and dynamic libraries to your Qt installation directory so other projects can find them.
Here is an example for a Linux environment (adjust paths as necessary):
# Enter the build output directory
cd build-qthttpserver-Desktop_Qt_5_12_6_GCC_64bit-Release/
# Move library files
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/
# Move CMake configs
cd cmake/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/cmake/
# Move pkgconfig
cd ..
cd pkgconfig/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/lib/pkgconfig/
# Move headers
cd ..
cd ..
cd include/
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/include/
# Move module specs
cd ..
cd mkspecs
cd modules
mv ./* /opt/Qt5.12.6/5.12.6/gcc_64/mkspecs/modules/
Starting QtHttpServer
The official Qt blog post introducing-qt-http-server demonstrates the basic usage:
#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();
}
Once started, navigate to http://127.0.0.1:9527 to see “Hello world”. To handle POST requests and read the body:
httpServer.route("/post-body", "POST", [] (const QHttpServerRequest &request) {
return request.body();
});
For more details on routing, check out: qhttpserver-routing-api.
Encapsulating the Service Class
To manage the server effectively, we wrap it in a class named MainSVC.
MainSVC Header
// FileName: mainsvc.h
class MainSVC : public QObject
{
Q_OBJECT
public:
MainSVC(QObject *parent=nullptr);
~MainSVC();
void start();
};
Implementation
Note: To ensure the QtHttpServer remains active, define it as a static variable or a class member.
// 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);
}
Using Qt Components in a C-API Dynamic Library
The QCoreApplication Challenge
If you call a Qt-based dynamic library from a non-Qt C++ project, you will likely encounter an error stating that a QCoreApplication is required.
This is because:
- QCoreApplication: Manages the Event Loop for non-GUI apps.
- QApplication: Inherits from
QCoreApplicationand manages GUI resources.
Qt’s signals/slots and networking modules require the event loop started by exec(). In a library context, we must manually maintain this loop.
Solution: Global Object and Background Event Loop
We can define a global QCoreApplication within the library and provide dummy argc/argv parameters:
int argc = 1;
char param[] = "test";
char *argv[1] = { param };
static QCoreApplication a(argc, argv);
Initialization and Synchronization
We define an init function that creates the service object and calls a.exec(). Since exec() is blocking, we use QtConcurrent::run to start it in a background thread. We use a QMutex to ensure the calling thread waits until initialization is complete.
Full Implementation:
// 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(); // Starts the event loop
}
int svc_init(void)
{
QFuture<void> future = QtConcurrent::run(init);
QThread::msleep(100); // Ensure the thread has started and locked the mutex
init_lock.lock(); // Wait for main_svc initialization to finish
init_lock.unlock();
return 0;
}
bool svc_start()
{
if (main_svc != nullptr) {
main_svc->start();
return true;
}
return false;
}
C-API Header
To allow non-Qt or non-C++ environments (like Python or pure C) to use the library, export the functions using 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
Build Configuration (CMake)
Using CMake is recommended. By enabling CMAKE_AUTOMOC, CMake handles the Meta-Object Compiler automatically.
cmake_minimum_required(VERSION 3.8.0)
project(test-svc VERSION 0.1.0 LANGUAGES CXX)
# Replace with your local Qt path
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)
# Enable automatic Qt tools
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})
Conclusion
By initializing a static QCoreApplication and running the event loop in a background thread, you can successfully leverage Qt modules like QtHttpServer inside a standard C-compatible dynamic library. This approach maintains the power of Qt’s signals and slots while providing a clean, decoupled interface for external callers.
Feel free to leave a comment if you have questions!