Merge branch 'signals'

This commit is contained in:
Paul Knopf 2018-07-20 02:09:51 -04:00
commit 2d125c0304
28 changed files with 1033 additions and 158 deletions

View file

@ -1,16 +1,19 @@
#include <QQmlEngine>
#include <QtNetCoreQml/qml/NetValue.h>
#include <QtNetCoreQml/qml/NetValueMetaObject.h>
NetValue::NetValue(QSharedPointer<NetInstance> instance, QObject *parent)
: instance(instance)
{
valueMeta = new NetValueMetaObject(this, instance);
setParent(parent);
}
#include <QDebug>
NetValue::~NetValue()
{
auto hit = netValues.find(instance.data());
if(hit != netValues.end())
{
netValues.erase(hit);
}
qDebug("NetValue deleted: %s", qPrintable(instance->getTypeInfo()->getClassName()));
if(instance != nullptr) {
instance->release();
}
}
@ -18,3 +21,83 @@ QSharedPointer<NetInstance> NetValue::getNetInstance()
{
return instance;
}
bool NetValue::activateSignal(QString signalName, QSharedPointer<NetVariantList> arguments)
{
// Build the signature so we can look it up.
// Perf?
QString signature = signalName;
signature.append("(");
if(arguments != NULL) {
for(int argumentIndex = 0; argumentIndex <= arguments->count() - 1; argumentIndex++)
{
if(argumentIndex > 0) {
signature.append(",");
}
signature.append("QVariant");
}
}
signature.append(")");
QByteArray normalizedSignalSignature = QMetaObject::normalizedSignature(signature.toLatin1().data());
int signalMethodIndex = valueMeta->indexOfMethod(normalizedSignalSignature);
// If signal not found, dump the registered signals for debugging.
if(signalMethodIndex < 0) {
qDebug("Signal not found: %s", qPrintable(normalizedSignalSignature));
qDebug("Current signals:");
for (int i = 0; i < metaObject()->methodCount(); i++) {
QMetaMethod method = metaObject()->method(i);
if (method.methodType() == QMetaMethod::Signal) {
qDebug("\t%s", qPrintable(method.methodSignature()));
}
}
return false;
}
// Build the types needed to activate the signal
QList<QSharedPointer<QVariant>> variantArgs;
std::vector<void*> voidArgs;
voidArgs.push_back(NULL); // For the return type, which is nothing for signals.
if(arguments != NULL) {
for(int x = 0 ; x < arguments->count(); x++) {
QSharedPointer<QVariant> variant = QSharedPointer<QVariant>(new QVariant(arguments->get(x)->asQVariant()));
variantArgs.append(variant);
voidArgs.push_back((void *)variant.data());
}
}
void** argsPointer = nullptr;
if(voidArgs.size() > 0) {
argsPointer = &voidArgs[0];
}
// Activate the signal!
valueMeta->activate(this, signalMethodIndex, argsPointer);
return true;
}
NetValue* NetValue::forInstance(QSharedPointer<NetInstance> instance, bool autoCreate)
{
if(netValues.find(instance.data()) != netValues.end())
{
return netValues.at(instance.data());
}
if(!autoCreate)
{
return NULL;
}
auto result = new NetValue(instance, nullptr);
QQmlEngine::setObjectOwnership(result, QQmlEngine::JavaScriptOwnership);
return result;
}
NetValue::NetValue(QSharedPointer<NetInstance> instance, QObject *parent)
: instance(instance)
{
valueMeta = new NetValueMetaObject(this, instance);
setParent(parent);
netValues[instance.data()] = this;
qDebug("NetValue created: %s", qPrintable(instance->getTypeInfo()->getClassName()));
}
std::map<NetInstance*, NetValue*> NetValue::netValues = std::map<NetInstance*, NetValue*>();

View file

@ -1,8 +1,11 @@
#ifndef NETVALUE_H
#define NETVALUE_H
#include <map>
#include <QtNetCoreQml.h>
#include <QtNetCoreQml/types/NetInstance.h>
#include <QtNetCoreQml/qml/NetVariantList.h>
#include <QObject>
#include <QSharedPointer>
@ -20,12 +23,20 @@ class NetValue : public QObject, NetValueInterface
Q_OBJECT
Q_INTERFACES(NetValueInterface)
public:
NetValue(QSharedPointer<NetInstance> instance, QObject *parent);
virtual ~NetValue();
QSharedPointer<NetInstance> getNetInstance();
bool activateSignal(QString signalName, QSharedPointer<NetVariantList> arguments);
static NetValue* forInstance(QSharedPointer<NetInstance> instance, bool autoCreate = true);
protected:
NetValue(QSharedPointer<NetInstance> instance, QObject *parent);
private:
QSharedPointer<NetInstance> instance;
NetValueMetaObject* valueMeta;
static std::map<NetInstance*, NetValue*> netValues;
};
#endif // NETVALUE_H

View file

