Pass along a Python type to C++ that inherits C++ - cpython

I have a C++ class RuleSet with a bunch of std::shared_ptr to some rules for a game, most notably to the victory condition, a std::shared_ptr<VictoryRule>. VictoryRule is a abstract C++ type, with one function winners to return the winners of a game. (team_t is a enum, but for simplicity consider it an alias to int.)
class VictoryRule {
public:
//! #brief Check if game is won by a team.
//! #return the teamno of the team that won or team_t::no_team if nobody won.
virtual std::vector<team_t> winners(const Game &game) = 0;
//! #brief Default virtual destructor.
virtual ~VictoryRule() = default;
};
I exposed this class to python with pybind11 to be able to subclass it in python, with a trampoline class.
class PyVictoryRule : public VictoryRule {
public:
std::vector<team_t> winners(const Game &game) override {
PYBIND11_OVERRIDE_PURE(std::vector<team_t>, VictoryRule, winners, game);
}
};
And it's registered with:
pybind11::class_<VictoryRule, PyVictoryRule, std::shared_ptr<VictoryRule>>(
m, "VictoryRule")
.def(pybind11::init())
.def("winners", &VictoryRule::winners, arg("game"));
And I'v reimplemented the Rule in python
class CustomVictoryRule(VictoryRule):
def __init__(self):
VictoryRule.__init__(self)
def winners(self, game):
return [1]
I can instantiate the class in python and call the winners method without any issue.
cvr = CustomVictoryRule()
cvr.winners() # Returns [1]
But I don't want to use the rule directly, I want to store it in the C++ Ruleset.
class RuleSet {
public:
std::shared_ptr<VictoryRule> get_victory_rule();
void register_victory_rule(std::shared_ptr<VictoryRule> victory_rule);
private:
std::unordered_map<CasePawn::Type, std::shared_ptr<Rule>> per_pawn_type_rule_;
std::shared_ptr<VictoryRule> victory_rule_;
};
All methods of RuleSet are registered to pybind11 as-is.
pybind11::class_<RuleSet, std::shared_ptr<RuleSet>>(m, "RuleSet")
.def(pybind11::init())
.def("get_victory_rule", &RuleSet::get_victory_rule)
.def("register_victory_rule", &RuleSet::register_victory_rule,
arg("victory_rule"));
But when I pass along a CustomVictoryRule to RuleSet.register_victory_rule, it loses its dynamic type, and Receive an error for trying to call pure virtual function VictoryRule::winners...
ruleset = RuleSet()
ruleset.register_victory_rule(CustomVictoryRule())
ruleset.get_victory_rule().winners() #fails with RuntimeError: `Tried to call pure virtual function "VictoryRule::winners"`
What should I do so that ruleset.get_victory_rule() return a victory rule with the correct type CustomVictoryRule, so that CustomVictoryRule.winners() is called in that last line of code ?

I found the issue. Instead of doing:
ruleset.register_victory_rule(CustomVictoryRule())
I do:
cvr = CustomVictoryRule()
ruleset.register_victory_rule(cvr)
Then it worked ! So somehow python keeps alive the instance when it's stored into a local variable that's kept alive as long as the ruleset.
Thus, I need to tell pybind11 to keep temporary python objects passed as arguments. Luckily, there's a pybind11::keep_alive decorator to functions to do exactly that. So, binding RuleSet from c++ to python with the following fixes the issue !
pybind11::class_<RuleSet, std::shared_ptr<RuleSet>>(m, "RuleSet")
.def(pybind11::init())
.def("victory_rule", &RuleSet::get_victory_rule)
.def("register_victory_rule", &RuleSet::register_victory_rule,
arg("victory_rule"), keep_alive<1, 2>());

Related

Passing native shared_ptr across managed assembly

