简体   繁体   中英

How to write wrapper classes for mapping Qt Signals to C# events (through C++/CLI)

Calling C++/Qt classes through C++/CLI wrapper is a like a walk in the park.

But I'm stuck mapping C++/Qt signals to C# events.

I tried to combine some available how-tos/answers but did not get any working result:

The problem here is, that these how-tos/answers are quite old. I am currently working with Qt5.5 (soon 5.6) and .NET 4.6. I tried to adapt everything to current state of the art but may have failed.

It may be, that I can't see the forest because of too much trees, so I would like to ask for a working example which accomplishes the task with current framework versions, so that I can spot the differences and learn from the mistakes.

[edit] You can checkout this github repo, for this source. The parts for QtSignal to C#Event are commented out to have this code in a working state.

Github repo: https://github.com/qwc/QtSignalToCSharpEvent

For those who still want to read everything without playing around... read on... Here my current non-working code:

So I have a class in pure Qt5

#ifndef PUREQT_H
#define PUREQT_H

#include <QObject>
#include <QString>
#include "PureQt_global.h"

class PUREQTSHARED_EXPORT PureQt : public QObject    {
    Q_OBJECT

public:
    PureQt(QString name);
    ~PureQt(){}
    QString getSomeVar();
    void someFunc(const QString & string);
public slots:
signals:
    void someFuncWasCalled(const QString &string);
private:
    QString someVar;
};

#endif // PUREQT_H

With the following rather simple implementation:

#include "PureQt.h"

PureQt::PureQt(QString name)    {
    this->someVar = "ctor("+name+")";
}

QString PureQt::getSomeVar()    {
    return this->someVar;
}

void PureQt::someFunc(const QString &string)    {
    this->someVar += "someFunc("+string+")";
    emit someFuncWasCalled(this->someVar);
}

Then I've implemented a managed wrapper with C++/CLI to be able to call the unmanaged code from C#. Be aware that I've already tried to add code to get signal to event management.

#pragma once
#include "conversion.h"
#include "../pureqt/PureQt.h"
#include "SignalProxy.h"

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;

namespace ManagedCppQtSpace {

    // different variants... from tinkering around.
    delegate void someFuncWasCalled(String^);
    delegate void someFuncWasCalledU(QString str);

    [StructLayoutAttribute(LayoutKind::Sequential)]
    public ref struct DelegateWrapper {
        [MarshalAsAttribute(UnmanagedType::FunctionPtr)]
        someFuncWasCalledU^ delegate;
    };

    public ref class ManagedCppQt
    {
    public:    
        ManagedCppQt(String^ name){
            pureQtObject = new PureQt(StringToQString(name));
            proxy = new SignalProxy(pureQtObject);
            wrapper = gcnew DelegateWrapper();
            wrapper->delegate = gcnew someFuncWasCalledU(this, ManagedCppQt::signalCallback);
            signalCallbackProxy callbackproxy;
            Marshal::StructureToPtr(wrapper, callbackproxy, false); // currently im stuck here with a compile error, but the problem may lie somewhere else...
            proxy->setCallback(callbackproxy);
        };
        ~ManagedCppQt(){
            delete pureQtObject;
        };

        event someFuncWasCalled ^ someFuncCalled;

        void someFunc(String^ string){
            pureQtObject->someFunc(StringToQString(string));
        };

        String^ getSomeString() {
            return QStringToString(pureQtObject->getSomeVar());
        }

        void signalCallback(QString str) {
            someFuncCalled(QStringToString(str));
        }

        DelegateWrapper ^ wrapper;

    private:
        PureQt * pureQtObject;
        SignalProxy * proxy;
    };
}

So to link signal and slot handling from Qt with a callback which is able to raise an event in managed code some will need a proxy class when there's no option to change the basis code (because it's also used in other unmanaged C++ projects).

#ifndef SIGNALPROXY_H
#define SIGNALPROXY_H

#include <QObject>
#include "../pureqt/PureQt.h"

typedef void (*signalCallbackProxy) (QString str);

