I am working in a C++ project with configured CMake. I added a test project to the solution. Everything compiles and run ok with MSVC 2019 but with mingw 11.2 I get numerous errors:
undefined reference to `LoginCommObj::~LoginCommObj()'
undefined reference to `BasisCommObj::~BasisCommObj()
undefined reference to `BasisCommObj::~BasisCommObj()
undefined reference to `vtable for LoginCommObj
.......
Here is the minimum reproducible example:
Project structure:
---multiround
|
------viewmodels
|
------------cancelroundviewmodel.h
------communicationobjects
|
-----------cancelroundcommobj.h
-----------cancelroundcommobj.cpp
-----------basiscommobj.h
-----------basiscommobj.cpp
-------communicationtools.h
-------communicationtools.cpp
-------gameinfo.h
-------globaldata.h
-------globalgamedata.h
-------globaluserdata.h
-------multiplayerround.cpp
-------multiplayerround.h
-------CMakeLists.txt
---tests
|
------main.cpp
------cancelroundcommobjtest.h
------cancelroundcommobjtest.cpp
------CMakeLists.txt
The source code is as follows:
multiround/CMakeLists.txt
cmake_minimum_required (VERSION 3.10)
project (libMultiRound)
cmake_policy(SET CMP0020 NEW)
cmake_policy(SET CMP0043 NEW)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_definitions(-DMAKE_MULTIPLAYERROUND_LIB)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${Qt6Widgets_INCLUDE_DIRS}
${Qt6Network_INCLUDE_DIRS}
${Qt6Core_INCLUDE_DIRS})
set(MULTIROUND_HEADR
communicationobjects/basiscommobj.h
globaldata.h
globalgamedata.h
globaluserdata.h
viewmodels/cancelroundviewmodel.h
communicationobjects/basiscommobj.h
communicationobjects/cancelroundcommobj.h
communicationtools.h
multiplayerround.h
gameinfo.h
)
set(MULTIROUND_SRCS
communicationobjects/basiscommobj.cpp
communicationobjects/cancelroundcommobj.cpp
communicationobjects/basiscommobj.cpp
communicationtools.cpp
multiplayerround.cpp
)
add_library(libMultiRound SHARED
${MULTIROUND_SRCS}
${MULTIROUND_HEADR}
)
target_link_libraries(libMultiRound
Qt6::Widgets
Qt6::Network
Qt6::Core)
multiround/viewmodels/cancelroundviewmodel.h
#ifndef __CANCEL_ROUND_VIEWMODEL__
#define __CANCEL_ROUND_VIEWMODEL__
#include <QString>
#include <QJsonObject>
struct CancelRoundViewModel {
long int m_RoundId;
long int m_GameId;
QJsonObject toJson() {
QJsonObject retVal;
retVal.insert("roundId", QString::number(m_RoundId));
retVal.insert("gameId", QString::number(m_GameId));
return retVal;
}
};
#endif
multiround/communicationobjects/basiscommobj.h
#ifndef __BASIS_COM_OBJ__
#define __BASIS_COM_OBJ__
#include <QObject>
#include <QJsonObject>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QSettings>
#include <QWidget>
#include "globaldata.h"
class BasisCommObj : public QObject {
Q_OBJECT
public:
BasisCommObj(const QString& requestPath, const QString& actionName):
m_RequestPath(requestPath), m_ActionName(actionName), m_ParentWidget(nullptr) {
connect( m_NetworkManager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslErrorOccured(QNetworkReply*,QList<QSslError>)));
}
virtual ~BasisCommObj();
bool makeRequestBasis(bool withToken, bool fromFinishedSlot = false);
virtual bool validateReply(const QJsonObject& reply) = 0;
protected:
BasisCommObj() {}
public slots:
virtual void errorRequest(QNetworkReply::NetworkError code);
virtual void finishedRequest();
void sslErrorOccured(QNetworkReply* reply, const QList<QSslError>& errors);
protected:
bool finishRequestHelper(QJsonObject& retJson);
bool checkInt(const QJsonValue& jsonValue);
bool checkLong(const QString& stringVal);
protected:
std::vector<QNetworkReply*> m_ReplyObjectVector; //TODO: we don't need this
QNetworkReply* m_ReplyObject = nullptr;
QString m_RequestPath;
QString m_ActionName;
QJsonObject m_RequestData;
QWidget* m_ParentWidget;
QNetworkAccessManager* m_NetworkManager;
QSettings* m_Settings;
bool m_IsSinglePlayer = true;
GlobalData* m_GlobalData;
};
#endif
multiround/communicationobjects/basiscommobj.cpp
#include "basiscommobj.h"
#include <cmath>
#include <QMessageBox>
#include <QDebug>
#include <QJsonValue>
#include "communicationtools.h"
BasisCommObj::~BasisCommObj()
{
if (m_ReplyObject != nullptr)
delete m_ReplyObject;
}
//TODO: add timer to control maximum duration of request
bool BasisCommObj::makeRequestBasis(bool withToken, bool fromFinishedSlot)
{
if (m_IsSinglePlayer) {
//() << "makeRequestBasis in single player modus";
return false;
}
if ( m_ReplyObject != nullptr && m_ReplyObject->isRunning())
return false;
if ( m_ReplyObject!= nullptr) {
if (!fromFinishedSlot) {
delete m_ReplyObject;
} else { //cannot delete the reply object from finished slot
m_ReplyObjectVector.push_back(m_ReplyObject); //TODO: I don't really need this
m_ReplyObjectVector[m_ReplyObjectVector.size() - 1]->deleteLater();
}
}
if (withToken) {
m_ReplyObject = CommunicationTools::buildPostRequestWithAuth(m_RequestPath, m_Settings->value("multiplayer/serverpath").toString(), m_RequestData, m_GlobalData->m_UserData.m_AuthToken, m_NetworkManager);
} else {
m_ReplyObject = CommunicationTools::buildPostRequest(m_RequestPath, m_Settings->value("multiplayer/serverpath").toString(), m_RequestData, m_NetworkManager);
}
connect(m_ReplyObject, &QNetworkReply::finished, this, &BasisCommObj::finishedRequest);
connect(m_ReplyObject, &QNetworkReply::errorOccurred, this, &BasisCommObj::errorRequest);
return true;
}
void BasisCommObj::errorRequest(QNetworkReply::NetworkError code)
{
//qDebug() << "Error 1";
if (m_IsSinglePlayer) {
// qDebug() << "errorRequest in single player modus";
return;
}
CommunicationTools::treatCommunicationError(m_ActionName, m_ReplyObject, m_ParentWidget);
}
void BasisCommObj::finishedRequest()
{
//() << "Finished Request 1";
QJsonObject retJson;
if (!finishRequestHelper(retJson))
return;
}
bool BasisCommObj::finishRequestHelper(QJsonObject& retJson)
{
if (m_IsSinglePlayer) {
//qDebug() << "finishRequestHelper in single player modus";
return false;
}
if (m_ReplyObject == nullptr)
return false;
if (m_ReplyObject->error() != QNetworkReply::NoError) {
return false;
}
QByteArray reply = m_ReplyObject->readAll();
QString replyQString(reply);
//qDebug() << replyQString;
retJson = CommunicationTools::objectFromString(replyQString);
if (!validateReply(retJson)) {
QMessageBox msgBox(m_ParentWidget);
msgBox.setText(m_ActionName + " reply was not recognized");
msgBox.exec();
return false;
}
return true;
}
bool BasisCommObj::checkInt(const QJsonValue& jsonValue) {
double val = jsonValue.toDouble();
double fractpart, intpart;
fractpart = modf(val , &intpart);
if (fractpart < 0.000001)
return true;
return false;
}
bool BasisCommObj::checkLong(const QString& stringVal)
{
bool ok = false;
stringVal.toLong(&ok, 10);
return ok;
}
void BasisCommObj::sslErrorOccured(QNetworkReply* reply, const QList<QSslError>& errors)
{
qDebug() << "Ssl errors";
for (auto error : errors) {
qDebug() << error.errorString();
}
}
multiround/communicationobjects/cancelroundcommobj.h
#ifndef __CANCEL_ROUND_COMMOBJ__
#define __CANCEL_ROUND_COMMOBJ__
#include "basiscommobj.h"
#include "viewmodels/cancelroundviewmodel.h"
class MultiplayerRound;
class CancelRoundCommObj : public BasisCommObj {
Q_OBJECT
public:
CancelRoundCommObj(const QString& requestPath, const QString& actionName):
BasisCommObj(requestPath, actionName) {}
bool makeRequest();
bool validateReply(const QJsonObject& retJson) override;
protected:
CancelRoundCommObj() {}
public slots:
void finishedRequest() override;
signals:
void roundCancelled();
private:
CancelRoundViewModel prepareViewModel();
private:
MultiplayerRound* m_MultiRound;
friend class CancelRoundCommObjTest;
};
#endif
multiround/communicationobjects/cancelroundcommobj.cpp
#include "cancelroundcommobj.h"
#include <QMessageBox>
#include "viewmodels/cancelroundviewmodel.h"
#include "multiplayerround.h"
bool CancelRoundCommObj::makeRequest()
{
if (m_IsSinglePlayer) {
//qDebug() << "makeRequestBasis in single player modus";
return false;
}
if (m_GlobalData->m_UserData.m_UserName.isEmpty()) {
if (m_ParentWidget != nullptr) { //nullptr is in tests
QMessageBox msgBox(m_ParentWidget);
msgBox.setText("No user logged in");
msgBox.exec();
}
return false;
}
m_RequestData = prepareViewModel().toJson();
makeRequestBasis(true);
return true;
}
void CancelRoundCommObj::finishedRequest()
{
QJsonObject retJson;
if (!finishRequestHelper(retJson))
return;
//m_MultiRound->setRoundCancelled();
emit roundCancelled();
}
bool CancelRoundCommObj::validateReply(const QJsonObject& reply) {
return (reply.contains("roundId"));
}
CancelRoundViewModel CancelRoundCommObj::prepareViewModel() {
CancelRoundViewModel cancelRoundData;
cancelRoundData.m_RoundId = m_GlobalData->m_GameData.m_RoundId;
cancelRoundData.m_GameId = m_GlobalData->m_GameData.m_GameId;
return cancelRoundData;
}
multiround/communicationtools.h
#ifndef _COMMUNICATION_TOOLS__
#define _COMMUNICATION_TOOLS__
#include <QString>
#include <QNetworkReply>
#include <QJsonObject>
#include <QWidget>
class CommunicationTools {
private:
static QString localTestServerPath;
public:
static QNetworkReply* buildPostRequest(const QString& routePath, const QString& serverPath, const QJsonObject& jsonObject, QNetworkAccessManager* networkManager);
static QNetworkReply* buildPostRequestWithAuth(const QString& routePath, const QString& serverPath, const QJsonObject& jsonObject, const QByteArray& authToken, QNetworkAccessManager* networkManager);
static QJsonObject objectFromString(const QString& in);
static void treatCommunicationError(const QString& actionName, QNetworkReply* reply, QWidget* parentWidget);
};
#endif
multiround/communicationtools.cpp
#include "communicationtools.h"
#include <QJsonDocument>
#include <QMessageBox>
QString CommunicationTools::localTestServerPath = "";
QNetworkReply * CommunicationTools::buildPostRequestWithAuth(const QString& routePath, const QString& serverPath, const QJsonObject& jsonObject, const QByteArray& authToken, QNetworkAccessManager* networkManager)
{
QString requestPath = serverPath;
if (serverPath.isEmpty())
requestPath = localTestServerPath;
//qDebug() << "request to " << requestPath ;
QUrl loginRequestUrl = QUrl(requestPath + routePath);
QNetworkRequest request(loginRequestUrl);
request.setRawHeader("Content-Type", "application/fhir+json");
request.setRawHeader(QByteArray("Authorization"), authToken);
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setProtocol(QSsl::TlsV1_3);
request.setSslConfiguration(config);
//qDebug() << "prepare request" ;
QByteArray data = QJsonDocument(jsonObject).toJson();
//qDebug() << "prepare json";
return networkManager->post(request, data);
}
QNetworkReply * CommunicationTools::buildPostRequest(const QString& routePath, const QString& serverPath, const QJsonObject& jsonObject, QNetworkAccessManager* networkManager)
{
QString requestPath = serverPath;
if (serverPath.isEmpty())
requestPath = localTestServerPath;
//qDebug() << "request to " << requestPath ;
QUrl loginRequestUrl = QUrl(requestPath + routePath);
QNetworkRequest request(loginRequestUrl);
request.setRawHeader("Content-Type", "application/fhir+json");
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setProtocol(QSsl::TlsV1_3);
request.setSslConfiguration(config);
//qDebug() << "prepare request" ;
QByteArray data = QJsonDocument(jsonObject).toJson();
//qDebug() << "prepare json";
return networkManager->post(request, data);
}
QJsonObject CommunicationTools::objectFromString(const QString& in)
{
QJsonObject obj;
QJsonDocument doc = QJsonDocument::fromJson(in.toUtf8());
// check validity of the document
if(!doc.isNull()) {
if(doc.isObject()) {
obj = doc.object();
} else {
//qDebug() << "Document is not an object";
}
} else {
//qDebug() << "Invalid JSON...\n" << in;
}
return obj;
}
void CommunicationTools::treatCommunicationError(const QString& actionName, QNetworkReply* reply, QWidget* parentWidget) {
QByteArray replyBA = reply->readAll();
QString registrationReplyQString(replyBA);
QMessageBox msgBox(parentWidget);
msgBox.setText("Error when " + actionName + " " + reply->errorString() + "\n" + registrationReplyQString);
msgBox.exec();
}
multiround/multiplayerround.h
#ifndef __MULTIPLAYER_ROUND__
#define __MULTIPLAYER_ROUND__
#if defined MAKE_MULTIPLAYERROUND_LIB
#define MULTIPLAYER_EXPORT Q_DECL_EXPORT
#else
#define MULTIPLAYER_EXPORT Q_DECL_IMPORT
#endif
#include <QObject>
#include <QNetworkAccessManager>
#include <QSettings>
#include <QJsonObject>
#include <QNetworkReply>
#include <QTimer>
#include <QWidget>
#include "gameinfo.h"
#include "globaldata.h"
#include "communicationobjects/cancelroundcommobj.h"
class MULTIPLAYER_EXPORT MultiplayerRound : public QObject/*, public AbstractPlaneRound*/ {
Q_OBJECT
private:
CancelRoundCommObj* m_CancelRoundCommObj;
public:
MultiplayerRound();
~MultiplayerRound();
void cancelRound();
};
#endif
multiround/multiplayerround.cpp
#include "multiplayerround.h"
#include <QMessageBox>
#include <QJsonArray>
#include "communicationtools.h"
MultiplayerRound::MultiplayerRound()
{
m_CancelRoundCommObj = new CancelRoundCommObj("/round/cancel", "cancelling round ");
//connect(m_CancelRoundCommObj, &CancelRoundCommObj::roundCancelled, this, &MultiplayerRound::roundWasCancelled);
}
MultiplayerRound::~MultiplayerRound()
{
delete m_CancelRoundCommObj;
}
void MultiplayerRound::cancelRound()
{
m_CancelRoundCommObj->makeRequest();
}
multiround/gameinfo.h
#ifndef __GAME_INFO__
#define __GAME_INFO__
class GameInfo {
private:
bool m_IsSinglePlayer = false;
public:
GameInfo(bool isMultiplayer) { m_IsSinglePlayer = !isMultiplayer; }
void setSinglePlayer(bool singlePlayer) {
m_IsSinglePlayer = singlePlayer;
}
bool getSinglePlayer() {
return m_IsSinglePlayer;
}
};
multiround/globaldata.h
#ifndef __GLOBAL_DATA__
#define __GLOBAL_DATA__
#include "globaluserdata.h"
#include "globalgamedata.h"
struct GlobalData {
GlobalGameData m_GameData;
GlobalUserData m_UserData;
public:
void reset() {
m_GameData.reset();
m_UserData.reset();
}
};
multiround/globalgamedata.h
#ifndef __GLOBAL_GAME_DATA__
#define __GLOBAL_GAME_DATA__
#include <QString>
struct GlobalGameData {
long int m_GameId;
long int m_RoundId;
long int m_UserId;
long int m_OtherUserId;
QString m_GameName;
QString m_OtherUsername;
public:
void reset() {
m_GameId = 0;
m_RoundId = 0;
m_UserId = 0;
m_OtherUserId = 0;
m_GameName.clear();
m_OtherUsername.clear();
}
};
#endif
multiround/globaluserdata.h
#ifndef __GLOBAL_USER_DATA__
#define __GLOBAL_USER_DATA__
#include <QString>
#include <QByteArray>
struct GlobalUserData {
QString m_UserName;
QString m_UserPassword;
QByteArray m_AuthToken;
long int m_UserId;
public:
void reset() {
m_AuthToken = QByteArray(); //TODO: token expires some times
m_UserName = QString();
m_UserPassword = QString();
m_UserId = 0;
}
};
#endif
tests/CMakeLists.txt
cmake_minimum_required (VERSION 3.10)
project (commobjtest)
cmake_policy(SET CMP0020 NEW)
cmake_policy(SET CMP0043 NEW)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../multiround/
${CMAKE_CURRENT_SOURCE_DIR}/../multiround/communicationobjects
${CMAKE_CURRENT_SOURCE_DIR}/../multiround/viewmodels
#${CMAKE_CURRENT_SOURCE_DIR}/../bcrypt/
${Qt6Widgets_INCLUDE_DIRS}
${Qt6Network_INCLUDE_DIRS}
${Qt6Core_INCLUDE_DIRS}
${Qt6Network_INCLUDE_DIRS})
set(TEST_HEADR
cancelroundcommobjtest.h
#logincommobjtest.h
)
set(TEST_SRCS
cancelroundcommobjtest.cpp
#logincommobjtest.cpp
main.cpp)
enable_testing(true)
add_executable(commobjtest ${TEST_HEADR} ${TEST_SRCS})
add_test(NAME commobjtest COMMAND commobjtest)
target_link_libraries(commobjtest
libMultiRound
#libbcrypt
#libCommon
Qt6::Test
Qt6::Widgets
Qt6::Network
Qt6::Core)
tests/main.cpp
#include "cancelroundcommobjtest.h"
int main(int argc, char** argv)
{
int status = 0;
{
CancelRoundCommObjTest tc;
status |= QTest::qExec(&tc, argc, argv);
}
return status;
}
tests/cancelroundcommobjtest.h
#ifndef __CANCEL_ROUND_COMMOBJ_TEST__
#define __CANCEL_ROUND_COMMOBJ_TEST__
#include <QObject>
#include <QTest>
#include "cancelroundcommobj.h"
class CancelRoundCommObjTest : public QObject {
Q_OBJECT
private:
CancelRoundCommObj m_CommObj;
private slots:
void initTestCase();
void SinglePlayerTest();
void NoUserLoggedInTest();
void PrepareViewModelTest();
void cleanupTestCase();
};
#endif
tests/cancelroundcommobjtest.cpp
#include "cancelroundcommobjtest.h"
#include <QTest>
void CancelRoundCommObjTest::initTestCase()
{
qDebug("CancelRoundCommObjTest starts ..");
}
void CancelRoundCommObjTest::SinglePlayerTest()
{
m_CommObj.m_IsSinglePlayer = true;
QVERIFY2(m_CommObj.makeRequest() == false, "CancelRoundCommObj should abort if single player game");
}
void CancelRoundCommObjTest::NoUserLoggedInTest()
{
m_CommObj.m_IsSinglePlayer = false;
m_CommObj.m_ParentWidget = nullptr;
GlobalData* gd = new GlobalData();
gd->m_UserData.m_UserName = "";
m_CommObj.m_GlobalData = gd;
QVERIFY2(m_CommObj.makeRequest() == false, "Cannot cancel round without being logged in");
}
void CancelRoundCommObjTest::PrepareViewModelTest()
{
GlobalData* gd = new GlobalData();
gd->m_UserData.m_UserName = "testUserName";
gd->m_GameData.m_RoundId = 123L;
gd->m_GameData.m_GameId = 234L;
m_CommObj.m_GlobalData = gd;
CancelRoundViewModel viewModel = m_CommObj.prepareViewModel();
QVERIFY2(viewModel.m_GameId == 234L, "GameId was not copied to the view model");
QVERIFY2(viewModel.m_RoundId == 123L, "RoundId was not copied to the view model");
}
void CancelRoundCommObjTest::cleanupTestCase()
{
qDebug("CancelRoundCommObjTest ends ..");
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.10)
project (Planes)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
find_package(Qt6 COMPONENTS Core Widgets Quick Network Test)
add_subdirectory(multiround)
add_subdirectory(tests)
Does has an idea what this can be? How come this works on the Microsoft compiler and on MinGW not ?