Added support for interacting with QObjects.

This commit is contained in:
Paul Knopf 2019-04-20 23:05:45 -04:00
parent e7a228a36f
commit a0afc0506f
35 changed files with 2052 additions and 27 deletions

View file

@ -50,6 +50,7 @@ enum NetVariantTypeEnum {
NetVariantTypeEnum_DateTime,
NetVariantTypeEnum_Object,
NetVariantTypeEnum_JSValue,
NetVariantTypeEnum_QObject
};

View file

@ -0,0 +1,344 @@
#include <QmlNet/qml/NetQObject.h>
#include <QmlNet/qml/NetQObjectSignalConnection.h>
#include <QmlNet/qml/NetQObjectArg.h>
#include <QmlNet/types/NetDelegate.h>
#include <QmlNet/types/Callbacks.h>
#include <QMetaMethod>
#include <QDebug>
NetQObject::NetQObject(QObject* qObject, bool ownsObject) :
_qObject(qObject),
_ownsObject(ownsObject)
{
}
NetQObject::~NetQObject()
{
if(_ownsObject) {
_qObject->deleteLater();
}
}
QObject* NetQObject::getQObject()
{
return _qObject;
}
QSharedPointer<NetVariant> NetQObject::getProperty(QString propertyName, bool* wasSuccess)
{
QSharedPointer<NetVariant> netVariant;
QByteArray propertyNameLocal = propertyName.toLocal8Bit();
if(_qObject->metaObject()->indexOfProperty(propertyNameLocal.data()) == -1) {
if(wasSuccess) {
*wasSuccess = false;
}
return nullptr;
}
QVariant result = _qObject->property(propertyNameLocal.data());
if(!result.isNull()) {
netVariant = QSharedPointer<NetVariant>(new NetVariant());
NetVariant::fromQVariant(&result, netVariant);
}
if(wasSuccess) {
*wasSuccess = true;
}
return netVariant;
}
void NetQObject::setProperty(QString propertyName, QSharedPointer<NetVariant> value, bool* wasSuccess)
{
bool result;
if(value == nullptr) {
result = _qObject->setProperty(propertyName.toLocal8Bit().data(), QVariant());
} else {
result = _qObject->setProperty(propertyName.toLocal8Bit().data(), value->toQVariant());
}
if(wasSuccess) {
*wasSuccess = result;
}
}
QSharedPointer<NetVariant> NetQObject::invokeMethod(QString methodName, QSharedPointer<NetVariantList> parameters, bool* wasSuccess)
{
int parameterCount = 0;
if(parameters != nullptr) {
parameterCount = parameters->count();
}
// Find the best method
int methodIndex = -1;
QMetaMethod method;
for(int x = 0; x < _qObject->metaObject()->methodCount(); x++) {
method = _qObject->metaObject()->method(x);
if(method.methodType() == QMetaMethod::Slot || method.methodType() == QMetaMethod::Method) {
if(methodName.compare(method.name()) == 0) {
// make sure number of parameters match
if(method.parameterCount() == parameterCount) {
methodIndex = x;
break;
}
}
}
}
if(methodIndex == -1) {
if(wasSuccess) {
*wasSuccess = false;
}
return nullptr;
}
if(parameterCount > 10) {
qWarning() << "Attempting to invoke a method with too many parameters: current: " << parameters->count() << " expected: <=10";
if(wasSuccess) {
*wasSuccess = false;
}
return nullptr;
}
NetQObjectArg returnValue(method.returnType());
NetQObjectArg val0;
NetQObjectArg val1;
NetQObjectArg val2;
NetQObjectArg val3;
NetQObjectArg val4;
NetQObjectArg val5;
NetQObjectArg val6;
NetQObjectArg val7;
NetQObjectArg val8;
NetQObjectArg val9;
if(parameterCount >= 1) {
val0 = NetQObjectArg(method.parameterType(0), parameters->get(0));
}
if(parameterCount >= 2) {
val0 = NetQObjectArg(method.parameterType(1), parameters->get(1));
}
if(parameterCount >= 3) {
val0 = NetQObjectArg(method.parameterType(2), parameters->get(2));
}
if(parameterCount >= 4) {
val0 = NetQObjectArg(method.parameterType(3), parameters->get(3));
}
if(parameterCount >= 5) {
val0 = NetQObjectArg(method.parameterType(4), parameters->get(4));
}
if(parameterCount >= 6) {
val0 = NetQObjectArg(method.parameterType(5), parameters->get(5));
}
if(parameterCount >= 7) {
val0 = NetQObjectArg(method.parameterType(6), parameters->get(6));
}
if(parameterCount >= 8) {
val0 = NetQObjectArg(method.parameterType(7), parameters->get(7));
}
if(parameterCount >= 9) {
val0 = NetQObjectArg(method.parameterType(8), parameters->get(8));
}
if(parameterCount >= 10) {
val0 = NetQObjectArg(method.parameterType(9), parameters->get(9));
}
if(!method.invoke(_qObject,
Qt::DirectConnection,
returnValue.genericReturnArguemnet(),
val0.genericArgument(),
val1.genericArgument(),
val2.genericArgument(),
val3.genericArgument(),
val4.genericArgument(),
val5.genericArgument(),
val6.genericArgument(),
val7.genericArgument(),
val8.genericArgument(),
val9.genericArgument())) {
if(wasSuccess) {
*wasSuccess = false;
}
return nullptr;
}
if(wasSuccess) {
*wasSuccess = true;
}
returnValue.unpack();
return returnValue.getNetVariant();
}
QSharedPointer<NetQObjectSignalConnection> NetQObject::attachSignal(QString signalName, QSharedPointer<NetReference> delegate, bool* wasSucessful)
{
int signalMethodIndex = -1;
QMetaMethod signalMethod;
for(int x = 0; x <_qObject->metaObject()->methodCount(); x++) {
signalMethod = _qObject->metaObject()->method(x);
if(signalMethod.methodType() == QMetaMethod::Signal) {
if(signalName.compare(signalMethod.name()) == 0) {
signalMethodIndex = x;
break;
}
}
}
// If signal not found, dump the registered signals for debugging.
if(signalMethodIndex < 0) {
if(wasSucessful) {
*wasSucessful = false;
}
return nullptr;
}
QSharedPointer<NetQObjectSignalConnection> signalConnection = QSharedPointer<NetQObjectSignalConnection>(new NetQObjectSignalConnection(delegate, _qObject));
QMetaObject::Connection connection = QObject::connect(_qObject,
signalMethod,
signalConnection.data(),
signalConnection->getSignalHandler());
if(!connection) {
if(wasSucessful) {
*wasSucessful = false;
}
return nullptr;
}
if(wasSucessful) {
*wasSucessful = true;
}
return signalConnection;
}
QSharedPointer<NetQObjectSignalConnection> NetQObject::attachNotifySignal(QString propertyName, QSharedPointer<NetReference> delegate, bool* wasSuccessful)
{
int propertyIndex = _qObject->metaObject()->indexOfProperty(propertyName.toLocal8Bit().data());
if(propertyIndex == -1) {
if(wasSuccessful) {
*wasSuccessful = false;
}
return nullptr;
}
QMetaProperty property = _qObject->metaObject()->property(propertyIndex);
if(property.notifySignalIndex() == -1) {
if(wasSuccessful) {
*wasSuccessful = false;
}
return nullptr;
}
QSharedPointer<NetQObjectSignalConnection> signalConnection = QSharedPointer<NetQObjectSignalConnection>(new NetQObjectSignalConnection(delegate, _qObject));
QMetaObject::Connection connection = QObject::connect(_qObject,
property.notifySignal(),
signalConnection.data(),
signalConnection->getSignalHandler());
if(!connection) {
if(wasSuccessful) {
*wasSuccessful = false;
}
return nullptr;
}
if(wasSuccessful) {
*wasSuccessful = true;
}
return signalConnection;
}
extern "C" {
Q_DECL_EXPORT void net_qobject_destroy(NetQObjectContainer* qObjectContainer)
{
delete qObjectContainer;
}
Q_DECL_EXPORT NetVariantContainer* net_qobject_getProperty(NetQObjectContainer* qObjectContainer, LPWCSTR propertyName, uchar* result)
{
bool wasSuccesful = false;
auto value = qObjectContainer->qObject->getProperty(QString::fromUtf16(propertyName), &wasSuccesful);
if(wasSuccesful) {
*result = 1;
} else {
*result = 0;
}
if(value == nullptr) {
return nullptr;
}
return new NetVariantContainer{ value };
}
Q_DECL_EXPORT void net_qobject_setProperty(NetQObjectContainer* qObjectContainer, LPWCSTR propertyName, NetVariantContainer* netVariantContainer, uchar* result)
{
bool wasSuccesful = false;
if(netVariantContainer == nullptr) {
qObjectContainer->qObject->setProperty(QString::fromUtf16(propertyName), nullptr, &wasSuccesful);
} else {
qObjectContainer->qObject->setProperty(QString::fromUtf16(propertyName), netVariantContainer->variant, &wasSuccesful);
}
if(wasSuccesful) {
*result = 1;
} else {
*result = 0;
}
}
Q_DECL_EXPORT NetVariantContainer* net_qobject_invokeMethod(NetQObjectContainer* qObjectContainer, LPWCSTR methodName, NetVariantListContainer* parametersContainer, uchar* result)
{
QSharedPointer<NetVariantList> parameters = nullptr;
if(parametersContainer != nullptr) {
parameters = parametersContainer->list;
}
bool wasSuccesful = false;
auto value = qObjectContainer->qObject->invokeMethod(QString::fromUtf16(methodName), parameters, &wasSuccesful);
if(wasSuccesful) {
*result = 1;
} else {
*result = 0;
}
if(value == nullptr) {
return nullptr;
}
return new NetVariantContainer { value };
}
Q_DECL_EXPORT NetQObjectSignalConnectionContainer* net_qobject_attachSignal(NetQObjectContainer* qObjectContainer, LPWCSTR signalName, NetReferenceContainer* delegate, uchar* result)
{
bool wasSuccesful = false;
auto signalConnection = qObjectContainer->qObject->attachSignal(QString::fromUtf16(signalName), delegate->instance, &wasSuccesful);
if(wasSuccesful) {
*result = 1;
} else {
*result = 0;
}
if(signalConnection == nullptr) {
return nullptr;
}
return new NetQObjectSignalConnectionContainer { signalConnection };
}
Q_DECL_EXPORT NetQObjectSignalConnectionContainer* net_qobject_attachNotifySignal(NetQObjectContainer* qObjectContainer, LPWCSTR propertyName, NetReferenceContainer* delegate, uchar* result)
{
bool wasSuccesful = false;
auto signalConnection = qObjectContainer->qObject->attachNotifySignal(QString::fromUtf16(propertyName), delegate->instance, &wasSuccesful);
if(wasSuccesful) {
*result = 1;
} else {
*result = 0;
}
if(signalConnection == nullptr) {
return nullptr;
}
return new NetQObjectSignalConnectionContainer { signalConnection };
}
Q_DECL_EXPORT void net_qobject_signal_handler_destroy(NetQObjectSignalConnectionContainer* signalContainer)
{
delete signalContainer;
}
}

