Search code examples
qtpropertiesforward-declarationqobjectqtscript

Qt: Q_PROPERTY with pointer and forward declaration for QtScript access


Problem

I am making a project using Q_OBJECT and Q_PROPERTY to access some objects from scripts. I have two problems:

  1. making classes that use forward declarations scriptable
  2. returning a property as pointer

Explanations

1. Why forward declaration?

The class B gets the forward declaration to A, because A needs the complete B type in the header due to the templates. B needs only an incomplete type (A*) in the header, thus the forward declaration is valid.

2. Why returning a pointer?

We cannot return a copy, as we need access to the actual object in the script. We cannot return a reference, as Qt does not allow slots to return references - their type would be ignored, they would only return void*.

Code

Complete code download on pastebin or as ZIP archive or as ZIP archive of minimal example is available, for testing / playing: I needed to split up the files for the forward declaration and for MOC. I added a Makefile to test it. Make deps: g++, moc, Qt.

Important parts

class A; // forward declaration necessary, see explanation above

class B : public QObject {
    Q_OBJECT
    Q_PROPERTY(A a READ GetA) // <-- ERROR HERE
    // ...

public slots:
    A* GetA() { 
        return mA; 
    }

private:
    A* mA;
    // ...
}

The error line in the script:

print(bObj.GetA().GetName());

Compile error

This error disappears when I comment out the Q_PROPERTY above.

tmp/B.moc.hpp:95:51: error: invalid use of incomplete type ‘struct A’
tmp/../B.hpp:10:7: error: forward declaration of ‘struct A’

Script exception

When leaving out the Q_PROPERTY and calling the GetA() method as a slot from the script, I get the following exception:

Line 7: "TypeError: cannot call GetA(): unknown return type `A*' 
        (register the type with qScriptRegisterMetaType())"

When registering A* with qRegisterMetaType<A*>("A*"); this changes to:

Line 7: "TypeError: Result of expression 'bObj.GetA().GetName' 
        [undefined] is not a function." 

That shows that GetA() does not return the A object, or somehow it returns a pointer, but the script cannot dereference it. GetA() then actually returns a QVariant(A*), can this be used somehow?

Questions:

  1. Can I somehow make a Q_PROPERTY from an incomplete type, or how could I avoid the forward declaration?
  2. Can I return a reference in a slot (maybe some tricks, e.g. a class that wraps the pointer and "overrides" the script operator., if something similar exists) or
  3. Can I somehow dereference a QVariant(A*) to A in QtScript?

Solution

    1. Your property type is A, not A*, that's why you get very reasonable error.
    2. You should use QScriptValue. Look this code. It works Ok:

      class A; // forward declaration necessary, see explanation above
      
      class B : public QObject
      {
          Q_OBJECT
      
          // Using QScriptValue, made from A instead of A to allow script work correctly with an object
          Q_PROPERTY(QScriptValue a READ GetA) 
      
      public slots:
          QScriptValue GetA() {
              //making QScriptValue from A. Type conversion in C style only due to limitation of incomplete type
              //In real app it's beter to put defenition of this slot after A's defenition
              return static_cast<QScriptEngine*>(parent())->newQObject((QObject*)mA);
          }
      
      private:
          A* mA;
          // ...
      public:
          //I decided my object will be a child of scriptEngine, but you can take a pointer to it in some other way
          B(QScriptEngine * parent);
      };
      
      class A: public QObject
      {
          Q_OBJECT
      public slots:
          QString GetName() const {return "a name";}
      public:
          A(QScriptEngine*parent):QObject(parent){}
      
      };
      
      B::B(QScriptEngine *parent):QObject(parent), mA(new A(parent)){}