Undefined reference to vtable Qt - different behaviour MSVC and MINGW

112 views Asked by At

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 ?

0

There are 0 answers