View file

@ -0,0 +1,32 @@
#ifndef NETQOBJECT_H
#define NETQOBJECT_H
#include <QmlNet.h>
#include <QSharedPointer>
class NetVariant;
class NetVariantList;
class NetQObjectSignalConnection;
class NetReference;
class NetQObject
{
public:
NetQObject(QObject* qObject, bool ownsObject = false);
~NetQObject();
QObject* getQObject();
QSharedPointer<NetVariant> getProperty(QString propertyName, bool* wasSuccess);
void setProperty(QString propertyName, QSharedPointer<NetVariant> value, bool* wasSuccess);
QSharedPointer<NetVariant> invokeMethod(QString methodName, QSharedPointer<NetVariantList> parameters, bool* wasSuccess);
QSharedPointer<NetQObjectSignalConnection> attachSignal(QString signalName, QSharedPointer<NetReference> delegate, bool* wasSuccess);
QSharedPointer<NetQObjectSignalConnection> attachNotifySignal(QString propertyName, QSharedPointer<NetReference> delegate, bool* wasSuccess);
private:
QObject* _qObject;
bool _ownsObject;
};
struct NetQObjectContainer {
QSharedPointer<NetQObject> qObject;
};
#endif // NETQOBJECT_H

View file

@ -0,0 +1,139 @@
#include <QmlNet/qml/NetQObjectArg.h>
#include <QmlNet/qml/NetVariant.h>
#include <QDebug>
NetQObjectArg::NetQObjectArg() :
_metaTypeId(QMetaType::Void),
_data(nullptr)
{
pack();
}
NetQObjectArg::NetQObjectArg(const int metaTypeId, QSharedPointer<NetVariant> netVariant) :
_metaTypeId(metaTypeId),
_data(nullptr),
_netVariant(netVariant)
{
pack();
}
NetQObjectArg::~NetQObjectArg()
{
}
QGenericArgument NetQObjectArg::genericArgument()
{
if(_metaTypeId == QMetaType::Void) {
return QGenericArgument();
}
if(_metaTypeId == QMetaType::QVariant) {
return QGenericArgument("QVariant", &_variant);
}
return QGenericArgument(_variant.typeName(), _variant.data());
}
QGenericReturnArgument NetQObjectArg::genericReturnArguemnet()
{
if(_metaTypeId == QMetaType::Void) {
return QGenericReturnArgument();
}
if(_metaTypeId == QMetaType::QVariant) {
return QGenericReturnArgument("QVariant", &_variant);
}
return QGenericReturnArgument(_variant.typeName(), _variant.data());
}
QSharedPointer<NetVariant> NetQObjectArg::getNetVariant()
{
return _netVariant;
}
void NetQObjectArg::pack()
{
if(_netVariant == nullptr) {
switch(_metaTypeId) {
case QMetaType::Void:
break;
case QMetaType::QVariant:
_variant = QVariant();
break;
default:
_variant = QVariant(_metaTypeId, nullptr);
break;
}
return;
}
switch(_metaTypeId) {
case QMetaType::Void:
break;
case QMetaType::QVariant:
_variant = _netVariant->toQVariant();
break;
case QMetaType::Bool:
_variant = QVariant::fromValue(_netVariant->getBool());
break;
case QMetaType::QChar:
_variant = QVariant::fromValue(_netVariant->getChar());
break;
case qMetaTypeId<qint32>():
_variant = QVariant::fromValue(_netVariant->getInt());
break;
case qMetaTypeId<quint32>():
_variant = QVariant::fromValue(_netVariant->getUInt());
break;
case qMetaTypeId<qint64>():
_variant = QVariant::fromValue(_netVariant->getLong());
break;
case qMetaTypeId<quint64>():
_variant = QVariant::fromValue(_netVariant->getULong());
break;
case QMetaType::Long:
_variant = QVariant::fromValue<long>(static_cast<long>(_netVariant->getLong()));
break;
case QMetaType::ULong:
_variant = QVariant::fromValue<ulong>(static_cast<ulong>(_netVariant->getULong()));
break;
case QMetaType::Float:
_variant = QVariant::fromValue(_netVariant->getFloat());
break;
case QMetaType::Double:
_variant = QVariant::fromValue(_netVariant->getDouble());
break;
case QMetaType::QString:
_variant = QVariant::fromValue(_netVariant->getString());
break;
case QMetaType::QDateTime:
_variant = QVariant::fromValue(_netVariant->getDateTime());
break;
case QMetaType::QObjectStar:
switch(_netVariant->getVariantType()) {
case NetVariantTypeEnum_Invalid:
_variant = QVariant::fromValue<QObject*>(nullptr);
break;
case NetVariantTypeEnum_Object:
case NetVariantTypeEnum_QObject:
_variant = _netVariant->toQVariant();
break;
default:
qWarning() << "Unabled to convert " << _netVariant->getVariantType() << " to QObject*";
break;
}
break;
default:
qWarning() << "Unsupported type: " << QMetaType::typeName(_metaTypeId);
_variant = QVariant(_metaTypeId, nullptr);
break;
}
}
void NetQObjectArg::unpack()
{
if(_metaTypeId == QMetaType::Void) {
return;
}
if(_netVariant == nullptr) {
_netVariant = QSharedPointer<NetVariant>(new NetVariant());
}
NetVariant::fromQVariant(&_variant, _netVariant);
}

View file

@ -0,0 +1,28 @@
#ifndef NETQOBJECTARG_H
#define NETQOBJECTARG_H
#include <QMetaMethod>
#include <QSharedPointer>
class NetVariant;
class NetQObjectArg
{
public:
NetQObjectArg();
NetQObjectArg(int metaTypeId, QSharedPointer<NetVariant> netVariant = nullptr);
~NetQObjectArg();
QGenericArgument genericArgument();
QGenericReturnArgument genericReturnArguemnet();
QSharedPointer<NetVariant> getNetVariant();
void pack();
void unpack();
private:
int _metaTypeId;
void* _data;
int test;
QVariant _variant;
QSharedPointer<NetVariant> _netVariant;
};
#endif // NETQOBJECTARG_H

View file

@ -0,0 +1,65 @@
#include <QmlNet/qml/NetQObjectSignalConnection.h>
#include <QmlNet/qml/NetVariant.h>
#include <QmlNet/qml/NetVariantList.h>
#include <QmlNet/types/Callbacks.h>
#include <QMetaMethod>
NetQObjectSignalConnectionBase::NetQObjectSignalConnectionBase()
{
}
NetQObjectSignalConnectionBase::~NetQObjectSignalConnectionBase()
{
}
QMetaMethod NetQObjectSignalConnectionBase::getSignalHandler()
{
return metaObject()->method(metaObject()->methodOffset());
}
void NetQObjectSignalConnectionBase::signalRaised()
{
// Dummy, handled in NetQObjectSignalConnection::qt_metacall()
}
NetQObjectSignalConnection::NetQObjectSignalConnection(QSharedPointer<NetReference> delegate,
QObject* qObject)
: _delegate(delegate),
_qObject(qObject)
{
}
NetQObjectSignalConnection::~NetQObjectSignalConnection()
{
}
int NetQObjectSignalConnection::qt_metacall(QMetaObject::Call c, int id, void** a)
{
if(c == QMetaObject::InvokeMetaMethod) {
int offset = this->metaObject()->methodOffset();
if(id < offset) {
return QObject::qt_metacall(c, id, a);
}
if(this->metaObject()->indexOfSlot("signalRaised()") == id) {
QMetaMethod signal = _qObject->metaObject()->method(senderSignalIndex());
// Convert signal args to QVariantList
QSharedPointer<NetVariantList> netParameters = QSharedPointer<NetVariantList>(new NetVariantList());
for (int i = 0; i < signal.parameterCount(); ++i) {
QVariant arg = QVariant(signal.parameterType(i), a[i+1]);
netParameters->add(NetVariant::fromQVariant(&arg));
}
QmlNet::invokeDelegate(_delegate, netParameters);
return -1;
}
}
return id;
}

