Export QObject-based class to DLL - qt

I'm composing a class which derives from QObject, and I want to export this class into a DLL file so other applications can use it. But I got some mysterious problem here:
The code is shown below:
mydll.h:
#ifndef MYDLL_H
#define MYDLL_H
#include "mydll_global.h"
#include <QObject>
#include <QDebug>
class MYDLLSHARED_EXPORT MyDll : public QObject
{
Q_OBJECT
public:
explicit MyDll(QObject * parent = 0);
void test() const;
};
#endif // MYDLL_H
mydll_global.h:
#ifndef MYDLL_GLOBAL_H
#define MYDLL_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(MYDLL_LIBRARY)
# define MYDLLSHARED_EXPORT Q_DECL_EXPORT
#else
# define MYDLLSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // MYDLL_GLOBAL_H
mydll.cpp:
#include "mydll.h"
MyDll::MyDll(QObject * parent) :
QObject(parent)
{
}
void MyDll::test() const {
qDebug() << "Hello from dll!";
}
and the dll is used in another application. The dll is compiled successfully. I've add LIBS += "myDll.dll" in the .pro file of the application using this dll, and I've copied myDll.dll to the working directory of the application.
The compiler reports:
C4273: "MyDll::qt_static_metacall" : inconsistent dll linkage.
C2491: "MyDll::staticMetaObject": definition of dllimport static data member not allowed
What's the problem here?

Your code for mydll_global.h checks whether MYDLL_LIBRARY is defined, but none of the code you have posted defines MYDLL_LIBRARY. Is this declared in a file that you have not shared on the question? If not, you need to add a #define MYDLL_LIBRARY in your build project, or your PCH.

Related

cmake qt6: unresolved external symbol “public: virtual struct QMetaObject”