@ -2,6 +2,7 @@
#include <QtNetCoreQml/types/NetTypeInfo.h>
#include <QtNetCoreQml/types/NetMethodInfo.h>
#include <QtNetCoreQml/types/NetPropertyInfo.h>
#include <QtNetCoreQml/types/NetSignalInfo.h>
#include <QtNetCoreQml/types/Callbacks.h>
#include <QQmlEngine>
#include <QDebug>
@ -36,8 +37,7 @@ void metaPackValue(QSharedPointer<NetVariant> source, QVariant* destination) {
case NetVariantTypeEnum_Object:
{
QSharedPointer<NetInstance> newInstance = source->getNetInstance();
NetValue* netValue = new NetValue(newInstance, NULL);
QQmlEngine::setObjectOwnership(netValue, QQmlEngine::JavaScriptOwnership);
NetValue* netValue = NetValue::forInstance(newInstance);
destination->setValue(netValue);
break;
}
@ -193,6 +193,32 @@ QMetaObject *metaObjectFor(QSharedPointer<NetTypeInfo> typeInfo)
mob.setClassName(typeInfo->getClassName().toLatin1());
mob.setFlags(QMetaObjectBuilder::DynamicMetaObject);
// register all the signals for the type
if(typeInfo->getSignalCount() > 0) {
for(uint index = 0; index <= typeInfo->getSignalCount() - 1; index++)
{
QSharedPointer<NetSignalInfo> signalInfo = typeInfo->getSignal(index);
QString signature = signalInfo->getName();
signature.append("(");
if(signalInfo->getParameterCount() > 0) {
for(uint parameterIndex = 0; parameterIndex <= signalInfo->getParameterCount() - 1; parameterIndex++)
{
if(parameterIndex > 0) {
signature.append(", ");
}
signature.append("QVariant");
}
}
signature.append(")");
mob.addSignal(signature.toLocal8Bit().constData());
}
}
if(typeInfo->getMethodCount() > 0) {
for(uint index = 0; index <= typeInfo->getMethodCount() - 1; index++)
{
@ -290,7 +316,16 @@ int NetValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a)
return value->qt_metacall(c, idx, a);
}
QSharedPointer<NetMethodInfo> methodInfo = instance->getTypeInfo()->getMethodInfo(idx - offset);
idx -= offset;
if(idx < (int)instance->getTypeInfo()->getSignalCount()) {
// This is a signal call, activate it!
activate(value, idx + offset, a);
return -1;
}
idx -= instance->getTypeInfo()->getSignalCount();
QSharedPointer<NetMethodInfo> methodInfo = instance->getTypeInfo()->getMethodInfo(idx);
QSharedPointer<NetVariantList> parameters = QSharedPointer<NetVariantList>(new NetVariantList());

View file

@ -1,18 +1,18 @@
#include <QtNetCoreQml/types/NetInstance.h>
#include <QtNetCoreQml/types/Callbacks.h>
#include <QtNetCoreQml/qml/NetValue.h>
#include <QDebug>
NetInstance::NetInstance(NetGCHandle* gcHandle, QSharedPointer<NetTypeInfo> typeInfo) :
gcHandle(gcHandle),
typeInfo(typeInfo)
{
qDebug("NetInstance created: %s", qPrintable(typeInfo->getClassName()));
}
NetInstance::~NetInstance()
{
releaseGCHandle(gcHandle);
gcHandle = NULL;
typeInfo = NULL;
release();
}
NetGCHandle* NetInstance::getGCHandle()
@ -25,6 +25,17 @@ QSharedPointer<NetTypeInfo> NetInstance::getTypeInfo()
return typeInfo;
}
void NetInstance::release()
{
if(gcHandle != nullptr) {
releaseGCHandle(gcHandle);
auto typeInfoClassName = typeInfo->getClassName();
gcHandle = nullptr;
typeInfo = nullptr;
qDebug("NetInstance released: %s", qPrintable(typeInfoClassName));
}
}
extern "C" {
Q_DECL_EXPORT NetInstanceContainer* net_instance_create(NetGCHandle* handle, NetTypeInfoContainer* typeContainer) {
@ -37,8 +48,29 @@ Q_DECL_EXPORT void net_instance_destroy(NetInstanceContainer* container) {
delete container;
}
Q_DECL_EXPORT NetInstanceContainer* net_instance_clone(NetInstanceContainer* container) {
NetInstanceContainer* result = new NetInstanceContainer{container->instance};
return result;
}
Q_DECL_EXPORT NetGCHandle* net_instance_getHandle(NetInstanceContainer* container) {
return container->instance->getGCHandle();
}
Q_DECL_EXPORT bool net_instance_activateSignal(NetInstanceContainer* container, LPWSTR signalName, NetVariantListContainer* parametersContainer) {
NetValue* existing = NetValue::forInstance(container->instance);
if(existing == NULL) {
// Not alive in the QML world, so no signals to raise.
return false;
}
QString signalNameString = QString::fromUtf16((const char16_t*)signalName);
QSharedPointer<NetVariantList> parameters;
if(parametersContainer != NULL) {
parameters = parametersContainer->list;
}
return existing->activateSignal(signalNameString, parameters);
}
}

View file

@ -10,6 +10,8 @@ public:
~NetInstance();
NetGCHandle* getGCHandle();
QSharedPointer<NetTypeInfo> getTypeInfo();
void release();
private:
NetGCHandle* gcHandle;
QSharedPointer<NetTypeInfo> typeInfo;

View file

@ -0,0 +1,55 @@
#include <QtNetCoreQml/types/NetSignalInfo.h>
#include <iostream>
NetSignalInfo::NetSignalInfo(QString name) :
_name(name) {
}
QString NetSignalInfo::getName() {
return _name;
}
void NetSignalInfo::addParameter(NetVariantTypeEnum type) {
if(type == NetVariantTypeEnum_Invalid) return;
_parameters.append(type);
}
uint NetSignalInfo::getParameterCount() {
return _parameters.size();
}
NetVariantTypeEnum NetSignalInfo::getParameter(uint index) {
if(index >= (uint)_parameters.length()) return NetVariantTypeEnum_Invalid;
return _parameters.at(index);
}
extern "C" {
Q_DECL_EXPORT NetSignalInfoContainer* signal_info_create(LPWSTR name) {
NetSignalInfoContainer* result = new NetSignalInfoContainer();
NetSignalInfo* instance = new NetSignalInfo(QString::fromUtf16((const char16_t*)name));
result->signal = QSharedPointer<NetSignalInfo>(instance);
return result;
}
Q_DECL_EXPORT void signal_info_destroy(NetSignalInfoContainer* container) {
delete container;
}
Q_DECL_EXPORT LPWSTR signal_info_getName(NetSignalInfoContainer* container) {
return (LPWSTR)container->signal->getName().utf16();
}
Q_DECL_EXPORT void signal_info_addParameter(NetSignalInfoContainer* container, NetVariantTypeEnum type) {
container->signal->addParameter(type);
}
Q_DECL_EXPORT uint signal_info_getParameterCount(NetSignalInfoContainer* container) {
return container->signal->getParameterCount();
}
Q_DECL_EXPORT NetVariantTypeEnum signal_info_getParameter(NetSignalInfoContainer* container, uint index) {
return container->signal->getParameter(index);
}
}

View file

@ -0,0 +1,24 @@
#ifndef NET_SIGNAL_INFO_METHOD_H
#define NET_SIGNAL_INFO_METHOD_H
#include <QtNetCoreQml.h>
#include <QtNetCoreQml/types/NetTypeInfo.h>
#include <QSharedPointer>
class NetSignalInfo {
public:
NetSignalInfo(QString name);
QString getName();
void addParameter(NetVariantTypeEnum type);
uint getParameterCount();
NetVariantTypeEnum getParameter(uint index);
private:
QString _name;
QList<NetVariantTypeEnum> _parameters;
};
struct NetSignalInfoContainer {
QSharedPointer<NetSignalInfo> signal;
};
#endif // NET_SIGNAL_INFO_METHOD_H

View file

@ -1,6 +1,7 @@
#include <QtNetCoreQml/types/NetTypeInfo.h>
#include <QtNetCoreQml/types/NetMethodInfo.h>
#include <QtNetCoreQml/types/NetPropertyInfo.h>
#include <QtNetCoreQml/types/NetSignalInfo.h>
NetTypeInfo::NetTypeInfo(QString fullTypeName) :
metaObject(NULL),
@ -60,6 +61,19 @@ QSharedPointer<NetPropertyInfo> NetTypeInfo::getProperty(uint index) {
return _properties.at(index);
}
void NetTypeInfo::addSignal(QSharedPointer<NetSignalInfo> signal) {
_signals.append(signal);
}
uint NetTypeInfo::getSignalCount() {
return _signals.size();
}
QSharedPointer<NetSignalInfo> NetTypeInfo::getSignal(uint index) {
if(index >= (uint)_signals.size()) return QSharedPointer<NetSignalInfo>(NULL);
return _signals.at(index);
}
extern "C" {
Q_DECL_EXPORT NetTypeInfoContainer* type_info_create(LPWSTR fullTypeName) {
@ -129,4 +143,22 @@ Q_DECL_EXPORT NetPropertyInfoContainer* type_info_getProperty(NetTypeInfoContain
return result;
}
Q_DECL_EXPORT void type_info_addSignal(NetTypeInfoContainer* container, NetSignalInfoContainer* signalContainer) {
container->netTypeInfo->addSignal(signalContainer->signal);
}
Q_DECL_EXPORT uint type_info_getSignalCount(NetTypeInfoContainer* container) {
return container->netTypeInfo->getSignalCount();
}
Q_DECL_EXPORT NetSignalInfoContainer* type_info_getSignal(NetTypeInfoContainer* container, uint index) {
QSharedPointer<NetSignalInfo> signal = container->netTypeInfo->getSignal(index);
if(signal == NULL) {
return NULL;
}
NetSignalInfoContainer* result = new NetSignalInfoContainer();
result->signal = signal;
return result;
}
}

View file

@ -8,6 +8,7 @@
class NetMethodInfo;
class NetPropertyInfo;
class NetSignalInfo;
class NetTypeInfo {
public:
@ -30,6 +31,10 @@ public:
uint getPropertyCount();
QSharedPointer<NetPropertyInfo> getProperty(uint index);
void addSignal(QSharedPointer<NetSignalInfo> signal);
uint getSignalCount();
QSharedPointer<NetSignalInfo> getSignal(uint index);
QMetaObject* metaObject;
private:
@ -38,6 +43,7 @@ private:
NetVariantTypeEnum _variantType;
QList<QSharedPointer<NetMethodInfo>> _methods;
QList<QSharedPointer<NetPropertyInfo>> _properties;
QList<QSharedPointer<NetSignalInfo>> _signals;
};
struct Q_DECL_EXPORT NetTypeInfoContainer {

View file

@ -4,7 +4,8 @@ SOURCES += \
$$PWD/Callbacks.cpp \
$$PWD/NetMethodInfo.cpp \
$$PWD/NetPropertyInfo.cpp \
$$PWD/NetInstance.cpp
$$PWD/NetInstance.cpp \
$$PWD/NetSignalInfo.cpp
HEADERS += \
$$PWD/NetTypeInfo.h \
@ -12,4 +13,5 @@ HEADERS += \
$$PWD/Callbacks.h \
$$PWD/NetMethodInfo.h \
$$PWD/NetPropertyInfo.h \
$$PWD/NetInstance.h
$$PWD/NetInstance.h \
$$PWD/NetSignalInfo.h

View file

@ -10,31 +10,74 @@ namespace Qt.NetCore.Sandbox
{
class Program
{
[Signal("testSignal", NetVariantType.String)]
public class TestQmlImport
{
public AnotherType Create()
{
return new AnotherType();
}
readonly AnotherType _anotherType = new AnotherType();
public void TestMethod(AnotherType anotherType)
public AnotherType GetSharedInstance()
{
return _anotherType;
}
}
[Signal("testSignal", NetVariantType.String)]
public class AnotherType
{
private static int _instanceCounter = 0;
public AnotherType()
}
public class InstanceType
{
public InstanceType()
{
Console.WriteLine($"AnotherType:{Interlocked.Increment(ref _instanceCounter)}");
}
~AnotherType()
public void Log(string logMessage)
{
Console.WriteLine($"AnotherType:{Interlocked.Decrement(ref _instanceCounter)}");
Console.WriteLine(logMessage);
}
}
public class TestQmlInstanceHandling
{
private InstanceType _instanceType;
private WeakReference<InstanceType> _weakInstanceTypeRef;
public int State { get; set; } = 0;
public TestQmlInstanceHandling()
{
_instanceType = new InstanceType();
_weakInstanceTypeRef = new WeakReference<InstanceType>(_instanceType);
}
public InstanceType GetInstance()
{
return _instanceType;
}
public void DeleteInstance()
{
_instanceType = null;
}
public void CreateNewInstance()
{
_instanceType = new InstanceType();
_weakInstanceTypeRef = new WeakReference<InstanceType>(_instanceType);
}
public bool IsInstanceAlive()
{
return _weakInstanceTypeRef.TryGetTarget(out var _);
}
public void GarbageCollect()
{
Thread.Sleep(1000);
GC.Collect(GC.MaxGeneration);
}
}
@ -55,7 +98,8 @@ namespace Qt.NetCore.Sandbox
engine.AddImportPath(Path.Combine(Directory.GetCurrentDirectory(), "Qml"));
QQmlApplicationEngine.RegisterType<TestQmlImport>("test");
QQmlApplicationEngine.RegisterType<TestQmlInstanceHandling>("testInstances");
engine.Load("main.qml");
return app.Exec();

View file

@ -9,6 +9,9 @@
<ProjectReference Include="..\Qt.NetCore\Qt.NetCore.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Images\placeholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="main.qml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View file

@ -2,19 +2,138 @@ import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import test 1.0
import MyModule 1.0 as MyModule
import testInstances 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Item {
Timer {
id: signalTimer
interval: 1000; running: true; repeat: true
onTriggered: {
console.log(Factorial.factorial(10))
var o = test.GetSharedInstance()
o.testSignal.connect(function(message) {
console.log("Signal was raised: " + message)
signalTimer.running = false
})
var o2 = test.GetSharedInstance()
o2.testSignal("Hello")
}
}
Timer {
id: instanceCheckTimer
property var instanceRef: null
interval: 1000; running: true; repeat: true
onTriggered: {
testInstances.GarbageCollect()
gc()
switch(testInstances.State) {
case 0:
console.log("Creating two QML references")
var ref1 = testInstances.GetInstance()
var ref2 = testInstances.GetInstance()
ref1 = null
ref2 = null
console.log("Created and deleted two references on QML side. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break
case 1:
testInstances.DeleteInstance()
console.log("Deleting .Net references. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break
case 2:
if(!testInstances.IsInstanceAlive()) {
console.log("Yeah! Instance has been released!");
testInstances.State++
}
break
case 3:
testInstances.CreateNewInstance()
console.log("Created new .Net Instance. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break
case 4:
instanceRef = testInstances.GetInstance()
var secondLocalRef = testInstances.GetInstance()
console.log("Created two QML refs. One scoped and one long living. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
//secondLocalRef will be freed here
break
case 5:
instanceRef.Log("Long living QML ref still works!")
testInstances.State++
break
case 6:
instanceRef.Log("Long living QML ref still works!")
testInstances.State++
break;
case 7:
console.log("Deleting long living QML ref. IsAlive = " + testInstances.IsInstanceAlive())
instanceRef = null
testInstances.State++
break;
case 8:
console.assert(testInstances.IsInstanceAlive(), ".Net object IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break;
case 9:
console.assert(testInstances.IsInstanceAlive(), ".Net object IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break;
case 10:
console.log("Releasing .Net instance a second time. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.DeleteInstance()
testInstances.State++
break;
case 11:
if(!testInstances.IsInstanceAlive()) {
console.log("Yeah! Instance has been released a second time!");
testInstances.State++
}
break;
case 12:
testInstances.CreateNewInstance()
console.log("Created new .Net Instance. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break
case 13:
instanceRef = testInstances.GetInstance()
testInstances.DeleteInstance()
console.log("a QML ref and deleted the .Net ref. IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break
case 14:
console.assert(testInstances.IsInstanceAlive(), ".Net object IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break;
case 15:
console.assert(testInstances.IsInstanceAlive(), ".Net object IsAlive = " + testInstances.IsInstanceAlive())
testInstances.State++
break;
case 16:
console.log("Releasing last QML ref. IsAlive = " + testInstances.IsInstanceAlive())
instanceRef = null
testInstances.State++
break;
case 17:
if(!testInstances.IsInstanceAlive()) {
console.log("Yeah! Instance has been released a third time!");
testInstances.State++
}
break;
default:
instanceCheckTimer.running = false
break;
}
}
}
}
@ -26,4 +145,8 @@ ApplicationWindow {
TestQmlImport {
id: test
}
TestQmlInstanceHandling {
id: testInstances
}
}

View file

@ -26,7 +26,7 @@ namespace Qt.NetCore.Tests.Qml
var testObject = new TestObject();
var variant = new NetVariant();
variant.Instance.Should().BeNull();
variant.Instance = NetInstance.CreateFromObject(testObject);
variant.Instance = NetInstance.GetForObject(testObject);
variant.Instance.Should().NotBeNull();
variant.Instance.Instance.Should().Be(testObject);
variant.VariantType.Should().Be(NetVariantType.Object);

View file

@ -0,0 +1,182 @@
using System;
using Moq;
using Qt.NetCore.Qml;
using Xunit;
namespace Qt.NetCore.Tests.Qml
{
public class SignalTests : BaseQmlTests<SignalTests.ObjectTestsQml>
{
public class ObjectTestsQml
{
public virtual SignalObject GetSignalObject()
{
return null;
}
public virtual bool SignalRaised { get; set; }
public virtual void MethodWithArgs(string arg1, int arg2)
{
}
public virtual void TestMethod()
{
}
}
[Signal("testSignal")]
[Signal("testSignalWithArgs", NetVariantType.String, NetVariantType.Int)]
public class SignalObject
{
}
[Fact]
public void Can_raise_signal_from_qml()
{
var signalObject = new SignalObject();
Mock.Setup(x => x.GetSignalObject()).Returns(signalObject);
Mock.Setup(x => x.SignalRaised).Returns(false);
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
ObjectTestsQml {
id: test
Component.onCompleted: function() {
var instance = test.GetSignalObject()
instance.testSignal.connect(function() {
test.SignalRaised = true
})
instance.testSignal()
}
}
");
Mock.VerifySet(x => x.SignalRaised = true, Times.Once);
}
[Fact]
public void Can_raise_signal_from_qml_with_args()
{
var signalObject = new SignalObject();
Mock.Setup(x => x.GetSignalObject()).Returns(signalObject);
Mock.Setup(x => x.SignalRaised).Returns(false);
Mock.Setup(x => x.MethodWithArgs("arg1", 3));
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
ObjectTestsQml {
id: test
Component.onCompleted: function() {
var instance = test.GetSignalObject()
instance.testSignalWithArgs.connect(function(arg1, arg2) {
test.SignalRaised = true
test.MethodWithArgs(arg1, arg2)
})
instance.testSignalWithArgs('arg1', 3)
}
}
");
Mock.VerifySet(x => x.SignalRaised = true, Times.Once);
Mock.Verify(x => x.MethodWithArgs("arg1", 3), Times.Once);
}
[Fact]
public void Can_raise_signal_from_qml_different_retrieval_of_net_instance()
{
var signalObject = new SignalObject();
Mock.Setup(x => x.GetSignalObject()).Returns(signalObject);
Mock.Setup(x => x.SignalRaised).Returns(false);
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
ObjectTestsQml {
id: test
Component.onCompleted: function() {
var instance1 = test.GetSignalObject()
instance1.testSignal.connect(function() {
test.SignalRaised = true
})
var instance2 = test.GetSignalObject()
instance2.testSignal()
}
}
");
Mock.VerifySet(x => x.SignalRaised = true, Times.Once);
}
[Fact]
public void Can_raise_signal_from_net()
{
var signalObject = new SignalObject();
Mock.Setup(x => x.GetSignalObject()).Returns(signalObject);
Mock.Setup(x => x.TestMethod()).Callback(() =>
{
signalObject.ActivateSignal("testSignal");
});
Mock.Setup(x => x.SignalRaised).Returns(false);
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
ObjectTestsQml {
id: test
Component.onCompleted: function() {
var instance1 = test.GetSignalObject()
instance1.testSignal.connect(function() {
test.SignalRaised = true
})
test.TestMethod()
}
}
");
Mock.VerifySet(x => x.SignalRaised = true, Times.Once);
}
[Fact]
public void Can_raise_signal_from_net_with_args()
{
var signalObject = new SignalObject();
Mock.Setup(x => x.GetSignalObject()).Returns(signalObject);
Mock.Setup(x => x.TestMethod()).Callback(() =>
{
signalObject.ActivateSignal("testSignalWithArgs", "arg1", 3);
});
Mock.Setup(x => x.SignalRaised).Returns(false);
Mock.Setup(x => x.MethodWithArgs("arg1", 3));
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
ObjectTestsQml {
id: test
Component.onCompleted: function() {
var instance1 = test.GetSignalObject()
instance1.testSignalWithArgs.connect(function(arg1, arg2) {
test.SignalRaised = true
test.MethodWithArgs(arg1, arg2)
})
test.TestMethod()
}
}
");
Mock.VerifySet(x => x.SignalRaised = true, Times.Once);
Mock.Verify(x => x.MethodWithArgs("arg1", 3), Times.Once);
}
}
}

View file

@ -37,7 +37,7 @@ namespace Qt.NetCore.Tests.Types
{
var o = new TestObject();
reference = new WeakReference(o);
instance = NetInstance.CreateFromObject(o);
instance = NetInstance.GetForObject(o);
}).Wait();
// NetInstance is still alive, so the weak reference must be alive as well.
@ -69,7 +69,7 @@ namespace Qt.NetCore.Tests.Types
var o = new TestObject();
var type = NetTypeManager.GetTypeInfo<TestObject>();
var method = type.GetMethod(0);
var instance = NetInstance.CreateFromObject(o);
var instance = NetInstance.GetForObject(o);
// This will jump to native, to then call the .NET delegate (round trip).
// The purpose is to simulate Qml invoking a method, sending .NET instance back.

View file

@ -15,20 +15,11 @@ namespace Qt.NetCore.Tests.Types
public void Can_create_net_instance()
{
var o = new TestObject();
var instance = NetInstance.CreateFromObject(o);
var instance = NetInstance.GetForObject(o);
var returnedInstance = instance.Instance;
o.Should().Be(returnedInstance);
}
[Fact]
public void Can_create_instance_from_type_info()
{
var typeInfo = NetTypeManager.GetTypeInfo<TestObject>();
var instance = NetInstance.InstantiateType(typeInfo);
instance.Should().NotBeNull();
instance.Instance.Should().BeOfType<TestObject>();
}
}
}

View file

@ -0,0 +1,24 @@
using FluentAssertions;
using Qt.NetCore.Types;
using Xunit;
namespace Qt.NetCore.Tests.Types
{
public class NetSignalInfoTests
{
[Fact]
public void Can_create_signal_info()
{
using (var signal = new NetSignalInfo("testSignal"))
{
signal.Name.Should().Be("testSignal");
signal.ParameterCount.Should().Be(0);
signal.GetParameter(0).Should().Be(NetVariantType.Invalid);
signal.AddParameter(NetVariantType.Double);
signal.ParameterCount.Should().Be(1);
signal.GetParameter(0).Should().Be(NetVariantType.Double);
signal.GetParameter(1).Should().Be(NetVariantType.Invalid);
}
}
}
}

View file

@ -12,5 +12,20 @@ namespace Qt.NetCore.Tests.Types
var typeInfo = new NetTypeInfo("fullTypeName");
typeInfo.FullTypeName.Should().Be("fullTypeName");
}
[Fact]
public void Can_add_signals()
{
var type = new NetTypeInfo("test");
var signal = new NetSignalInfo("signalName");
signal.AddParameter(NetVariantType.Bool);
type.GetSignal(0).Should().BeNull();
type.SignalCount.Should().Be(0);
type.AddSignal(signal);
type.SignalCount.Should().Be(1);
type.GetSignal(0).Name.Should().Be("signalName");
type.GetSignal(0).GetParameter(0).Should().Be(NetVariantType.Bool);
}
}
}

View file

@ -130,6 +130,25 @@ namespace Qt.NetCore.Tests.Types
property.ParentType.ClassName.Should().Be("TestType6");
}
[Signal("testSignal", NetVariantType.DateTime, NetVariantType.Object)]
public class TestType7
{
}
[Fact]
public void Can_get_signal()
{
var type = NetTypeManager.GetTypeInfo<TestType7>();
type.SignalCount.Should().Be(1);
var signal = type.GetSignal(0);
signal.Name.Should().Be("testSignal");
signal.ParameterCount.Should().Be(2);
signal.GetParameter(0).Should().Be(NetVariantType.DateTime);
signal.GetParameter(1).Should().Be(NetVariantType.Object);
}
[Fact]
public void Null_type_returned_for_invalid_type()
{

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Qt.NetCore.Qml;
@ -42,11 +43,11 @@ namespace Qt.NetCore.Internal
type.PrefVariantType = NetVariantType.Object;
// Don't grab properties and methods for system-level types.
if (IsPrimitive(typeInfo)) return;
if (Helpers.IsPrimitive(typeInfo)) return;
foreach (var methodInfo in typeInfo.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (IsPrimitive(methodInfo.DeclaringType)) continue;
if (Helpers.IsPrimitive(methodInfo.DeclaringType)) continue;
NetTypeInfo returnType = null;
@ -69,7 +70,7 @@ namespace Qt.NetCore.Internal
foreach (var propertyInfo in typeInfo.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (IsPrimitive(propertyInfo.DeclaringType)) continue;
if (Helpers.IsPrimitive(propertyInfo.DeclaringType)) continue;
using (var property = new NetPropertyInfo(
type,
@ -81,6 +82,18 @@ namespace Qt.NetCore.Internal
type.AddProperty(property);
}
}
foreach (var signalAttribute in typeInfo.GetCustomAttributes().OfType<SignalAttribute>())
{
using (var signal = new NetSignalInfo(signalAttribute.Name))
{
foreach (var parameter in signalAttribute.Parameters)
{
signal.AddParameter(parameter);
}
type.AddSignal(signal);
}
}
}
}
@ -99,10 +112,11 @@ namespace Qt.NetCore.Internal
var typeCreator = NetInstance.TypeCreator;
var instance = typeCreator != null ? typeCreator.Create(typeInfo) : Activator.CreateInstance(typeInfo);
var instanceHandle = GCHandle.Alloc(instance);
// NOTE: We DON'T wrap
return Interop.NetInstance.Create(GCHandle.ToIntPtr(instanceHandle), type);
var netInstance = NetInstance.GetForObject(instance);
// When .NET collects this NetInstance, we don't want it to delete this
// handle. Ownership has been passed to the caller.
return Interop.NetInstance.Clone(netInstance.Handle);
}
finally
{
@ -124,9 +138,7 @@ namespace Qt.NetCore.Internal
if(propertInfo == null)
throw new InvalidOperationException($"Invalid property {property.Name}");
var value = propertInfo.GetValue(o);
PackValue(ref value, result);
Helpers.PackValue(propertInfo.GetValue(o), result);
}
}
@ -145,7 +157,7 @@ namespace Qt.NetCore.Internal
throw new InvalidOperationException($"Invalid property {property.Name}");
object newValue = null;
Unpackvalue(ref newValue, value);
Helpers.Unpackvalue(ref newValue, value);
propertInfo.SetValue(o, newValue);
}
@ -169,7 +181,7 @@ namespace Qt.NetCore.Internal
for (var x = 0; x < parameterCount; x++)
{
object v = null;
Unpackvalue(ref v, parameters.Get(x));
Helpers.Unpackvalue(ref v, parameters.Get(x));
methodParameters.Add(v);
}
}
@ -181,91 +193,18 @@ namespace Qt.NetCore.Internal
{
throw new InvalidOperationException($"Invalid method name {method.MethodName}");
}
var returnValue = methodInfo.Invoke(instance, methodParameters?.ToArray());
var returnObject = methodInfo.Invoke(instance, methodParameters?.ToArray());
if (result == null)
{
// this method doesn't have return type
}
else
{
PackValue(ref returnValue, result);
Helpers.PackValue(returnObject, result);
}
}
}
private bool IsPrimitive(Type type)
{
if (type.Namespace == "System") return true;
if (type.Namespace == "System.Threading.Tasks") return true;
return false;
}
private void PackValue(ref object source, NetVariant destination)
{
if (source == null)
{
destination.Clear();
}
else
{
var type = source.GetType();
if (type == typeof(bool))
destination.Bool = (bool)source;
else if(type == typeof(char))
destination.Char = (char)source;
else if(type == typeof(double))
destination.Double = (double)source;
else if (type == typeof(int))
destination.Int = (int)source;
else if(type == typeof(uint))
destination.UInt = (uint)source;
else if (type == typeof(string))
destination.String = (string)source;
else if(type == typeof(DateTime))
destination.DateTime = (DateTime)source;
else
{
destination.Instance = NetInstance.CreateFromObject(source);
}
}
}
private void Unpackvalue(ref object destination, NetVariant source)
{
switch (source.VariantType)
{
case NetVariantType.Invalid:
destination = null;
break;
case NetVariantType.Bool:
destination = source.Bool;
break;
case NetVariantType.Char:
destination = source.Char;
break;
case NetVariantType.Int:
destination = source.Int;
break;
case NetVariantType.UInt:
destination = source.UInt;
break;
case NetVariantType.Double:
destination = source.Double;
break;
case NetVariantType.String:
destination = source.String;
break;
case NetVariantType.DateTime:
destination = source.DateTime;
break;
case NetVariantType.Object:
destination = source.Instance.Instance;
break;
default:
throw new Exception("Unsupported variant type.");
}
}
}
}

View file

@ -0,0 +1,82 @@
using System;
using Qt.NetCore.Qml;
using Qt.NetCore.Types;
namespace Qt.NetCore.Internal
{
internal static class Helpers
{
public static bool IsPrimitive(Type type)
{
if (type.Namespace == "System") return true;
if (type.Namespace == "System.Threading.Tasks") return true;
return false;
}
public static void PackValue(object source, NetVariant destination)
{
if (source == null)
{
destination.Clear();
}
else
{
var type = source.GetType();
if (type == typeof(bool))
destination.Bool = (bool)source;
else if(type == typeof(char))
destination.Char = (char)source;
else if(type == typeof(double))
destination.Double = (double)source;
else if (type == typeof(int))
destination.Int = (int)source;
else if(type == typeof(uint))
destination.UInt = (uint)source;
else if (type == typeof(string))
destination.String = (string)source;
else if(type == typeof(DateTime))
destination.DateTime = (DateTime)source;
else
{
destination.Instance = NetInstance.GetForObject(source);
}
}
}
public static void Unpackvalue(ref object destination, NetVariant source)
{
switch (source.VariantType)
{
case NetVariantType.Invalid:
destination = null;
break;
case NetVariantType.Bool:
destination = source.Bool;
break;
case NetVariantType.Char:
destination = source.Char;
break;
case NetVariantType.Int:
destination = source.Int;
break;
case NetVariantType.UInt:
destination = source.UInt;
break;
case NetVariantType.Double:
destination = source.Double;
break;
case NetVariantType.String:
destination = source.String;
break;
case NetVariantType.DateTime:
destination = source.DateTime;
break;
case NetVariantType.Object:
destination = source.Instance.Instance;
break;
default:
throw new Exception("Unsupported variant type.");
}
}
}
}

View file

@ -24,6 +24,7 @@ namespace Qt.NetCore
NetInstance = NativeLibraryBuilder.Default.ActivateInterface<INetInstanceInterop>("QtNetCoreQml");
NetVariantList = NativeLibraryBuilder.Default.ActivateInterface<INetVariantListInterop>("QtNetCoreQml");
NetTestHelper = NativeLibraryBuilder.Default.ActivateInterface<INetTestHelperInterop>("QtNetCoreQml");
NetSignalInfo = NativeLibraryBuilder.Default.ActivateInterface<INetSignalInfoInterop>("QtNetCoreQml");
QResource = NativeLibraryBuilder.Default.ActivateInterface<IQResourceInterop>("QtNetCoreQml");
var cb = DefaultCallbacks.Callbacks();
@ -52,6 +53,8 @@ namespace Qt.NetCore
public static INetTestHelperInterop NetTestHelper { get; }
public static INetSignalInfoInterop NetSignalInfo { get; }
public static IQResourceInterop QResource { get; set; }
public static void RegisterCallbacks(ICallbacks callbacks)

View file

@ -0,0 +1,19 @@
using System;
namespace Qt.NetCore
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class SignalAttribute : Attribute
{
public SignalAttribute(string name, params NetVariantType[] parameters)
{
Name = name;
Parameters = parameters;
}
public string Name { get; }
public NetVariantType[] Parameters { get; }
}
}