View file

@ -0,0 +1,35 @@
#ifndef NETQOBJECTSIGNALCONNECTION_H
#define NETQOBJECTSIGNALCONNECTION_H
#include <QObject>
#include <QSharedPointer>
class NetReference;
class NetQObjectSignalConnectionBase : public QObject
{
Q_OBJECT
public:
NetQObjectSignalConnectionBase();
~NetQObjectSignalConnectionBase();
QMetaMethod getSignalHandler();
public slots:
void signalRaised();
};
class NetQObjectSignalConnection : public NetQObjectSignalConnectionBase
{
public:
NetQObjectSignalConnection(QSharedPointer<NetReference> delegate, QObject* qObject);
~NetQObjectSignalConnection() override;
int qt_metacall(QMetaObject::Call c, int id, void** a) override;
private:
QSharedPointer<NetReference> _delegate;
QObject* _qObject;
};
struct NetQObjectSignalConnectionContainer {
QSharedPointer<NetQObjectSignalConnection> signalConnection;
};
#endif // NETQOBJECTSIGNALCONNECTION_H

View file

@ -2,12 +2,176 @@
#include <QQmlComponent>
#include <QDebug>
TestQObject::TestQObject()
: QObject(nullptr),
_writeOnly(0),
_readAndWrite(0),
_propWithSignal(0)
{
}
TestQObject::~TestQObject()
{
}
int TestQObject::getReadOnly()
{
return 3;
}
void TestQObject::setWriteOnly(int value)
{
_writeOnly = value;
}
int TestQObject::getReadAndWrite()
{
return _readAndWrite;
}
void TestQObject::setReadAndWrite(int value)
{
_readAndWrite = value;
}
int TestQObject::getPropWithSignal()
{
return _propWithSignal;
}
void TestQObject::setPropWithSignal(int value)
{
if(value == _propWithSignal) {
return;
}
_propWithSignal = value;
emit propWithSignalChanged(value);
}
QVariant TestQObject::getVariantProperty()
{
return _variantValue;
}
void TestQObject::setVariantProperty(QVariant value)
{
_variantValue = value;
}
void TestQObject::testSlot()
{
emit testSignal();
}
void TestQObject::testSlotWithArg(int arg)
{
emit testSignalWithArg(arg);
}
QVariant TestQObject::testVariantReturn()
{
return getVariantProperty();
}
bool TestQObject::testSlotBool(bool value)
{
emit testSignalBool(value);
return value;
}
QChar TestQObject::testSlotChar(QChar value)
{
emit testSignalChar(value);
return value;
}
int TestQObject::testSlotInt(int value)
{
emit testSignalInt(value);
return value;
}
uint TestQObject::testSlotUInt(uint value)
{
emit testSignalUInt(value);
return value;
}
long TestQObject::testSlotLong(long value)
{
emit testSignalLong(value);
return value;
}
ulong TestQObject::testSlotULong(ulong value)
{
emit testSignalULong(value);
return value;
}
float TestQObject::testSlotFloat(float value)
{
emit testSignalFloat(value);
return value;
}
double TestQObject::testSlotDouble(double value)
{
emit testSignalDouble(value);
return value;
}
QString TestQObject::testSlotString(QString value)
{
emit testSignalString(value);
return value;
}
QDateTime TestQObject::testSlotDateTime(QDateTime value)
{
emit testSignalDateTime(value);
return value;
}
QObject* TestQObject::testSlotQObject(QObject* value)
{
emit testSignalQObject(value);
return value;
}
qint32 TestQObject::testSlotQInt32(qint32 value)
{
emit testSignalQInt32(value);
return value;
}
quint32 TestQObject::testSlotQUInt32(quint32 value)
{
emit testSignalQUInt32(value);
return value;
}
qint64 TestQObject::testSlotQInt64(qint64 value)
{
emit testSignalQInt64(value);
return value;
}
quint64 TestQObject::testSlotQUInt64(quint64 value)
{
emit testSignalQUInt64(value);
return value;
}
extern "C" {
Q_DECL_EXPORT void net_test_helper_runQml(QQmlApplicationEngineContainer* qmlEngineContainer, LPWSTR qml) {
QQmlComponent component(qmlEngineContainer->qmlEngine);
QString qmlString = QString::fromUtf16(static_cast<const char16_t*>(qml));
component.setData(qmlString.toUtf8(), QUrl());
QObject *object = component.create();
if(object == nullptr) {
@ -15,6 +179,10 @@ Q_DECL_EXPORT void net_test_helper_runQml(QQmlApplicationEngineContainer* qmlEng
return;
}
QSharedPointer<TestQObject> testQObject = QSharedPointer<TestQObject>(new TestQObject());
object->setProperty("testQObject", QVariant::fromValue(testQObject.data()));
QMetaObject::invokeMethod(object, "runTest");
delete object;
}

View file

@ -3,5 +3,72 @@
#include <QmlNet.h>
#include <QmlNet/qml/QQmlApplicationEngine.h>
#include <QDateTime>
class TestQObject : public QObject
{
Q_OBJECT
Q_PROPERTY(int readOnly READ getReadOnly)
Q_PROPERTY(int writeOnly WRITE setWriteOnly)
Q_PROPERTY(int readAndWrite READ getReadAndWrite WRITE setReadAndWrite)
Q_PROPERTY(int propWithSignal READ getPropWithSignal WRITE setPropWithSignal NOTIFY propWithSignalChanged)
Q_PROPERTY(QVariant variantProperty READ getVariantProperty WRITE setVariantProperty)
public:
TestQObject();
~TestQObject();
int getReadOnly();
void setWriteOnly(int value);
int getReadAndWrite();
void setReadAndWrite(int value);
int getPropWithSignal();
void setPropWithSignal(int value);
QVariant getVariantProperty();
void setVariantProperty(QVariant value);
signals:
void propWithSignalChanged(int value);
void testSignal();
void testSignalWithArg(int arg);
void testSignalBool(bool value);
void testSignalChar(QChar value);
void testSignalInt(int value);
void testSignalUInt(uint value);
void testSignalLong(long value);
void testSignalULong(ulong value);
void testSignalFloat(float value);
void testSignalDouble(double value);
void testSignalString(QString value);
void testSignalDateTime(QDateTime value);
void testSignalQObject(QObject* qObject);
void testSignalQInt32(qint32 value);
void testSignalQUInt32(quint32 value);
void testSignalQInt64(qint64 value);
void testSignalQUInt64(quint64 value);
public slots:
void testSlot();
void testSlotWithArg(int arg);
QVariant testVariantReturn();
bool testSlotBool(bool value);
QChar testSlotChar(QChar value);
int testSlotInt(int value);
uint testSlotUInt(uint value);
long testSlotLong(long value);
ulong testSlotULong(ulong value);
float testSlotFloat(float value);
double testSlotDouble(double value);
QString testSlotString(QString value);
QDateTime testSlotDateTime(QDateTime value);
QObject* testSlotQObject(QObject* value);
qint32 testSlotQInt32(qint32 value);
quint32 testSlotQUInt32(quint32 value);
qint64 testSlotQInt64(qint64 value);
quint64 testSlotQUInt64(quint64 value);
private:
int _writeOnly;
int _readAndWrite;
int _propWithSignal;
QVariant _variantValue;
};
#endif // NETTESTHELPER_H

View file

@ -1,6 +1,8 @@
#include <QmlNet/qml/NetValueMetaObjectPacker.h>
#include <QmlNet/qml/NetVariant.h>
#include <QmlNet/qml/NetValue.h>
#include <QmlNet/qml/NetJsValue.h>
#include <QmlNet/qml/NetQObject.h>
#include <QDebug>
const char* NetValueTypePacker::getQmlType()
@ -55,6 +57,9 @@ void NetValueTypePacker::pack(const QSharedPointer<NetVariant>& source, void* de
case NetVariantTypeEnum_JSValue:
destinationVariant->setValue(source->getJsValue()->getJsValue());
break;
case NetVariantTypeEnum_QObject:
destinationVariant->setValue(source->getQObject()->getQObject());
break;
}
}
@ -116,8 +121,11 @@ void NetValueTypePacker::unpack(const QSharedPointer<NetVariant>& destination, v
NetValueInterface* netValue = qobject_cast<NetValueInterface*>(value);
if(netValue) {
destination->setNetReference(netValue->getNetReference());
return;
} else {
QSharedPointer<NetQObject> netQObject(new NetQObject(value));
destination->setQObject(netQObject);
}
return;
}
break;
}
@ -131,6 +139,12 @@ void NetValueTypePacker::unpack(const QSharedPointer<NetVariant>& destination, v
// TODO: Try to convert other types to JS Value.
break;
}
case NetVariantTypeEnum_QObject:
{
if(sourceVariant->userType() == QMetaType::QObjectStar) {
QSharedPointer<NetQObject> netQObject(new NetQObject(sourceVariant->value<QObject*>()));
}
}
}
NetVariant::fromQVariant(sourceVariant, destination);
@ -192,6 +206,7 @@ NetValueMetaObjectPacker::NetValueMetaObjectPacker()
case NetVariantTypeEnum_DateTime:
case NetVariantTypeEnum_Object:
case NetVariantTypeEnum_JSValue:
case NetVariantTypeEnum_QObject:
packers[type] = variantPacker;
break;
case NetVariantTypeEnum_String:

