5

I'm trying to simplify (i.e. get rid of loads of boilerplate code) the creation of QObject wrapper classes that forward property access of other QObject derived classes.

To start small, I'm just trying it with one property:

// Sy_test.h - The wrapped class
class Sy_test : public QObject
{
    Q_OBJECT
    Q_PROPERTY( bool prop READ getProp WRITE setProp NOTIFY propChanged )

public:
    Sy_test( QObject* parent = nullptr ) :
        QObject{ parent },
        prop_{ false } {}

    bool getProp() const { return prop_; }

public slots:
    void setProp( bool value )
    {
        if ( value != prop_ ) {
            prop_ = value;
            emit propChanged( prop_ );
        }
    }

signals:
    void propChanged( bool value );

private:
    bool prop_;
};

// Sy_proxy.h - The wrapper generator
#define SY_PROXYPROPERTY( Type, Name, Getter, Setter, Notifier )\
private:\
    Q_PROPERTY( Type Name READ Getter WRITE Setter NOTIFY Notifier )\
\
public:\
    Type Getter() const { return target_->Getter(); }\
\
public slots:\
    void Setter( Type value ) { target_->Setter( value ); }\
\
signals:\
    void Notifier( Type value );\
\
private:\
    void setConnection()\
    {\
        connect( target_, &std::remove_pointer< decltype( target_ ) >::type::Notifier,\
                 this,    &std::remove_pointer< decltype( this    ) >::type::Notifier );\
    }

#define SY_PROXY( ProxyName, TargetType, Prop1 )\
class ProxyName : public QObject\
{\
    Q_OBJECT \
    Prop1 \
\
public:\
    ProxyName( TargetType* target ) :\
        target_{ target }\
    {\
        setConnection();\
    }\
\
    virtual ~ProxyName() {}\
\
private:\
    TargetType* target_;\
};

// This should create a Sy_test wrapper class called Sy_testProxy
SY_PROXY( Sy_testProxy,
          Sy_test,
          SY_PROXYPROPERTY( bool, prop, getProp, setProp, propChanged ) )

So the SY_PROXY macro should create a class called Sy_testProxy that carries a copy of the Sy_test::prop property with implementations that just forward requests/signals.

And it almost does. Looking at the post-preprocessor output (I'm using g++, so the .ii files), I can see the Sy_testProxy class is built and it's of the same form as the Sy_test class. However, I get an error:

../CppTest/Sy_proxy.h:47: Error: NOTIFY signal 'propChanged' of property 'prop' does not exist in class Sy_testProxy.
make: *** [moc_Sy_proxy.cpp] Error 1

So it looks like the moc is failing to parse my macro magic; although I'm not sure where as clearly the SY_PROXY macro is present (the error is coming from a class called Sy_testProxy), and SY_PROXYPROPERTY must be valid too (as the moc must have read the Q_PROPERTY macro from it). Can anyone see where I've gone wrong?

For the record: I hate macros like everyone else, but I've fell into using them due to the moc's aversion to templates and QObject virtual inheritance. This investigation was triggered because I had a collection of instances performing heavy calculations in a separate thread, but they drive QML representations. However QML does not allow connections/property bindings to objects outside of the main thread, so I've been forced into creating proxy object that live in the main thread. If anyone has a better idea, I'm very open to them!

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • I guess it doesn't change anything, but you could try `Q_SIGNAL` instead of `signals`... AFAIK moc doesn't expand macros very well. It does to some extent, but since it doesn't stick to the standard, I always avoided them in headers of QObject derived classes. – leemes Sep 16 '15 at 15:12
  • This seems like an X-Y problem to me. The first paragraph of your question should be much more elaborate. Please explain what exactly are you trying to do. What is your class hierarchy, what objects have what properties, where are the template classes, and why do you need all this. The "for the record" paragraph is a part of that, but still doesn't provide enough information. – Kuba hasn't forgotten Monica Sep 16 '15 at 16:44
  • I'm also not sure what you mean by "QML does not allow connections/property bindings to objects outside of the main thread" – Kuba hasn't forgotten Monica Sep 16 '15 at 17:54
  • @KubaOber Attempting to connect/bind/invoke from QML to a C++ `QObject` running in a non-main thread results in "Illegal attempt to connect to ____ that is in a different thread than the QML engine". – cmannett85 Sep 16 '15 at 18:46
  • @KubaOber I have `QObject` derived types in multiple threads, many of which have QML representations (i.e. QML types that can display the data these instances carry), however I can't bind to any of their properties due to the above flaw - so I create proxy objects in the main thread which allows the two to communicate. – cmannett85 Sep 16 '15 at 18:56
  • @cmannett85 "Illegal attempt to connect to ____ that is in a different thread than the QML engine" Admittedly, that's a stupid shortcoming. How did I not run into that yet I'm not sure, I guess I was lucky. Live and learn. Thanks for making me aware of that. – Kuba hasn't forgotten Monica Sep 17 '15 at 01:20
  • @leemes `Q_SIGNAL` did indeed work, make it an answer and I'll accept it. – cmannett85 Sep 17 '15 at 14:28
  • @cmannett85 Glad I could help. Sorry that I wrote the full answer so late, but I had no time before. – leemes Sep 26 '15 at 22:37

3 Answers3

5

moc doesn't like macros very well. It expands them to some degree, but it fails when they get complicated¹.

You can try to replace signals: with public:² (i.e. manually expanding the signals macro), andtell moc that you want the function to be a signal by putting Q_SIGNAL in front of the function declaration.

Replace

signals:\
    void Notifier( Type value );\

with

public:\
    Q_SIGNAL void Notifier( Type value );\

¹: for some definition of complicated... I don't know when it fails, but I ran into some different problems in the past. From my experience my guess is that moc has problems when a macro body contains another macro, like signals in your example. But this is just a guess - maybe the kind of macros moc fails at is something else.

²: Before Qt 5, that used to be protected.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • Thanks for this! I was wondering how I could use macros to generate signals, and the obvious way to do it simply doesn't work. – Bri Bri May 16 '16 at 17:01
0

The vagaries of moc aside, your wrapper is not thread-safe. The property getter is not invoked from the correct thread. So I don't see any point of the wrapper. You might as well use the wrapped class directly from QML, not the wrapper.

To be thread-safe, your wrapper should be caching the wrapped property's value, so that the reads always happen from the local copy.

At that point you might as well write a fully dynamic wrapper that thread-safely forwards all properties from the wrapped object. Using the metaobject system you can generate everything on the fly - the copies of the property values, etc. As far as properties go, you can copy the entire binary descriptor as your wrapper pretends to have the same properties.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • `Sy_test` is just example code to experiment with the macros, non of this threadsafe, nor was it meant to be. I've already written proxies that fully forward all properties, `Q_INVOKABLE` methods, and public slots - but the whole point of this exercise was to reduce all the associated boiler plate that comes with that. – cmannett85 Sep 16 '15 at 18:50
  • @cmannett85 You need one proxy class that can forward from any other class. A universal one :) OK, challenge accepted. – Kuba hasn't forgotten Monica Sep 17 '15 at 01:18
0

There is an excellent piece of code, you can google for qmltricks, it has everything you need as a good start.

You will need just one header. There is a room for extend to support read only properties or custom getters/setters.. but I would suggest to hae a look. I cant find an original.page now, saw a presentation on last Qt Summit, you probably can check qt site for hands-on materials.

Below a link on a github, there are several versions around available.

https://github.com/Cavewhere/lib-qt-qml-tricks/blob/master/include/QQmlHelpers

evilruff
  • 3,947
  • 1
  • 15
  • 27