View file

@ -0,0 +1,17 @@
using Qt.NetCore.Types;
namespace Qt.NetCore
{
public static class Signals
{
public static bool ActivateSignal(this object instance, string signalName, params object[] args)
{
var existing = NetInstance.GetForObject(instance, false /*don't create one if doesn't exist*/);
if (existing == null)
{
return false;
}
return existing.ActivateSignal(signalName, args);
}
}
}

View file

@ -1,19 +1,16 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using AdvancedDLSupport;
using Qt.NetCore.Internal;
using Qt.NetCore.Qml;
namespace Qt.NetCore.Types
{
public class NetInstance : BaseDisposable
{
static NetInstance()
{
}
public NetInstance(IntPtr gcHandle, NetTypeInfo type)
:base(Interop.NetInstance.Create(gcHandle, type.Handle))
private NetInstance(IntPtr gcHandle, NetTypeInfo type, bool ownsHandle = true)
:base(Interop.NetInstance.Create(gcHandle, type.Handle), ownsHandle)
{
}
@ -23,22 +20,6 @@ namespace Qt.NetCore.Types
}
public static NetInstance CreateFromObject(object value)
{
if (value == null) return null;
var typeInfo = NetTypeManager.GetTypeInfo(GetUnproxiedType(value.GetType()).AssemblyQualifiedName);
if(typeInfo == null) throw new InvalidOperationException($"Couldn't create type info from {value.GetType().AssemblyQualifiedName}");
var handle = GCHandle.Alloc(value);
return new NetInstance(GCHandle.ToIntPtr(handle), typeInfo);
}
public static NetInstance InstantiateType(NetTypeInfo type)
{
var result = Interop.Callbacks.InstantiateType(type.Handle);
if (result == IntPtr.Zero) return null;
return new NetInstance(result);
}
public object Instance
{
get
@ -48,11 +29,41 @@ namespace Qt.NetCore.Types
}
}
public NetInstance Clone()
{
return new NetInstance(Interop.NetInstance.Clone(Handle));
}
public bool ActivateSignal(string signalName, params object[] parameters)
{
if (parameters != null && parameters.Length > 0)
{
using (var list = new NetVariantList())
{
foreach (var parameter in parameters)
{
using (var variant = new NetVariant())
{
Helpers.PackValue(parameter, variant);
list.Add(variant);
}
}
return Interop.NetInstance.ActivateSignal(Handle, signalName, list.Handle);
}
}
else
{
return Interop.NetInstance.ActivateSignal(Handle, signalName, IntPtr.Zero);
}
}
protected override void DisposeUnmanaged(IntPtr ptr)
{
Interop.NetInstance.Destroy(ptr);
}
#region Instance helpers
public static ITypeCreator TypeCreator { get; set; }
private static Type GetUnproxiedType(Type type)
@ -62,6 +73,42 @@ namespace Qt.NetCore.Types
return type;
}
private static readonly ConditionalWeakTable<object, NetInstance> ObjectNetInstanceConnections = new ConditionalWeakTable<object, NetInstance>();
public static bool ExistsForObject(object value)
{
return ObjectNetInstanceConnections.TryGetValue(value, out NetInstance netInstance);
}
public static NetInstance GetForObject(object value, bool autoCreate = true)
{
if (value == null) return null;
var alreadyExists = false;
if (ObjectNetInstanceConnections.TryGetValue(value, out var netInstance))
{
alreadyExists = true;
if (GCHandle.FromIntPtr(netInstance.Handle).IsAllocated)
{
return netInstance;
}
}
if (!autoCreate) return null;
var typeInfo = NetTypeManager.GetTypeInfo(GetUnproxiedType(value.GetType()).AssemblyQualifiedName);
if(typeInfo == null) throw new InvalidOperationException($"Couldn't create type info from {value.GetType().AssemblyQualifiedName}");
var handle = GCHandle.Alloc(value);
var newNetInstance = new NetInstance(GCHandle.ToIntPtr(handle), typeInfo);
if(alreadyExists)
{
ObjectNetInstanceConnections.Remove(value);
}
ObjectNetInstanceConnections.Add(value, newNetInstance);
return newNetInstance;
}
#endregion
}
public interface INetInstanceInterop
@ -70,8 +117,12 @@ namespace Qt.NetCore.Types
IntPtr Create(IntPtr handle, IntPtr type);
[NativeSymbol(Entrypoint = "net_instance_destroy")]
void Destroy(IntPtr instance);
[NativeSymbol(Entrypoint = "net_instance_clone")]
IntPtr Clone(IntPtr instance);
[NativeSymbol(Entrypoint = "net_instance_getHandle")]
IntPtr GetHandle(IntPtr instance);
[NativeSymbol(Entrypoint = "net_instance_activateSignal")]
bool ActivateSignal(IntPtr instance, [MarshalAs(UnmanagedType.LPWStr)]string signalName, IntPtr variants);
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
using AdvancedDLSupport;
using Qt.NetCore.Internal;
namespace Qt.NetCore.Types
{
public class NetSignalInfo : BaseDisposable
{
internal NetSignalInfo(IntPtr handle, bool ownsHandle = true)
: base(handle, ownsHandle)
{
}
public NetSignalInfo(string name)
: this(Interop.NetSignalInfo.Create(name))
{
}
public string Name => Interop.NetSignalInfo.GetName(Handle);
public void AddParameter(NetVariantType type)
{
Interop.NetSignalInfo.AddParameter(Handle, type);
}
public uint ParameterCount => Interop.NetSignalInfo.GetParameterCount(Handle);
public NetVariantType GetParameter(uint index)
{
return Interop.NetSignalInfo.GetParameter(Handle, index);
}
protected override void DisposeUnmanaged(IntPtr ptr)
{
Interop.NetSignalInfo.Destroy(ptr);
}
}
public interface INetSignalInfoInterop
{
[NativeSymbol(Entrypoint = "signal_info_create")]
IntPtr Create([MarshalAs(UnmanagedType.LPWStr)] string name);
[NativeSymbol(Entrypoint = "signal_info_destroy")]
void Destroy(IntPtr signal);
[NativeSymbol(Entrypoint = "signal_info_getName")]
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetName(IntPtr signal);
[NativeSymbol(Entrypoint = "signal_info_addParameter")]
void AddParameter(IntPtr signal, NetVariantType type);
[NativeSymbol(Entrypoint = "signal_info_getParameterCount")]
uint GetParameterCount(IntPtr signal);
[NativeSymbol(Entrypoint = "signal_info_getParameter")]
NetVariantType GetParameter(IntPtr signal, uint index);
}
}