View file

@ -1,6 +1,8 @@
#include <QmlNet/types/NetReference.h>
#include <QmlNet/qml/NetVariant.h>
#include <QmlNet/qml/NetValue.h>
#include <QmlNet/qml/NetJsValue.h>
#include <QmlNet/qml/NetQObject.h>
#include <QmlNetUtilities.h>
#include <QDateTime>
#include <QDebug>
@ -18,15 +20,22 @@ struct NetJsValueQmlContainer
{
QSharedPointer<NetJSValue> jsValue;
};
struct NetQObjectQmlContainer
{
QSharedPointer<NetQObject> netQObject;
};
}
Q_DECLARE_METATYPE(NetReferenceQmlContainer)
Q_DECLARE_METATYPE(NetJsValueQmlContainer)
Q_DECLARE_METATYPE(NetQObjectQmlContainer)
namespace
{
const int NetReferenceQmlContainerTypeId = qMetaTypeId<NetReferenceQmlContainer>();
const int NetJsValueQmlContainerTypeId = qMetaTypeId<NetJsValueQmlContainer>();
const int NetQObjectQmlContainerTypeId = qMetaTypeId<NetQObjectQmlContainer>();
}
NetVariant::NetVariant() = default;
@ -68,7 +77,11 @@ NetVariantTypeEnum NetVariant::getVariantType() const
}
else if(type == NetJsValueQmlContainerTypeId) {
return NetVariantTypeEnum_JSValue;
} else {
}
else if(type == NetQObjectQmlContainerTypeId) {
return NetVariantTypeEnum_QObject;
}
else {
qWarning() << "Unknown type for NetVariant: " << variant.typeName();
return NetVariantTypeEnum_Invalid;
}
@ -206,6 +219,16 @@ QSharedPointer<NetJSValue> NetVariant::getJsValue() const
return getValue<NetJsValueQmlContainer>().jsValue;
}
void NetVariant::setQObject(QSharedPointer<NetQObject> netQObject)
{
setValue(NetQObjectQmlContainer{ std::move(netQObject) });
}
QSharedPointer<NetQObject> NetVariant::getQObject() const
{
return getValue<NetQObjectQmlContainer>().netQObject;
}
void NetVariant::clear()
{
clearNetReference();
@ -274,13 +297,23 @@ void NetVariant::fromQVariant(const QVariant* variant, const QSharedPointer<NetV
case QMetaType::QDateTime:
destination->setValueVariant(*variant);
break;
case QMetaType::ULong:
destination->setULong(variant->value<quint64>());
break;
case QMetaType::Long:
destination->setLong(variant->value<qint64>());
break;
case QMetaType::QObjectStar: {
QObject* value = variant->value<QObject*>();
if(value == nullptr) {
destination->clear();
return;
}
NetValueInterface* netValue = qobject_cast<NetValueInterface*>(value);
if(netValue) {
destination->setNetReference(netValue->getNetReference());
} else {
qDebug() << "Unsupported variant type: " << variant->type() << variant->typeName();
destination->setQObject(QSharedPointer<NetQObject>(new NetQObject(value)));
}
break;
}
@ -314,6 +347,8 @@ QVariant NetVariant::toQVariant() const
return getJsValue()->getJsValue().toVariant();
case NetVariantTypeEnum_Object:
return QVariant::fromValue<QObject*>(NetValue::forInstance(getNetReference()));
case NetVariantTypeEnum_QObject:
return QVariant::fromValue<QObject*>(this->getQObject()->getQObject());
default:
return variant;
}
@ -341,6 +376,10 @@ void NetVariant::clearNetReference()
variant.value<NetJsValueQmlContainer>().jsValue.clear();
variant.clear();
}
else if(variant.canConvert<NetQObjectQmlContainer>()) {
variant.value<NetQObjectQmlContainer>().netQObject.clear();
variant.clear();
}
}
template<typename T>
@ -559,6 +598,24 @@ Q_DECL_EXPORT NetJSValueContainer* net_variant_getJsValue(NetVariantContainer* c
return result;
}
Q_DECL_EXPORT void net_variant_setQObject(NetVariantContainer* container, NetQObjectContainer* qObjectContainer) {
if(qObjectContainer == nullptr) {
container->variant->setQObject(nullptr);
} else {
container->variant->setQObject(qObjectContainer->qObject);
}
}
Q_DECL_EXPORT NetQObjectContainer* net_variant_getQObject(NetVariantContainer* container) {
const QSharedPointer<NetQObject>& instance = container->variant->getQObject();
if(instance == nullptr) {
return nullptr;
}
NetQObjectContainer* result = new NetQObjectContainer();
result->qObject = instance;
return result;
}
Q_DECL_EXPORT void net_variant_clear(NetVariantContainer* container) {
container->variant->clear();
}

View file

@ -1,12 +1,16 @@
#ifndef NETVARIANT_H
#define NETVARIANT_H
#include <QmlNet/types/NetReference.h>
#include <QmlNet/qml/NetJsValue.h>
#include <QmlNet.h>
#include <QSharedPointer>
#include <QVariant>
#include <QDateTime>
#include <QJSValue>
class NetJSValue;
class NetQObject;
class NetReference;
class NetVariant
{
public:
@ -38,6 +42,8 @@ public:
QDateTime getDateTime() const;
void setJsValue(QSharedPointer<NetJSValue> jsValue);
QSharedPointer<NetJSValue> getJsValue() const;
void setQObject(QSharedPointer<NetQObject> netQObject);
QSharedPointer<NetQObject> getQObject() const;
void clear();
static QSharedPointer<NetVariant> fromQJSValue(const QJSValue& qJsValue);
QJSValue toQJSValue() const;

View file

@ -225,6 +225,26 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerType(NetTypeInfoContainer* typeC
NETVALUETYPE_CASE(148)
NETVALUETYPE_CASE(149)
NETVALUETYPE_CASE(150)
NETVALUETYPE_CASE(151)
NETVALUETYPE_CASE(152)
NETVALUETYPE_CASE(153)
NETVALUETYPE_CASE(154)
NETVALUETYPE_CASE(155)
NETVALUETYPE_CASE(156)
NETVALUETYPE_CASE(157)
NETVALUETYPE_CASE(158)
NETVALUETYPE_CASE(159)
NETVALUETYPE_CASE(160)
NETVALUETYPE_CASE(161)
NETVALUETYPE_CASE(162)
NETVALUETYPE_CASE(163)
NETVALUETYPE_CASE(164)
NETVALUETYPE_CASE(165)
NETVALUETYPE_CASE(166)
NETVALUETYPE_CASE(167)
NETVALUETYPE_CASE(168)
NETVALUETYPE_CASE(169)
NETVALUETYPE_CASE(170)
}
qFatal("Too many registered types: %d", netValueTypeNumber);
return -1;
@ -395,6 +415,26 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerSingletonTypeNet(NetTypeInfoCont
NETVALUETYPESINGLETON_CASE(148)
NETVALUETYPESINGLETON_CASE(149)
NETVALUETYPESINGLETON_CASE(150)
NETVALUETYPESINGLETON_CASE(151)
NETVALUETYPESINGLETON_CASE(152)
NETVALUETYPESINGLETON_CASE(153)
NETVALUETYPESINGLETON_CASE(154)
NETVALUETYPESINGLETON_CASE(155)
NETVALUETYPESINGLETON_CASE(156)
NETVALUETYPESINGLETON_CASE(157)
NETVALUETYPESINGLETON_CASE(158)
NETVALUETYPESINGLETON_CASE(159)
NETVALUETYPESINGLETON_CASE(160)
NETVALUETYPESINGLETON_CASE(161)
NETVALUETYPESINGLETON_CASE(162)
NETVALUETYPESINGLETON_CASE(163)
NETVALUETYPESINGLETON_CASE(164)
NETVALUETYPESINGLETON_CASE(165)
NETVALUETYPESINGLETON_CASE(166)
NETVALUETYPESINGLETON_CASE(167)
NETVALUETYPESINGLETON_CASE(168)
NETVALUETYPESINGLETON_CASE(169)
NETVALUETYPESINGLETON_CASE(170)
}
qFatal("Too many registered types: %d", netValueTypeNumber);
return -1;

View file

@ -15,7 +15,10 @@ HEADERS += \
$$PWD/NetListModel.h \
$$PWD/QtWebEngine.h \
$$PWD/QCoreApplication.h \
$$PWD/QTest.h
$$PWD/QTest.h \
$$PWD/NetQObject.h \
$$PWD/NetQObjectSignalConnection.h \
$$PWD/NetQObjectArg.h
SOURCES += \
$$PWD/QQmlApplicationEngine.cpp \
@ -34,4 +37,7 @@ SOURCES += \
$$PWD/NetListModel.cpp \
$$PWD/QtWebEngine.cpp \
$$PWD/QCoreApplication.cpp \
$$PWD/QTest.cpp
$$PWD/QTest.cpp \
$$PWD/NetQObject.cpp \
$$PWD/NetQObjectSignalConnection.cpp \
$$PWD/NetQObjectArg.cpp

View file

@ -18,6 +18,7 @@ using gcCollectCb = void (*)(int);
using raiseNetSignalsCb = uchar (*)(NetReferenceContainer *, LPWCSTR, NetVariantListContainer *);
using awaitTaskCb = void (*)(NetReferenceContainer *, NetJSValueContainer *, NetJSValueContainer *);
using serializeNetToStringCb = uchar (*)(NetReferenceContainer *, NetVariantContainer *);
using invokeDelegateCb = void (*)(NetReferenceContainer *, NetVariantListContainer *);
struct Q_DECL_EXPORT NetTypeInfoManagerCallbacks {
isTypeValidCb isTypeValid;
@ -35,6 +36,7 @@ struct Q_DECL_EXPORT NetTypeInfoManagerCallbacks {
raiseNetSignalsCb raiseNetSignals;
awaitTaskCb awaitTask;
serializeNetToStringCb serializeNetToString;
invokeDelegateCb invokeDelegate;
};
static NetTypeInfoManagerCallbacks sharedCallbacks;
@ -175,6 +177,12 @@ bool serializeNetToString(QSharedPointer<NetReference> instance, QSharedPointer<
new NetVariantContainer{std::move(result)}) == 1;
}
void invokeDelegate(QSharedPointer<NetReference> del, QSharedPointer<NetVariantList> parameters)
{
sharedCallbacks.invokeDelegate(new NetReferenceContainer{std::move(del)},
new NetVariantListContainer{std::move(parameters)});
}
}
extern "C" {

View file

@ -8,6 +8,7 @@
#include <QmlNet/types/NetReference.h>
#include <QmlNet/qml/NetVariant.h>
#include <QmlNet/qml/NetVariantList.h>
#include <QmlNet/qml/NetJsValue.h>
#include <QSharedPointer>
#include <QString>
@ -43,6 +44,8 @@ void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> s
bool serializeNetToString(QSharedPointer<NetReference> instance, QSharedPointer<NetVariant> result);
void invokeDelegate(QSharedPointer<NetReference> del, QSharedPointer<NetVariantList> parameters);
}
#endif // NET_TYPE_INFO_MANAGER_H

