How to export QObject on windows
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