View file

@ -59,6 +59,20 @@ namespace Qt.NetCore.Types
if (result == IntPtr.Zero) return null;
return new NetPropertyInfo(result);
}
public void AddSignal(NetSignalInfo signal)
{
Interop.NetTypeInfo.AddSignal(Handle, signal.Handle);
}
public uint SignalCount => Interop.NetTypeInfo.GetSignalCount(Handle);
public NetSignalInfo GetSignal(uint index)
{
var result = Interop.NetTypeInfo.GetSignal(Handle, index);
if (result == IntPtr.Zero) return null;
return new NetSignalInfo(result);
}
protected override void DisposeUnmanaged(IntPtr ptr)
{
@ -99,5 +113,12 @@ namespace Qt.NetCore.Types
uint GetPropertyCount(IntPtr typeInfo);
[NativeSymbol(Entrypoint = "type_info_getProperty")]
IntPtr GetProperty(IntPtr typeInfo, uint index);
[NativeSymbol(Entrypoint = "type_info_addSignal")]
void AddSignal(IntPtr typeInfo, IntPtr signal);
[NativeSymbol(Entrypoint = "type_info_getSignalCount")]
uint GetSignalCount(IntPtr typeInfo);
[NativeSymbol(Entrypoint = "type_info_getSignal")]
IntPtr GetSignal(IntPtr typeInfo, uint index);
}
}