View file

@ -66,7 +66,8 @@ namespace Qml.Net.Tests.Qml
import tests 1.0
{0} {{
id: {1}
Component.onCompleted: function() {{
property var testQObject: null
function runTest() {{
{2}
}}
}}

View file

@ -228,7 +228,7 @@ namespace Qml.Net.Tests.Qml
[Fact]
public void Can_read_properties_from_js_object()
{
var testObject = new JsValueTests.JsTestsQml.TestObject();
var testObject = new JsTestsQml.TestObject();
dynamic result = null;
Mock.Setup(x => x.Method(It.IsAny<INetJsValue>())).Callback(new Action<dynamic>(jsValue =>
{

View file

@ -9,7 +9,6 @@ namespace Qml.Net.Tests.Qml
{
public virtual void DerivedMethod()
{
}
}
@ -17,7 +16,6 @@ namespace Qml.Net.Tests.Qml
{
public virtual void BaseMethod()
{
}
}

View file

@ -0,0 +1,584 @@
using System;
using System.Runtime.CompilerServices;
using FluentAssertions;
using FluentAssertions.Execution;
using Moq;
using Xunit;
namespace Qml.Net.Tests.Qml
{
public class QObjectTests : BaseQmlTests<QObjectTests.QObjectTestsQml>
{
public class QObjectTestsQml
{
public virtual void Method(dynamic value)
{
}
}
[Fact]
public void Exception_throw_when_using_invalid_methods()
{
AssertQObject(qObject =>
{
// Valid
qObject.InvokeMethod("testSlot");
Assert.Throws<Exception>(() => { qObject.InvokeMethod("nonexistant"); });
});
}
[Fact]
public void Exception_thrown_when_using_invalid_properties()
{
AssertQObject(qObject =>
{
// Valid
qObject.SetProperty("readAndWrite", 3);
Assert.Throws<Exception>(() => { qObject.SetProperty("nonexistant", 3); });
Assert.Throws<Exception>(() => { qObject.GetProperty("nonexistant"); });
});
}
[Fact]
public void Exception_thrown_when_attaching_to_invalid_signal()
{
AssertQObject(qObject =>
{
// Valid
var handler = qObject.AttachSignal("testSignal", parameters =>
{
});
handler.Dispose();
Assert.Throws<Exception>(() =>
{
qObject.AttachSignal(
"nonexistant",
parameters =>
{
});
});
});
}
[Fact]
public void Exception_thrown_when_attaching_to_invalid_notify_signal()
{
AssertQObject(qObject =>
{
// Valid
var handler = qObject.AttachNotifySignal("propWithSignal", parameters =>
{
});
handler.Dispose();
Assert.Throws<Exception>(() =>
{
qObject.AttachNotifySignal(
"readAndWrite",
parameters =>
{
});
});
Assert.Throws<Exception>(() =>
{
qObject.AttachNotifySignal(
"nonexistant",
parameters =>
{
});
});
});
}
[Fact]
public void Can_get_property_on_qobject()
{
AssertQObject(qObject =>
{
qObject.GetProperty("readOnly").Should().Be(3);
});
}
[Fact]
public void Can_set_property_on_qobject()
{
AssertQObject(qObject =>
{
// No real way to test this.
// I suppose it doesn't throw, eh?
qObject.SetProperty("writeOnly", 3);
});
}
[Fact]
public void Can_set_and_get_property_on_qobject()
{
AssertQObject(qObject =>
{
qObject.SetProperty("readAndWrite", 340);
qObject.GetProperty("readAndWrite").Should().Be(340);
});
}
[Fact]
public void Can_get_set_object_name()
{
AssertQObject(qObject =>
{
var d = (dynamic)qObject;
d.objectName = "testtt";
((string)d.objectName).Should().Be("testtt");
});
}
[Fact]
public void Can_set_random_types_on_property()
{
AssertQObject(qObject =>
{
qObject.GetProperty("variantProperty").Should().BeNull();
qObject.SetProperty("variantProperty", true);
qObject.GetProperty("variantProperty").Should().Be(true);
qObject.SetProperty("variantProperty", 'T');
qObject.GetProperty("variantProperty").Should().Be('T');
qObject.SetProperty("variantProperty", int.MinValue);
qObject.GetProperty("variantProperty").Should().Be(int.MinValue);
qObject.SetProperty("variantProperty", uint.MaxValue);
qObject.GetProperty("variantProperty").Should().Be(uint.MaxValue);
qObject.SetProperty("variantProperty", long.MaxValue);
qObject.GetProperty("variantProperty").Should().Be(long.MaxValue);
qObject.SetProperty("variantProperty", ulong.MaxValue);
qObject.GetProperty("variantProperty").Should().Be(ulong.MaxValue);
qObject.SetProperty("variantProperty", float.MaxValue);
qObject.GetProperty("variantProperty").Should().Be(float.MaxValue);
qObject.SetProperty("variantProperty", double.MaxValue);
qObject.GetProperty("variantProperty").Should().Be(double.MaxValue);
qObject.SetProperty("variantProperty", "");
qObject.GetProperty("variantProperty").Should().Be("");
qObject.SetProperty("variantProperty", "test");
qObject.GetProperty("variantProperty").Should().Be("test");
var dateTime = DateTimeOffset.Now;
dateTime = new DateTimeOffset(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Offset);
qObject.SetProperty("variantProperty", dateTime);
var resultDateTime = (DateTimeOffset)qObject.GetProperty("variantProperty");
resultDateTime = new DateTimeOffset(resultDateTime.Year, resultDateTime.Month, resultDateTime.Day, resultDateTime.Hour, resultDateTime.Minute, resultDateTime.Second, resultDateTime.Millisecond, resultDateTime.Offset);
resultDateTime.Should().Be(dateTime);
var o = new object();
qObject.SetProperty("variantProperty", o);
qObject.GetProperty("variantProperty").Should().BeSameAs(o);
qObject.SetProperty("objectName", "ttttt");
qObject.SetProperty("variantProperty", qObject);
var result = qObject.GetProperty("variantProperty");
result.Should().NotBeNull();
var resultQObject = result as INetQObject;
resultQObject.Should().NotBeNull();
resultQObject.GetProperty("objectName").Should().Be("ttttt");
});
}
[Fact]
public void Can_return_random_types_from_method()
{
AssertQObject(qObject =>
{
qObject.InvokeMethod("testVariantReturn").Should().Be(null);
qObject.SetProperty("variantProperty", true);
qObject.InvokeMethod("testVariantReturn").Should().Be(true);
qObject.SetProperty("variantProperty", 'T');
qObject.InvokeMethod("testVariantReturn").Should().Be('T');
qObject.SetProperty("variantProperty", int.MinValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(int.MinValue);
qObject.SetProperty("variantProperty", uint.MaxValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(uint.MaxValue);
qObject.SetProperty("variantProperty", long.MaxValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(long.MaxValue);
qObject.SetProperty("variantProperty", ulong.MaxValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(ulong.MaxValue);
qObject.SetProperty("variantProperty", float.MaxValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(float.MaxValue);
qObject.SetProperty("variantProperty", double.MaxValue);
qObject.InvokeMethod("testVariantReturn").Should().Be(double.MaxValue);
qObject.SetProperty("variantProperty", "");
qObject.InvokeMethod("testVariantReturn").Should().Be("");
qObject.SetProperty("variantProperty", "test");
qObject.InvokeMethod("testVariantReturn").Should().Be("test");
var dateTime = DateTimeOffset.Now;
dateTime = new DateTimeOffset(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Offset);
qObject.SetProperty("variantProperty", dateTime);
var resultDateTime = (DateTimeOffset)qObject.InvokeMethod("testVariantReturn");
resultDateTime = new DateTimeOffset(resultDateTime.Year, resultDateTime.Month, resultDateTime.Day, resultDateTime.Hour, resultDateTime.Minute, resultDateTime.Second, resultDateTime.Millisecond, resultDateTime.Offset);
resultDateTime.Should().Be(dateTime);
var o = new object();
qObject.SetProperty("variantProperty", o);
qObject.InvokeMethod("testVariantReturn").Should().BeSameAs(o);
qObject.SetProperty("objectName", "ttttt");
qObject.SetProperty("variantProperty", qObject);
var result = qObject.InvokeMethod("testVariantReturn");
result.Should().NotBeNull();
var resultQObject = result as INetQObject;
resultQObject.Should().NotBeNull();
resultQObject.GetProperty("objectName").Should().Be("ttttt");
});
}
[Fact]
public void Can_set_qobject_on_global_context_property()
{
AssertQObject(qObject =>
{
var d = (dynamic)qObject;
var property = Guid.NewGuid().ToString().Replace("-", "");
d.objectName = property;
qmlApplicationEngine.SetContextProperty(property, d);
var result = qmlApplicationEngine.GetContextProperty(property);
(result is INetQObject).Should().BeTrue();
result.Should().NotBeNull();
((string)d.objectName).Should().Be(property);
});
}
[Fact]
public void Can_invoke_method_on_qobject()
{
AssertQObject(qObject =>
{
// TODO: Assert
qObject.InvokeMethod("testSlot");
});
}
[Fact]
public void Can_attach_notify_signal_to_qobject()
{
AssertQObject(qObject =>
{
var raised = false;
var expected = 0;
var handler = qObject.AttachNotifySignal("propWithSignal", parameters =>
{
raised = true;
parameters.Count.Should().Be(1);
parameters[0].Should().Be(expected);
});
expected = 43;
qObject.SetProperty("propWithSignal", 43);
raised.Should().BeTrue();
raised = false;
qObject.SetProperty("propWithSignal", 43);
raised.Should().BeFalse();
expected = 44;
raised = false;
qObject.SetProperty("propWithSignal", 44);
raised.Should().BeTrue();
});
}
[Fact]
public void Can_attach_signal_to_qobject()
{
AssertQObject(qObject =>
{
var raised = 0;
var handler = qObject.AttachSignal("testSignal", parameters =>
{
raised++;
});
handler.Should().NotBeNull();
qObject.InvokeMethod("testSlot");
raised.Should().Be(1);
handler.Dispose();
qObject.InvokeMethod("testSlot");
raised.Should().Be(1);
});
}
[Fact]
public void Can_attach_signal_with_arg_to_qobject()
{
AssertQObject(qObject =>
{
var raised = false;
var handler = qObject.AttachSignal("testSignalWithArg", parameters =>
{
raised = true;
parameters.Should().NotBeNull();
parameters.Count.Should().Be(1);
parameters[0].Should().Be(33);
});
qObject.InvokeMethod("testSlotWithArg", 33);
handler.Dispose();
raised = false;
qObject.InvokeMethod("testSlotWithArg", 33);
raised.Should().BeFalse();
});
}
[Fact]
public void Can_use_bool_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Bool", true);
AssertValue(qObject, "Bool", false);
});
}
[Fact]
public void Can_use_char_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Char", 'T');
});
}
[Fact]
public void Can_use_int_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Int", int.MinValue);
AssertValue(qObject, "Int", int.MaxValue);
});
}
[Fact]
public void Can_use_uint_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "UInt", uint.MinValue);
AssertValue(qObject, "UInt", uint.MaxValue);
});
}
[Fact]
public void Can_use_long_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Long", -23423);
});
}
[Fact]
public void Can_use_ulong_on_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "ULong", 2323);
});
}
[Fact]
public void Can_use_float_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Float", float.MinValue);
AssertValue(qObject, "Float", float.MaxValue);
});
}
[Fact]
public void Can_use_double_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "Double", double.MinValue);
AssertValue(qObject, "Double", double.MaxValue);
});
}
[Fact]
public void Can_use_string_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "String", "test");
AssertValue(qObject, "String", "");
AssertValue(qObject, "String", null);
});
}
[Fact]
public void Can_use_datetime_with_qobject()
{
AssertQObject(qObject =>
{
var value = DateTimeOffset.Now;
value = new DateTimeOffset(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Millisecond, value.Offset);
AssertValue(qObject, "DateTime", value);
});
}
[Fact]
public void Can_use_qobject_with_qobject()
{
AssertQObject(qObject =>
{
qObject.SetProperty("objectName", "testttdd");
AssertValue(qObject, "QObject", qObject, result =>
{
result.Should().BeAssignableTo<INetQObject>()
.Subject.GetProperty("objectName").Should().Be("testttdd");
});
});
}
[Fact]
public void Can_use_object_with_qobject()
{
AssertQObject(qObject =>
{
var o = new object();
AssertValue(qObject, "QObject", o, result =>
{
result.Should().BeSameAs(o);
});
AssertValue(qObject, "QObject", null, result =>
{
result.Should().BeNull();
});
});
}
[Fact]
public void Can_use_qint32_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "QInt32", int.MinValue);
AssertValue(qObject, "QInt32", int.MaxValue);
});
}
[Fact]
public void Can_use_quint32_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "QUInt32", uint.MinValue);
AssertValue(qObject, "QUInt32", uint.MaxValue);
});
}
[Fact]
public void Can_use_qint64_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "QInt64", long.MinValue);
AssertValue(qObject, "QInt64", long.MaxValue);
});
}
[Fact]
public void Can_use_quint64_with_qobject()
{
AssertQObject(qObject =>
{
AssertValue(qObject, "QUInt64", ulong.MinValue);
AssertValue(qObject, "QUInt64", ulong.MaxValue);
});
}
private void AssertValue(INetQObject qObject, string method, object value)
{
var raised = false;
var handler = qObject.AttachSignal($"testSignal{method}", parameters =>
{
raised = true;
parameters.Count.Should().Be(1);
parameters[0].Should().Be(value);
});
using (handler)
{
var invokeResult = qObject.InvokeMethod($"testSlot{method}", value);
invokeResult.Should().Be(value);
raised.Should().BeTrue();
}
}
private void AssertValue(INetQObject qObject, string method, object value, Action<object> assert)
{
var raised = false;
var handler = qObject.AttachSignal($"testSignal{method}", parameters =>
{
raised = true;
parameters.Count.Should().Be(1);
assert(parameters[0]);
});
using (handler)
{
assert(qObject.InvokeMethod($"testSlot{method}", value));
raised.Should().BeTrue();
}
}
private void AssertQObject(Action<INetQObject> action)
{
Exception assertException = null;
Mock.Setup(x => x.Method(It.IsAny<INetQObject>()))
.Callback(new Action<dynamic>(x =>
{
try
{
action(x as INetQObject);
}
catch (Exception ex)
{
assertException = ex;
}
}));
RunQmlTest(
"test",
@"
test.method(testQObject)
");
Mock.Verify(x => x.Method(It.IsAny<INetQObject>()), Times.Once);
if (assertException != null)
{
throw new Exception(assertException.Message, assertException);
}
}
}
}