class SignalProxy : public QObject
{
    Q_OBJECT

public:
    explicit SignalProxy(PureQt* pqt);
    ~SignalProxy();
    void setCallback(signalCallbackProxy callback);
signals:
public slots:
    void someFuncSlot(QString str);
private:
    PureQt* pureQt;
    signalCallbackProxy scallback;
};

#endif // SIGNALPROXY_H

With implementation:

#include "SignalProxy.h"

SignalProxy::SignalProxy(PureQt* pqt){
    pureQt = pqt;
    this->connect(pureQt, SIGNAL(PureQt::someFuncWasCalled(QString)), this, SLOT(someFuncSlot(QString)));
}

SignalProxy::~SignalProxy()
{}

void SignalProxy::setCallback(signalCallbackProxy callback){
    this->scallback = callback;
}

void SignalProxy::someFuncSlot(QString str){
    if(this->scallback != NULL)
        this->scallback(str);
}

So. Now, how to correctly link these two worlds, Qt signals -> managed .NET events?

I've also tried some simple approaches, which lead to compile errors, like:

QObject::connect(pureQtObject, &PureQt::someFuncWasCalled, &MangagedCppQtSpace::ManagedCppQt::signalCallback);

instead of the proxy class, or with a lambda function:

QObject::connect(pureQtObject, &PureQt::someFuncWasCalled, [] (QString str) {
    signalCallback(str);// or ManagedCppQt::signalCallback, but for this the method has to be static, and it isn't possible to raise events from static methods...
}

Problem here is mixing Qt with C++ CLI. To have functional signal and slots you Qt needs process header files to generate own meta data. Problem is that tool will be unable to understand C++CLI features.

To overcome this problem first you have to do fallback to C++ interfaces and there perform safely C++ CLI operations.

So you need extra class like this which doesn't know .net and creates bridge to standard C++:

class PureQtReceiverDelegate { // this should go to separate header file
   virtual void NotifySomeFunc(const char *utf8) = 0;
};

class PureQtReceiver : public QObject {
    Q_OBJECT

public:
    PureQtReceiver(PureQtReceiverDelegate *delegate, PureQt *parent)
    : QObject(parent)
    , mDelegate(delegate)
    {
        bool ok = connect(parent, SIGNAL(PureQt::someFuncWasCalled(QString)),
                this, SLOT(someFuncReceiver(QString)));
        Q_ASSERT(ok);
    }

public slots:
    void someFuncReceiver(const QString & string)
    {
        delegate->NotifySomeFunc(string.toUtf8().data());
    }
private:
    PureQtReceiverDelegate *delegate;
};

Now your C++CLI class should implement this PureQtReceiverDelegate and there convert string to .net version and post notification.

Note you can/should forward declare Qt specific classes in C++CLI header file.


Above solution is good if you are using Qt4 od don't to use C++11.

If you are using Qt 5 and have C++11 available than there is more handy solution: you can use lambda expression in when making a connection to a QObject. So your ManagedCppQt can look like this:

header:

#include "../pureqt/PureQt.h"

using namespace ManagedCppQtSpace;

ManagedCppQt:ManagedCppQt(String^ name) {
    pureQtObject = new PureQt(QStringFromString(name));
    QObject::connect(pureQtObject, &PureQt::someFuncWasCalled,
                     [this](const QString &string){
                         if (this->someFuncCalled) {
                              String^ s = StringFromQString(string);
                              this->someFuncCalled(s);
                         }
                     });
}

ManagedCppQt::~ManagedCppQt(){
    delete pureQtObject;
}

in cpp:

 #include "../pureqt/PureQt.h" using namespace ManagedCppQtSpace; ManagedCppQt:ManagedCppQt(String^ name) { pureQtObject = new PureQt(QStringFromString(name)); QObject::connect(pureQtObject, &PureQt::someFuncWasCalled, [this](const QString &string){ if (this->someFuncCalled) { String^ s = StringFromQString(string); this->someFuncCalled(s); } }); } ManagedCppQt::~ManagedCppQt(){ delete pureQtObject; } 

This is much easier, faster and easier to maintain.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM