How to export QObject on windows

Categories: Tool chain

Problem

Suppose you have an qt application which is splitted into one or more shared libraries.Or you maybe want to provide library with shall be shared.

In both cases you want to use Qt’s signal slot mechanism, which than is used by your application or the users of your library. Or you simply want to provide classes and methods, which can be used from someone else.

Here is a sample of the library.

#pragma once

#include <QtCore/QObject>

class Foo : public QObject
{
    Q_OBJECT

public:
    Foo();
    virtual ~Foo();

signals:
    void callFrom(QString who);

public slots:
    void doSomethingFoo();
};

Sample Code in your application or some user of your library.

void Application::doSomthing()
{
    auto obj = std::make_shared<Foo>();
    
    connect(this, &Application::callFoo, obj.get(), &Foo::doSomethingFoo);
    connect(obj.get(), &Foo::callFrom, this, &Application::onLibCalls);

    emit callFoo();

    processEvents();

    exit();
}

And the CMake setup of LibA and some Programm dynamically linking LibA.

# ...
QT_STANDARD_PROJECT_SETUP()

QT_ADD_LIBRARY(
    LibA SHARED
    source/LibA/Foo.h
    source/LibA/Foo.cpp
)

QT_ADD_EXECUTABLE(
    Programm
    source/Exe/Application.h
    source/Exe/Application.cpp
    source/Exe/main.cpp
)

TARGET_LINK_LIBRARIES(LibA     PRIVATE      Qt6::Core) # LibA     depends on          QtCore
TARGET_LINK_LIBRARIES(Programm PRIVATE LibA Qt6::Core) # Programm depends on LibA and QtCore
# ...

Exporting symbols on Windows

In order to that we need to explicitly export the symbols on windows of the classes which are used from the outside.

On Windows this is done by using __declspec(dllexport).

What happens here?

The Compiler runs over foo.cpp and foo.h of LibA, generates foo.obj and marks the symbols of foo as exportable.The linker then links all obj files into a dll. All symbols which were marked as “to be exported” are the useable from outside.

Sample from Dependcies Viewer.

On Unix-Systems like Linux or Mac we don’t need this mechanism, because here every symbol is exported automatically. Everything is transparent by default.

On windows everything is hidden by default, to prevent the instrumentation of internals and thus the preventing abuse of the libraries or sys internals. But, if this is a good design or some useful feature, is not the topic here.

Okay, let explicitly export symbols from Foo.

#include <QtCore/QObject>

class __declspec(dllexport) Foo : public QObject
{
    Q_OBJECT

    ...
};

Build and … we get an error.

>Application.obj : error LNK2001: unresolved external symbol "public: static struct QMetaObject const Foo::staticMetaObject" ...
>\UseQObjectFromLib\bin\Debug\Programm.exe : fatal error LNK1120: 1 unresolved externals

What is missing here?

We have forgotten to tell Programm that we need to import symbols from our Library.

On Windows again this is done by using __declspec(dllimport).

In order to do that we simply must mark the class Foo with __declspec(dllimport).

Foo marked as to be imported.

#include <QtCore/QObject>

class __declspec(dllimport) Foo : public QObject
{
    Q_OBJECT

    ...
};

But now, as you might noticed, we need two statements __declspec(dllimport) and __declspec(dllexport) on front of Foo, which is clearly not possible. But how can we than solve this?

Preprocessor magic

Let’s introduce a new header file.

#ifdef D_EXPORT
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif

And tell CMake to use it

# ...
QT_ADD_LIBRARY(
    LibA SHARED
    source/LibA/export.h
    source/LibA/Foo.h
    source/LibA/Foo.cpp
)
#...

With this we switch between __declspec(dllimport) and __declspec(dllexport) by defining or not defining D_EXPORT. This is our switch.
We go on and place the newly defined EXPORT_API in front of Foo.

#include "LibA/export.h"

#include <QtCore/QObject>

class EXPORT_API Foo : public QObject
{
    Q_OBJECT

    ...
};

Now we decide based on the definition of D_EXPORT, if we want export or import symbols from LibA. So let’s define D_EXPORT. But the question is where?

Let’s place D_EXPORT in export.h

#define D_EXPORT

#ifdef D_EXPORT
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif

The same linking error again. But Why?

If we place it here it always expands to the __declspec(dllexport) case, which will result in the linking error, because Programm never “sees” Foo as __declspec(dllimport).

We need to tell the compiler when he “has to see” which version of __declspec(…). When compiling things from LibA, he must see __declspec(dllexport) and when from Programm he has to see __declspec(dllimport).

Here is the required compiler view of Foo.h from LibA’s perspective.

class __declspec(dllexport) Foo : public QObject

And here is the required compiler view of Foo.h from Programm’s perspective.

class __declspec(dllimport) Foo : public QObject

So let’s add a component bases compiler definition for LibA and do nothing for Programm.

TARGET_COMPILE_DEFINITIONS(LibA   PRIVATE D_EXPORT)

And now it compiles and links. πŸ™‚

And if we check, what is imported from Lib, we see that is all the symbols, which are used by Programm.

OS independence

Great, we are almost done. The last thing we want assure is, that we want to be OS-agnostic. This means, that the code must also compile on operation system, which dont care about the to be exported symbols.

Fortunately Qt offers something for us.

Q_DECL_EXPORT
Q_DECL_IMPORT

These defines are located in QCompilerdetection.h, which i recommend to include via

#include <QtCore/QtGlobal>

Q_DECL_EXPORT and Q_DECL_IMPORT expands to the relevant statements according to the OS we compile for. On Windows to __declspec(dllexport) and __declspec(dllimport).

Our version of export.h looks like this now.

#pragma once

#include <QtCore/QtGlobal>

#ifdef D_EXPORT
#define EXPORT_API Q_DECL_EXPORT
#else
#define EXPORT_API Q_DECL_IMPORT
#endif

And out final version of Foo

#pragma once

#include "LibA/export.h"

#include <QtCore/QObject>

class EXPORT_API Foo : public QObject
{
        // ...
}

With this solution we can now export freely everything we want to from LibA and link from LibA’s dll. If we don’t want things to be visible for the outside world (on windows), we simply don’t use EXPORT_API.

The sources can be found here: Use QObject From Lib

Have fun with it. πŸ™‚


    Leave a Reply

    Your email address will not be published.

    Hi Human, please solve this: 8 + 1 =