View file

@ -3,4 +3,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSX/@EntryIndexedValue">OSX</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gstream/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pixmaps/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=qobject/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Runtimes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -10,6 +10,8 @@ namespace Qml.Net
object GetProperty(string propertyName);
void SetProperty(string propertyName, object value);
object GetItemAtIndex(int arrayIndex);
object Call(params object[] parameters);

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace Qml.Net
{
public interface INetQObject : IDisposable
{
object GetProperty(string propertyName);
void SetProperty(string propertyName, object value);
object InvokeMethod(string methodName, params object[] parameters);
IDisposable AttachSignal(string signalName, SignalHandler handler);
IDisposable AttachNotifySignal(string propertyName, SignalHandler handler);
}
public delegate void SignalHandler(List<object> parameters);
}

View file

@ -248,6 +248,10 @@ namespace Qml.Net.Internal.CodeGen
{
variant.JsValue = valueJsValue;
}
else if (value is NetQObject valueQObject)
{
variant.QObject = valueQObject;
}
else
{
variant.Instance = NetReference.CreateForObject(value);

View file

@ -83,6 +83,8 @@ namespace Qml.Net.Internal.CodeGen
break;
case NetVariantType.JsValue:
throw new NotImplementedException();
case NetVariantType.QObject:
throw new NotImplementedException();
case NetVariantType.Invalid:
throw new Exception("invalid type");
default:
@ -224,6 +226,8 @@ namespace Qml.Net.Internal.CodeGen
break;
case NetVariantType.JsValue:
throw new NotImplementedException();
case NetVariantType.QObject:
throw new NotImplementedException();
case NetVariantType.Invalid:
throw new Exception("invalid type");
default:

View file

@ -462,6 +462,24 @@ namespace Qml.Net.Internal
}
}
public void InvokeDelegate(IntPtr del, IntPtr parameters)
{
using (var netReference = new NetReference(del))
{
using (var netParameters = new NetVariantList(parameters))
{
var o = netReference.Instance;
var oDel = o as Del;
if (oDel == null)
{
throw new Exception($"NetReferecnce is invalid type: {o.GetType().FullName}");
}
oDel.Raise(netParameters);
}
}
}
private NetVariantType GetPrefVariantType(Type typeInfo)
{
if (typeInfo == typeof(bool))

View file

@ -0,0 +1,16 @@
using System;
using Qml.Net.Internal.Qml;
namespace Qml.Net.Internal
{
internal class Del
{
public event Action<NetVariantList> Invoked;
public void Raise(NetVariantList parameters)
{
var handler = Invoked;
handler?.Invoke(parameters);
}
}
}

View file

@ -55,6 +55,8 @@ namespace Qml.Net.Internal
destination.DateTime = ((DateTimeOffset)source).DateTime;
else if (typeof(INetJsValue).IsAssignableFrom(type))
destination.JsValue = ((NetJsValue.NetJsValueDynamic)source).JsValue;
else if (typeof(INetQObject).IsAssignableFrom(type))
destination.QObject = ((NetQObject.NetQObjectDynamic)source).QObject;
else
{
if (type.IsEnum)
@ -131,6 +133,9 @@ namespace Qml.Net.Internal
case NetVariantType.JsValue:
destination = source.JsValue.AsDynamic();
break;
case NetVariantType.QObject:
destination = source.QObject.AsDynamic();
break;
default:
throw new Exception("Unsupported variant type.");
}

View file

@ -73,6 +73,8 @@ namespace Qml.Net.Internal
Utilities = LoadInteropType<UtilitiesInterop>(library, loader);
QtWebEngine = LoadInteropType<QtWebEngineInterop>(library, loader);
QTest = LoadInteropType<QTestInterop>(library, loader);
NetQObject = LoadInteropType<NetQObjectInterop>(library, loader);
NetQObjectSignalConnection = LoadInteropType<NetQObjectSignalConnectionInterop>(library, loader);
// RuntimeManager.ConfigureRuntimeDirectory may set these environment variables.
// However, they only really work when called with Qt.PutEnv.
@ -122,6 +124,10 @@ namespace Qml.Net.Internal
public static QtWebEngineInterop QtWebEngine { get; }
public static QTestInterop QTest { get; }
public static NetQObjectInterop NetQObject { get; }
public static NetQObjectSignalConnectionInterop NetQObjectSignalConnection { get; set; }
private static T LoadInteropType<T>(IntPtr library, IPlatformLoader loader)
where T : new()

View file

@ -121,6 +121,22 @@ namespace Qml.Net.Internal.Qml
return unpacked;
}
public void SetProperty(string propertyName, object value)
{
if (value == null)
{
_jsValue.SetProperty(propertyName, null);
}
else
{
using (var variant = new NetVariant())
{
Helpers.Pack(value, variant, value.GetType());
_jsValue.SetProperty(propertyName, variant);
}
}
}
public object GetItemAtIndex(int arrayIndex)
{
var result = _jsValue.GetItemAtIndex(arrayIndex);
@ -170,19 +186,7 @@ namespace Qml.Net.Internal.Qml
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (value == null)
{
_jsValue.SetProperty(binder.Name, null);
}
else
{
using (var variant = new NetVariant())
{
Helpers.PackValue(value, variant);
_jsValue.SetProperty(binder.Name, variant);
}
}
SetProperty(binder.Name, value);
return true;
}
}