We are in the process of converting C# code to C++, but we need to do so in phases. I am at a point now where I need to instantiate several native objects from within managed code. These native objects I cannot change, and their declaration looks like this:
public class NativeA();
public class NativeB(std::shared_ptr<NativeA> obj);
Both NativeA and NativeB need to be instantiated from managed code as:
void main() {
ManagedA ObjectA = gcnew ManagedA();
ManagedB ObjectB = gcnew ManagedB(ObjectA);
}
The problem comes in with getting the shared_ptr of NativeA in the constructor of NativeB. Niether NativeA nor NativeB will be manipulated in managed code, they just need to be instantiated. Ideally, something like this:
public ref class ManagedA {
public:
ManagedA() { _object = new NativeA(); }
~ManagedA() { delete _object; }
NativeA * Get() { return _object; }
private:
NativeA *_object;
};
public ref class ManagedB {
public:
ManagedB(ManagedA^ objectA ) {
_object = new NativeB(std::make_shared<NativeA>(*objectA->Get());
}
~ManagedB() { delete _object; }
private:
NativeB *_object;
};
But, this is not allowed in c++/cli because native types are declared as private. Defining #pragma make_public(NativeA) does not solve this either.
My intent is not to work with the native objects in managed code, they just need to be instantiated, so I really don't care about trying to marshal the native pointers and deal with .NET GC if I don't have to, and I don't want to perform a copy. I just want to wrap the classes in order to pass them around.
Is there a clean and simple way to do this?
It appears that the answer was not due to a syntax or usage problem. The two managed objects were in different DLLs and could not be passed across them via .NET. Once the code was compiled in the same project, the issue was resolved.
Although the error message indicated the problem was an accessibility issue in VS 2015, and because it reported it during the link phase, I suspect the cause was because the linker would not have known about the implementation of the NativeA in NativeB without declaring an extern. Being wrapped in CLR, it surfaced as a different issue.

Do Qt properties assume objects are pointers and not members?

Let's say I have a C++ object with a member object that I expose to QML:
class X : public QObject
{
Q_OBJECT
};
class Y : public QObject
{
Q_OBJECT
Q_PROPERTY(X* x READ getX CONSTANT)
public:
X* getX(void) { return &x; }
X x;
};
Most of the time this works, but sometimes it will cause crashes. The call stack is quite lengthy but goes like this:
QScopedPointer<QObjectData, QScopedPointerDeleter<QObjectData> >::data
qGetPtrHelper<QScopedPointer<QObjectData>>
QObject::d_func
QObjectPrivate::get
QQmlNotifierEndpoint::disconnect
QQmlNotifierEndpoint::~QQmlNotifierEndpoint
QQmlJavaScriptExpressionGuard::~QQmlJavaScriptExpressionGuard
QRecyclePool<QQmlJavaScriptExpressionGuard, 1024>::Delete
QQmlJavaScriptExpressionGuard::Delete
QQmlJavaScriptExpression::GuardCapture::captureProperty
QQmlEnginePrivate::captureProperty
QV4::QObjectWrapper::getProperty
etc.
If instead I set X to be a pointer:
class Y : public QObject
{
Q_OBJECT
Q_PROPERTY(X* x READ getX CONSTANT)
public:
Y()
{ x = new X; }
X* getX(void) { return x; }
X* x;
};
The crashes go away.
Is this a known restriction of Q_PROPERTY, that if you return a pointer to a QObject, that object is assumed to be a pointer (not a member) and things like deleteLater() might be called?
This crash most likely happens because QML takes ownership of the object c your function returns, and want to delete it at some point late.
In your first example, since x is not allocated from the free store, trying to delete it will crash.
In the second example, the QML engine still tries to delete it, but it does so without problem.
The question to ask is why does the QML engine takes ownership of the object ?
The documentation states in Data Type Conversion Between QML and C++ | Data Ownership :
When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership() with QQmlEngine::CppOwnership specified.
Additionally, the QML engine respects the normal QObject parent ownership semantics of Qt C++ objects, and will not ever take ownership of a QObject instance which already has a parent.
So, in order to remedy to your problem, you could either affect a QObject parent to your x object, or explicitely declaring it having QmlEngine::CppOwnership.

Q_OBJECT "copying" - copy all properties instead

As we all know, Q_OBJECTs are instances and are not copyable.
Is there any kind of syntactic sugar to copy all static and dynamic properties of an arbitrary QObject derived class?
It seems such a nobrainer, but I can't find any reference to such - obviously implementing one myself should be quite trivial - loop over metaObject(), loop over dynamicPropertyNames(), set accordingly.
You could implement a copy helper class as follows.
/** Enable QObjects to be explicitly copyable by copying property values. */
template<class T>
class QObjectCopyHelper<T>
{
protected:
explicit QObjectCopyHelper(T *client) : m_client(client) {}
public:
T *clone(QObject *parent = 0) {
T *copy = new T(parent);
// loop over and copy properties from m_client to copy
// (both from T::staticMetaObject and dynamic ones)
return copy;
}
private:
T *m_client; // <-- I think we need this, but I might be wrong
};
Then you can use this in any QObject subclass with very low work needed to be done:
class MyClass : public QObject, public QObjectCopyHelper<MyClass>
{
Q_OBJECT
...
};
However, this still needs clone() to be called (the ugly "Java-style"). So we can additionally define a copy constructor just calling clone() and you also might think of a assign() method called within the assignment operator.
Please note that this really copies the properties only! There are a lot of other things being tracked in QObject, like the current connections. They explicitly forbid copying QObjects because it would be very difficult to define rules on how this should be done, and these rules would be the correct ones for some use cases only, while in others you want other rules...
A solution is discussed here, where they take the loop-over-the-properties approach. There seems to be no "syntactic sugar" here.

Convert a QStandardItemModel to a QVariant

I'm trying to send a QStandardItemModel-derived object to PythonQt, but I'm a little confused on how it needs to be sent. When I was using boost::python I had several controls like boost::noncopyable to ensure I wasn't recreating this object, but sharing it with python. I also had constructs to provide a boost shared pointer to python from inside python.
class Scene : public boost::enable_shared_from_this<Scene>, public QStandardItemModel
In PythonQt, however, I'm not sure what's available. The function call takes a QVariantList for all the function parameters.
QVariant PythonQt::call(PyObject* object, const QString &callable, const QVariantList &args = QVariantList))
What I'm confused about now is how to get my object to python via a QVariant. Since its derived from QStandardItemModel, I figured it would already be register
void MyObject::someFunction(QString fileName)
{
QVariant myObjectV = qVariantFromValue(this);
// send to python
...
}
But this gives me the following error:
'qt_metatype_id' : is not a member of 'QMetaTypeId<MyObject>'
I've tried registering it after I declare my class, but this throws a different error.
class MyObject : public QStandardItemModel
{
Q_OBJECT
...
};
Q_DECLARE_METATYPE(MyObject)
QStandardItemModel::QStandardItemModel(const QStandardItemModel&) is private within this context.
I actually get the error twice--once in header where I add the Q_DECLARE_METATYPE and in another header, which has a class which always derives from QStandardItemModel but is otherwise unrelated.
Is Q_DECLARE_METATYPE even the correct way to go about converting this object to a QVariant?
BOOST_PYTHON_MODULE(scene)
{
class_("Scene");
}
Yes, by default, QVariant can take one of te following types - http://doc.qt.io/qt-4.8/qvariant.html#Type-enum - and they are not enough for your task. You should declare additional types by yourself via qmetatype system. Thus you shoud call qRegisterMetaType() function.

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:
making classes that use forward declarations scriptable
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:
Can I somehow make a Q_PROPERTY from an incomplete type, or how could I avoid the forward declaration?
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
Can I somehow dereference a QVariant(A*) to A in QtScript?
Your property type is A, not A*, that's why you get very reasonable error.
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)){}

Resources