EN ·
🌏 中文

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:

  1. QCoreApplication: Manages the Event Loop for non-GUI apps.
  2. QApplication: Inherits from QCoreApplication and 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!