View file

@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Runtime.InteropServices;
using Qml.Net.Internal.Types;
namespace Qml.Net.Internal.Qml
{
internal class NetQObject : BaseDisposable
{
public NetQObject(IntPtr handle, bool ownsHandle = true)
: base(handle, ownsHandle)
{
}
protected override void DisposeUnmanaged(IntPtr ptr)
{
Interop.NetQObject.Destroy(ptr);
}
public NetVariant GetProperty(string propertyName)
{
byte wasSuccessful = 0;
var result = Interop.NetQObject.GetProperty(Handle, propertyName, ref wasSuccessful);
if (wasSuccessful == 0)
{
// Dispose of the type before we throw
using (result == IntPtr.Zero ? null : new NetVariant(result)) { }
throw new Exception("Getting property failed.");
}
return result != IntPtr.Zero ? new NetVariant(result) : null;
}
public void SetProperty(string propertyName, NetVariant value)
{
byte wasSuccessful = 0;
Interop.NetQObject.SetProperty(Handle, propertyName, value?.Handle ?? IntPtr.Zero, ref wasSuccessful);
if (wasSuccessful == 0)
{
throw new Exception("Setting property failed.");
}
}
public NetVariant InvokeMethod(string methodName, NetVariantList parameters)
{
byte wasSuccessful = 0;
var result = Interop.NetQObject.InvokeMethod(Handle, methodName, parameters?.Handle ?? IntPtr.Zero, ref wasSuccessful);
if (wasSuccessful == 0)
{
// Dispose of the type before we throw
using (result == IntPtr.Zero ? null : new NetVariant(result)) { }
throw new Exception("Invoking method failed.");
}
return result != IntPtr.Zero ? new NetVariant(result) : null;
}
public NetQObjectSignalConnection AttachSignal(string signalName, Del del)
{
using (var delReference = NetReference.CreateForObject(del))
{
byte wasSuccessful = 0;
var result = Interop.NetQObject.AttachSignal(Handle, signalName, delReference.Handle, ref wasSuccessful);
if (wasSuccessful == 0)
{
// Dispose of the type before we throw
using (result == IntPtr.Zero ? null : new NetQObjectSignalConnection(result)) { }
throw new Exception("Attaching to signal failed.");
}
return result == IntPtr.Zero ? null : new NetQObjectSignalConnection(result);
}
}
public NetQObjectSignalConnection AttachNotifySignal(string propertyName, Del del)
{
using (var delReference = NetReference.CreateForObject(del))
{
byte wasSuccessful = 0;
var result = Interop.NetQObject.AttachNotifySignal(Handle, propertyName, delReference.Handle, ref wasSuccessful);
if (wasSuccessful == 0)
{
// Dispose of the type before we throw
using (result == IntPtr.Zero ? null : new NetQObjectSignalConnection(result)) { }
throw new Exception("Attaching to notify signal failed.");
}
return result == IntPtr.Zero ? null : new NetQObjectSignalConnection(result);
}
}
public dynamic AsDynamic()
{
return new NetQObjectDynamic(this);
}
internal class NetQObjectDynamic : DynamicObject, INetQObject
{
readonly NetQObject _qObject;
public NetQObjectDynamic(NetQObject qObject)
{
_qObject = qObject;
}
public void Dispose()
{
_qObject.Dispose();
}
public NetQObject QObject => _qObject;
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = GetProperty(binder.Name);
// TODO: Check if this was actually a property
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
SetProperty(binder.Name, value);
// TODO: Check if this was actually a property
return true;
}
public object GetProperty(string propertyName)
{
var property = _qObject.GetProperty(propertyName);
if (property == null)
{
return null;
}
object unpacked = null;
Helpers.Unpackvalue(ref unpacked, property);
property.Dispose();
return unpacked;
}
public void SetProperty(string propertyName, object value)
{
if (value == null)
{
_qObject.SetProperty(propertyName, null);
}
else
{
using (var variant = new NetVariant())
{
Helpers.PackValue(value, variant);
_qObject.SetProperty(propertyName, variant);
}
}
}
public object InvokeMethod(string methodName, params object[] parameters)
{
NetVariantList variantParameters = null;
if (parameters != null && parameters.Length > 0)
{
variantParameters = new NetVariantList();
foreach (var parameter in parameters)
{
using (var variantParameter = NetVariant.From(parameter))
{
variantParameters.Add(variantParameter);
}
}
}
using (variantParameters)
using (var result = _qObject.InvokeMethod(methodName, variantParameters))
{
if (result == null)
{
return null;
}
object unpacked = null;
Helpers.Unpackvalue(ref unpacked, result);
result.Dispose();
return unpacked;
}
}
public IDisposable AttachSignal(string signalName, SignalHandler handler)
{
var del = new Del();
del.Invoked += parameters =>
{
var result = new List<object>();
var parametersCount = parameters.Count;
for (var x = 0; x < parametersCount; x++)
{
using (var parameter = parameters.Get(x))
{
object parameterValue = null;
Helpers.Unpackvalue(ref parameterValue, parameter);
result.Add(parameterValue);
}
}
handler(result);
};
return _qObject.AttachSignal(signalName, del);
}
public IDisposable AttachNotifySignal(string propertyName, SignalHandler handler)
{
var del = new Del();
del.Invoked += parameters =>
{
var result = new List<object>();
var parametersCount = parameters.Count;
for (var x = 0; x < parametersCount; x++)
{
using (var parameter = parameters.Get(x))
{
object parameterValue = null;
Helpers.Unpackvalue(ref parameterValue, parameter);
result.Add(parameterValue);
}
}
handler(result);
};
return _qObject.AttachNotifySignal(propertyName, del);
}
}
}
internal class NetQObjectInterop
{
[NativeSymbol(Entrypoint = "net_qobject_destroy")]
public DestroyDel Destroy { get; set; }
public delegate void DestroyDel(IntPtr qObject);
[NativeSymbol(Entrypoint = "net_qobject_getProperty")]
public GetPropertyDel GetProperty { get; set; }
public delegate IntPtr GetPropertyDel(IntPtr qObject, [MarshalAs(UnmanagedType.LPWStr)] string propertyName, ref byte result);
[NativeSymbol(Entrypoint = "net_qobject_setProperty")]
public SetPropertyDel SetProperty { get; set; }
public delegate IntPtr SetPropertyDel(IntPtr qObject, [MarshalAs(UnmanagedType.LPWStr)] string propertyName, IntPtr netVariant, ref byte result);
[NativeSymbol(Entrypoint = "net_qobject_invokeMethod")]
public InvokeMethodDel InvokeMethod { get; set; }
public delegate IntPtr InvokeMethodDel(IntPtr qObject, [MarshalAs(UnmanagedType.LPWStr)] string methodName, IntPtr parameters, ref byte result);
[NativeSymbol(Entrypoint = "net_qobject_attachSignal")]
public AttachSignalDel AttachSignal { get; set; }
public delegate IntPtr AttachSignalDel(IntPtr qObject, [MarshalAs(UnmanagedType.LPWStr)] string signalName, IntPtr del, ref byte result);
[NativeSymbol(Entrypoint = "net_qobject_attachNotifySignal")]
public AttachNotifySignalDel AttachNotifySignal { get; set; }
public delegate IntPtr AttachNotifySignalDel(IntPtr qObject, [MarshalAs(UnmanagedType.LPWStr)] string signalName, IntPtr del, ref byte result);
}
}