I am building qt6 project with cmake. I want to generate a library with QWidget, and the code is:
#ifndef GLOBAL_EXPORTS
#define GLOBAL_EXPORT __declspec(dllexport)
#else
#define GLOBAL_EXPORT __declspec(dllimport)
#endif // !GLOBAL_EXPORTS
class GLOBAL_EXPORT SWidgets : public QWidget
{
Q_OBJECT
public:
SWidgets();
};
In compiling, the vs reports:
unresolved external symbol "public: virtual struct QMetaObject const SWidgets::staticMetaObject"
The bug can be solved by copy moc file. But, it is inconvenient because I have many qt relative library.
In addition, I have add set(CMAKE_AUTOMOC 1), and it do not work.
I have upload the code in github (https://github.com/zhang-qiang-github/cmake_qt).
How to solve this bug by cmake? Any suggestion is appreciated~~~
Update: sorry for uploading code to github.
First, I define my GLOBAL_EXPORTS in a Global.h file:
#ifndef GLOBAL_EXPORTS_H
#define GLOBAL_EXPORTS_H
#if (WIN32)
#ifndef GLOBAL_EXPORTS
#define GLOBAL_EXPORT __declspec(dllexport)
#else
#define GLOBAL_EXPORT __declspec(dllimport)
#endif // !GLOBAL_EXPORTS
#else
#define GLOBAL_EXPORT
#endif
#endif
Then I define my custom widget as:
#ifndef swidget_header_h
#define swidget_header_h
#include "Global.h"
#include <qwidget.h>
#include "qpushbutton.h"
class GLOBAL_EXPORT SWidgets : public QWidget
{
Q_OBJECT
public:
SWidgets();
};
#endif // !swidget_header_h
As #Tsyvarev has pointed out, your handling of the GLOBAL_EXPORTS definition is wrong.
You should export the symbols if the GLOBAL_EXPORTS definition is set, therefore it should read (note: I changed #ifndef to #ifdef):
#ifdef GLOBAL_EXPORTS
#define GLOBAL_EXPORT __declspec(dllexport)
#else
#define GLOBAL_EXPORT __declspec(dllimport)
#endif // !GLOBAL_EXPORTS
Then in your CMakeLists.txt of the export_dll project you should add a line defining GLOBAL_EXPORTS:
set(CMAKE_AUTOMOC 1)
add_library(export_dll SHARED ${SOURCES} ${HEADERS})
set_target_properties(export_dll PROPERTIES COMPILE_DEFINITIONS GLOBAL_EXPORTS)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui)
target_link_libraries(export_dll PRIVATE Qt6::Widgets Qt6::Core Qt6::Gui)
By doing this __declspec(dllexport) will be added to the symbols when creating the DLL and every consumer of the DLL will automatically get the symbols with a proper __declspec(dllimport) set.

Qt class MyButton when use Q_PRIVATE_SLOT, the .h file compile error: undefined MyButtonPrivate

https://www.dvratil.cz/2019/11/q-private-slot-with-new-connect-syntax/
#include <QPushButton>
#include <memory>
class MyButtonPrivate;
class MyButton : public QPushButton {
Q_OBJECT
public:
explicit MyButton(QWidget* parent);
~MyButton() noexcept override;
private:
std::unique_ptr<MyButtonPrivate> const d_ptr;
Q_DECLARE_PRIVATE(MyButton);
Q_PRIVATE_SLOT(d_func(), void onClicked(bool));
};
this is the .h file, compile error: undefined MyButtonPrivate.
truely, the moc_MyButton.cpp(auto generated by compiler) doesnot include the MyButtonPrivate.
then what's wrong, and how to solve?
I'm not sure why it's an error, but according to https://forum.qt.io/topic/61295/solved-what-is-the-usage-of-q_private_slot/3, with Qt > 5 and C++11 it is a useless macro.
the moc file generated:
#include "../../MyButton.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
...
case 0: _t->d_func()->onClicked((*reinterpret_cast< bool(*)>(_a[1]))); break;
...
notice: _t->d_func()->onClicked invoke the private class's func(but no include)
so absolutely result in compile error. the question is moc file doesnt include MyButton_p.h, and manually add the include:
#include "../../MyButton.h"
#include "../../MyButton_p.h"
...
then its ok(but the moc file shouldnt modify manually)

Qt: Compare QVariable with Enum of DLL

I try to compare a QVariable of a QSignalSpy with a Enum in a test function. I found Q_ENUM_NS, but I got a linker error. I have rebuilt it and run qmake.
Error Message: Undefined reference to CCS::staticMetaObject
testopcua.cpp (most parts)
Q_DECLARE_METATYPE(CCS::DataManagerState)
using namespace ::testing;
class TestOpcUa : public Test
{
public:
TestOpcUa()
{
//.....
}
~TestOpcUa()
{
delete _selectedConfValid;
delete _opcUaObjectValid;
}
protected:
MockOpcConf* _selectedConfValid = nullptr;
OpcUa* _opcUaObjectValid = nullptr;
};
TEST_F(TestOpcUa, checkHandshake)
{
qRegisterMetaType<CCS::DataManagerState>("state");
QSignalSpy readySpy(_opcUaObjectValid, SIGNAL(newManagerState(CCS::DataManagerState)));
_opcUaObjectValid->connect();
readySpy.wait();
ASSERT_EQ(readySpy.count(), 1);
ASSERT_EQ(readySpy.at(0).at(0).value<CCS::DataManagerState>(), CCS::DataManagerState::Ready); //Linker error
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}
enums.h of DLL
#include <QObject>
namespace CCS
{
Q_NAMESPACE
enum class DataManagerState
{None, Configure, Ready, Active, Waiting, Stop};
Q_ENUM_NS(DataManagerState)
}
I think you need to add an explicit export/import directive to the QT_NAMESPACE macro.
namespace CCS
{
Q_DECL_EXPORT Q_NAMESPACE
...
}
But you'll most likely need to abstract the Q_DECL_EXPORT part so it is a Q_DECL_IMPORT in right circumstances, as described in Creating Shared Libraries (or in the Qt Wiki How to create a library with Qt...)
global.h of DLL
#include <QtCore/QtGlobal>
#if defined(MYSHAREDLIB_LIBRARY)
# define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#else
# define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif
enums.h of DLL
#include "global.h"
#include <QObject>
namespace CCS
{
MYSHAREDLIB_EXPORT Q_NAMESPACE
...
}
Note that if you plan to also use the "DLL" code as a statically linked lib (or directly included in other code, w/out any lib), you'll need a 3rd condition in global.h which defines MYSHAREDLIB_EXPORT as nothing.
global.h of DLL
#include <QtCore/QtGlobal>
#if defined(MYSHAREDLIB_LIBRARY)
# define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#elif defined(MYSHAREDLIB_STATIC)
# define MYSHAREDLIB_EXPORT
#else
# define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif
Q_DECLARE_METATYPE(CCS::DataManagerState)
Shouldn't need this.
Since Qt 5.14 you can use Q_NAMESPACE_EXPORT macro.
https://doc.qt.io/qt-5/qobject.html#Q_NAMESPACE_EXPORT
namespace CSS
{
Q_NAMESPACE_EXPORT(MYSHAREDLIB_LIBRARY)
...
}

templated class in Qt

Is it possible to use a templated class (not based on QObject and doesn't have Q_OBJECT macro) in Qt? I keep getting a linker error when trying to use a templated class. however, when I remove the template from the class, it compiles and links fine. I'm just trying to declare a local variable of type Filter, which uses a template, and I get this linker error:
error: undefined reference to `NumericFilter<int>::NumericFilter(int, int)'
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "filter.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
NumericFilter<int> filter(0, 1);
}
MainWindow::~MainWindow()
{
delete ui;
}
filter.h
template <class T>
class NumericFilter {
public:
NumericFilter (int itemType, int val);
protected:
T m_val;
};
filter.cpp
#include "filter.h"
template <class T>
NumericFilter<T>::NumericFilter (int, int)
{
}
Note that if you remove the template in the declaration and source files and comment out the 'T' member, then it compiles fine.
http://www.parashift.com/c++-faq-lite/separate-template-fn-defn-from-decl.html
If you compile and (try to) link these two .cpp files, most compilers will generate linker errors. There are two solutions for this. The first solution is to physically move the definition of the template function into the .h file, even if it is not an inline function.
This solution may (or may not!) cause significant code bloat, meaning your executable size may increase dramatically (or, if your compiler is smart enough, may not; try it and see).
The other solution is to leave the definition of the template function in the .cpp file and simply add the line template void foo(); to that file:
// File "foo.cpp"
#include <iostream>
#include "foo.h"
template<typename T> void foo()
{
std::cout << "Here I am!\n";
}
template void foo<int>();
So for your case, you would have in your .cpp file:
#include "filter.h"
template <class T>
NumericFilter<T>::NumericFilter(int, int)
{
}
template NumericFilter<int>::NumericFilter<int>(int, int); // added line!!!
Tada! No compile errors!
BTW, parashift's explanations on templates in C++ are the best IMHO.
Hope that helps.

Using a Singleton Class across a Qt Application and its Plugins

I'm trying to use a Singleton Class (its a program-wide debug logger called 'PrisLog') across my Qt Application. The program also has plugins. I want to make my singleton class available to those plugins, but this doesn't work. From what I can tell, trying to use the class in the plugin results in another instance being created.
-The singleton class is just a *.cpp and *.h file, nothing else. I've linked both my main application and the plugin to these files individually... is this the right way to do it?
-I've attached my singleton class's code below, though I think I've created the class correctly. If I use it from within separate classes in my main application, it works as expected (one instance).
EDIT: Linking both the application and plugin to the same static lib (the singleton class) works. Here's how my qmake *.pro files looked:
MySingletonLib.pro
TEMPLATE = lib
CONFIG += staticlib
HEADERS += \
mysingletonlib.h
SOURCES += \
mysingletonlib.cpp
MyPlugin.pro (also incl #include mysingletonlib.h in myplugin.h)
INCLUDEPATH += path/to/MySingletonLib
LIBS += -Lpath/to/MySingletonLib -lMySingletonLib
MyPlugin.pro (also incl #include mysingletonlib.h in myapp.h)
INCLUDEPATH += path/to/MySingletonLib
LIBS += -Lpath/to/MySingletonLib -lMySingletonLib
And the original code:
#ifndef PRISLOG_H
#define PRISLOG_H
#include <QFile>
#include <QDir>
#include <QString>
#include <QMutex>
#include <QDebug>
#include <QMutexLocker>
#include <QTextStream>
#include <QDateTime>
// PrisLog (singleton) class definition
class PrisLog
{
public:
static PrisLog* Instance();
void SetLogsPath(QString);
QString GetLogsPath();
void SetDebugDestination(QString);
void SetElmRxDestination(QString);
void SetElmTxDestination(QString);
void SetDlgDestination(QString);
QTextStream* GetDebugStream();
QTextStream* GetElmRxStream();
QTextStream* GetElmTxStream();
QTextStream* GetDlgStream();
QMutex* GetDebugMutex();
private:
PrisLog(); // private constructor
PrisLog(const PrisLog&); // prevent copy constructor
PrisLog& operator=(const PrisLog&); // prevent assignment
static PrisLog* m_Instance;
static bool m_InitFlag;
QString m_appPath;
QFile m_DebugFile;
QTextStream m_DebugStream;
QMutex m_DebugMutex;
QFile m_ElmRxFile;
QTextStream m_ElmRxStream;
QFile m_ElmTxFile;
QTextStream m_ElmTxStream;
QFile m_DlgFile;
QTextStream m_DlgStream;
};
// thread-UNSAFE writer, but less expensive
// use: single stream <--> single thread!
class PrisLogWriter
{
public:
PrisLogWriter(QTextStream*);
~PrisLogWriter();
QTextStream* m_stream;
};
// thread-UNSAFE writer, but less expensive
// this version does not include any formatting
// use: single stream <--> single thread!
class PrisLogRawWriter
{
public:
PrisLogRawWriter(QTextStream*);
~PrisLogRawWriter();
QTextStream* m_stream;
};
// thread-safe writer
// use: single stream <--> many threads
class PrisLogSafeWriter
{
public:
PrisLogSafeWriter(QTextStream*, QMutex*);
~PrisLogSafeWriter();
QTextStream* m_stream;
private:
QMutex* m_mutex;
};
#define PRISLOGDEBUG (*(PrisLogSafeWriter(PrisLog::Instance()->GetDebugStream(), PrisLog::Instance()->GetDebugMutex()).m_stream))
#define PRISLOGELMRX (*(PrisLogWriter(PrisLog::Instance()->GetElmRxStream()).m_stream))
#define PRISLOGELMTX (*(PrisLogWriter(PrisLog::Instance()->GetElmTxStream()).m_stream))
#define PRISLOGDLG (*(PrisLogRawWriter(PrisLog::Instance()->GetDlgStream()).m_stream))
#endif // PRISLOG_H
I think you should take this class to a statically linked, but separeted shared dll/so.
If your host application does not use this class, the linker simply won't link it into the binary and your plugin could not use it. Additionally: your binary has no library class interface.
You need to make sure, that only one instance exists. The safest is, that the cpp is compiled only once in the overall exe.
To make sure that the other DLLs can call PrisLog::Instance, this class/function (i.e. all public methods if PrisLog) needs to be declared with __declspec(dllexport), even if it is located within the exe.
The DLLs can then dynamically find the object and method.
BTW: Why don't you use the logging from Qt?

Resources