0

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 ?

Cristi
  • 648
  • 1
  • 13
  • 28
  • Use qmake instead of cmake in Qt projects. If you still want using cmake, you should care of building Qt moc files on dependent objects. – 273K Oct 15 '22 at 17:16
  • @273K: Setting of [CMAKE_AUTOMOC](https://cmake.org/cmake/help/latest/variable/CMAKE_AUTOMOC.html) variable is responsible for generating moc files. And this setting is listed in the question's code... – Tsyvarev Oct 15 '22 at 18:12
  • @Tsyvarev OP sets the min cmake version 3.10, for that currently Qt4 and Qt5 are supported. Can it be the issue? OP didn't show a [mcve], so we don't know if moc_*.cpp or *.moc files are included, that are required to activate the cmake moc preprocessor. – 273K Oct 15 '22 at 19:01
  • I am using cmake 3.17. The version required for Qt 6.4 is 3.16 – Cristi Oct 15 '22 at 19:32
  • My code is actually in Github so I could give you the link but I am not sure if that is allowed on Stackoverflow. – Cristi Oct 15 '22 at 19:33
  • Links are not forbidden as an extension to a question. In your case it's 50-50 w/o a [mcve]. – 273K Oct 15 '22 at 19:40
  • @273K: "OP sets the min cmake version 3.10, for that currently Qt4 and Qt5 are supported. Can it be the issue?" - As their actual CMake is not so low, I don't think such version requirement is a problem in the given case. But I agree that the problem could be with the content of corresponding `.cpp` or `.moc` files which prevents generation of the moc files. – Tsyvarev Oct 15 '22 at 21:52
  • @Cristi: Please, add **relevant** part of your code into the question post. "Relevant" means that we don't need to **guess** whether one of the obvious reason is applied to your problem. You could take current duplicate questions as a source of such "obvious reasons". And the fact "this works on the Microsoft compiler" alone is not sufficient to discard those "obvious reasons". If you code is large, then you need to reduce it to [mcve] before posting here. – Tsyvarev Oct 15 '22 at 21:56
  • I will try to do that and add to the question. – Cristi Oct 16 '22 at 04:09
  • I have actually found the solution to this. In order to expose the function in CancelRoundCommObj and BasisCommObj classes to the test project one needs to add the MULTIROUND_EXPORT before CancelRoundCommObj and BasisCommObj definitions just like in the MultiplayerRound class. I have no idea why it works without this export in the MSVC compiler. Maybe it is the option CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS that has an effect there. – Cristi Oct 16 '22 at 18:56

0 Answers0