View file

@ -0,0 +1,25 @@
using System;
namespace Qml.Net.Internal.Qml
{
internal class NetQObjectSignalConnection : BaseDisposable
{
public NetQObjectSignalConnection(IntPtr handle, bool ownsHandle = true)
: base(handle, ownsHandle)
{
}
protected override void DisposeUnmanaged(IntPtr ptr)
{
Interop.NetQObjectSignalConnection.Destroy(ptr);
}
}
internal class NetQObjectSignalConnectionInterop
{
[NativeSymbol(Entrypoint = "net_qobject_signal_handler_destroy")]
public DestroyDel Destroy { get; set; }
public delegate void DestroyDel(IntPtr signalHandler);
}
}

View file

@ -81,7 +81,7 @@ namespace Qml.Net.Internal.Qml
get => Utilities.ContainerToString(Interop.NetVariant.GetString(Handle));
set => Interop.NetVariant.SetString(Handle, value);
}
public DateTimeOffset? DateTime
{
get
@ -135,6 +135,16 @@ namespace Qml.Net.Internal.Qml
set => Interop.NetVariant.SetJsValue(Handle, value?.Handle ?? IntPtr.Zero);
}
public NetQObject QObject
{
get
{
var result = Interop.NetVariant.GetQObject(Handle);
return result == IntPtr.Zero ? null : new NetQObject(result);
}
set => Interop.NetVariant.SetQObject(Handle, value?.Handle ?? IntPtr.Zero);
}
public void Clear()
{
Interop.NetVariant.Clear(Handle);
@ -174,6 +184,8 @@ namespace Qml.Net.Internal.Qml
case NetVariantType.JsValue:
return JsValue.AsDynamic();
case NetVariantType.QObject:
return QObject.AsDynamic();
default:
throw new NotImplementedException($"unhandled type {VariantType}");
}
@ -187,7 +199,20 @@ namespace Qml.Net.Internal.Qml
public static NetVariant From<T>(T value)
{
var variant = new NetVariant();
Helpers.Pack(value, variant, typeof(T));
if (value != null)
{
Helpers.Pack(value, variant, typeof(T));
}
return variant;
}
public static NetVariant From(object value)
{
var variant = new NetVariant();
if (value != null)
{
Helpers.Pack(value, variant, value.GetType());
}
return variant;
}
}
@ -329,6 +354,16 @@ namespace Qml.Net.Internal.Qml
public delegate IntPtr GetJsValueDel(IntPtr variant);
[NativeSymbol(Entrypoint = "net_variant_setQObject")]
public SetQObjectDel SetQObject { get; set; }
public delegate void SetQObjectDel(IntPtr variant, IntPtr jsValue);
[NativeSymbol(Entrypoint = "net_variant_getQObject")]
public GetQObjectDel GetQObject { get; set; }
public delegate IntPtr GetQObjectDel(IntPtr variant);
[NativeSymbol(Entrypoint = "net_variant_clear")]
public ClearDel Clear { get; set; }

View file

@ -1,4 +1,5 @@
using System;
using System.Dynamic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -22,6 +23,7 @@ namespace Qml.Net.Internal.Types
public IntPtr RaseNetSignals;
public IntPtr AwaitTask;
public IntPtr Serialize;
public IntPtr InvokeDelegate;
}
internal class CallbacksInterop
@ -88,6 +90,8 @@ namespace Qml.Net.Internal.Types
Task AwaitTask(IntPtr target, IntPtr succesCallback, IntPtr failureCallback);
bool Serialize(IntPtr instance, IntPtr result);
void InvokeDelegate(IntPtr del, IntPtr parameters);
}
internal class CallbacksImpl
@ -108,6 +112,7 @@ namespace Qml.Net.Internal.Types
RaiseNetSignalsDelegate _raiseNetSignalsDelegate;
AwaitTaskDelegate _awaitTaskDelegate;
SerializeDelegate _serializeDelegate;
InvokeDelegateDelegate _invokeDelegateDelegate;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate byte IsTypeValidDelegate([MarshalAs(UnmanagedType.LPWStr)]string typeName);
@ -153,6 +158,9 @@ namespace Qml.Net.Internal.Types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate byte SerializeDelegate(IntPtr instance, IntPtr result);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void InvokeDelegateDelegate(IntPtr del, IntPtr parameters);
public CallbacksImpl(ICallbacks callbacks)
{
@ -202,6 +210,9 @@ namespace Qml.Net.Internal.Types
_serializeDelegate = Serialize;
GCHandle.Alloc(_serializeDelegate);
_invokeDelegateDelegate = InvokeDelegate;
GCHandle.Alloc(_invokeDelegateDelegate);
}
private byte IsTypeValid(string typeName)
@ -282,6 +293,11 @@ namespace Qml.Net.Internal.Types
return _callbacks.Serialize(instance, result) ? (byte)1 : (byte)0;
}
private void InvokeDelegate(IntPtr del, IntPtr parameters)
{
_callbacks.InvokeDelegate(del, parameters);
}
public Callbacks Callbacks()
{
return new Callbacks
@ -300,7 +316,8 @@ namespace Qml.Net.Internal.Types
GCCollect = Marshal.GetFunctionPointerForDelegate(_gcCollectDelegate),
RaseNetSignals = Marshal.GetFunctionPointerForDelegate(_raiseNetSignalsDelegate),
AwaitTask = Marshal.GetFunctionPointerForDelegate(_awaitTaskDelegate),
Serialize = Marshal.GetFunctionPointerForDelegate(_serializeDelegate)
Serialize = Marshal.GetFunctionPointerForDelegate(_serializeDelegate),
InvokeDelegate = Marshal.GetFunctionPointerForDelegate(_invokeDelegateDelegate)
};
}
}

View file

@ -15,5 +15,6 @@
DateTime,
Object,
JsValue,
QObject
}
}