Added support for serializing .NET objects to strings.

This commit is contained in:
Paul Knopf 2018-08-16 23:55:45 -04:00
parent cae6c8e4a0
commit 5a7bd17c21
13 changed files with 203 additions and 16 deletions

View file

@ -15,6 +15,7 @@ void Heap::NetObject::init() {
o->defineDefaultProperty(QStringLiteral("gcCollect"), QV4::NetObject::method_gccollect);
o->defineDefaultProperty(QStringLiteral("await"), QV4::NetObject::method_await);
o->defineDefaultProperty(QStringLiteral("cancelTokenSource"), QV4::NetObject::method_cancelTokenSource);
o->defineDefaultProperty(QStringLiteral("serialize"), QV4::NetObject::method_serialize);
}
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
@ -92,6 +93,34 @@ void NetObject::method_cancelTokenSource(const BuiltinFunction *, Scope &scope,
scope.result = QV4::QObjectWrapper::wrap(scope.engine, netValue);
}
void NetObject::method_serialize(const BuiltinFunction *, Scope &scope, CallData *callData)
{
qDebug("TESTdd");
if(callData->argc != 1) {
THROW_GENERIC_ERROR("Net.serialize(): Missing instance parameter");
}
ScopedValue instance(scope, callData->args[0]);
if(instance->isNullOrUndefined()) {
THROW_GENERIC_ERROR("Net.serialize(): Instance parameter must not be null or undefined");
}
QJSValue instanceJsValue(scope.engine, instance->asReturnedValue());
QSharedPointer<NetVariant> value = NetVariant::fromQJSValue(instanceJsValue);
if(value->getVariantType() != NetVariantTypeEnum_Object) {
THROW_GENERIC_ERROR("Net.serialize(): Parameter is not a .NET object");
}
QSharedPointer<NetVariant> result = QSharedPointer<NetVariant>(new NetVariant());
bool serializationResult = serializeNetToString(value->getNetReference(), result);
if(!serializationResult) {
THROW_GENERIC_ERROR("Net.serialize(): Could not serialize object.");
}
scope.result = scope.engine->newString(result->getString());
}
#else
ReturnedValue NetObject::method_gccollect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) {
@ -185,4 +214,28 @@ ReturnedValue NetObject::method_cancelTokenSource(const FunctionObject *b, const
return QV4::QObjectWrapper::wrap(scope.engine, netValue);
}
ReturnedValue NetObject::method_serialize(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
QV4::Scope scope(b);
if(argc != 1) {
THROW_GENERIC_ERROR("Net.serialize(): Missing instance parameter");
}
QV4::ScopedValue instance(scope, argv[0]);
if(instance->isNullOrUndefined()) {
THROW_GENERIC_ERROR("Net.serialize(): Instance parameter must not be null or undefined");
}
QJSValue instanceJsValue(scope.engine, instance->asReturnedValue());
QSharedPointer<NetVariant> value = NetVariant::fromQJSValue(instanceJsValue);
if(value->getVariantType() != NetVariantTypeEnum_Object) {
THROW_GENERIC_ERROR("Net.serialize(): Parameter is not a .NET object");
}
QSharedPointer<NetVariant> result = QSharedPointer<NetVariant>(new NetVariant());
bool serializationResult = serializeNetToString(value->getNetReference(), result);
if(!serializationResult) {
THROW_GENERIC_ERROR("Net.serialize(): Could not serialize object.");
}
return Encode(scope.engine->newString(result->getString()));
}
#endif

View file

@ -20,10 +20,12 @@ struct NetObject : Object
static void method_gccollect(const BuiltinFunction *, Scope &scope, CallData *callData);
static void method_await(const BuiltinFunction *, Scope &scope, CallData *callData);
static void method_cancelTokenSource(const BuiltinFunction *, Scope &scope, CallData *callData);
static void method_serialize(const BuiltinFunction *, Scope &scope, CallData *callData);
#else
static ReturnedValue method_gccollect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_await(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_cancelTokenSource(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_serialize(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
#endif
};

View file

@ -12,6 +12,7 @@ typedef void (*invokeMethodCb)(NetMethodInfoContainer* method, NetReferenceConta
typedef void (*gcCollectCb)(int maxGeneration);
typedef bool (*raiseNetSignalsCb)(NetReferenceContainer* target, LPWCSTR signalName, NetVariantListContainer* parameters);
typedef void (*awaitTaskCb)(NetReferenceContainer* target, NetJSValueContainer* successCallback, NetJSValueContainer* failureCallback);
typedef bool (*serializeNetToStringCb)(NetReferenceContainer* instance, NetVariantContainer* result);
struct Q_DECL_EXPORT NetTypeInfoManagerCallbacks {
isTypeValidCb isTypeValid;
@ -26,6 +27,7 @@ struct Q_DECL_EXPORT NetTypeInfoManagerCallbacks {
gcCollectCb gcCollect;
raiseNetSignalsCb raiseNetSignals;
awaitTaskCb awaitTask;
serializeNetToStringCb serializeNetToString;
};
static NetTypeInfoManagerCallbacks sharedCallbacks;
@ -136,6 +138,12 @@ void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> s
sharedCallbacks.awaitTask(targetContainer, sucessCallbackContainer, failureCallbackContainer);
}
bool serializeNetToString(QSharedPointer<NetReference> instance, QSharedPointer<NetVariant> result)
{
return sharedCallbacks.serializeNetToString(new NetReferenceContainer{instance},
new NetVariantContainer{result});
}
extern "C" {
Q_DECL_EXPORT void type_info_callbacks_registerCallbacks(NetTypeInfoManagerCallbacks* callbacks) {

View file

@ -35,4 +35,6 @@ bool raiseNetSignals(QSharedPointer<NetReference> target, QString signalName, QS
void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> successCallback, QSharedPointer<NetJSValue> failureCallback);
bool serializeNetToString(QSharedPointer<NetReference> instance, QSharedPointer<NetVariant> result);
#endif // NET_TYPE_INFO_MANAGER_H

View file

@ -9,19 +9,23 @@ namespace Qml.Net.Sandbox
{
class Program
{
[Signal("testSignal", NetVariantType.String)]
public class TestQmlImport
{
public void Test(INetJsValue jsValue)
public TestObject GetObject()
{
Console.WriteLine("In net method, invoking callback");
jsValue.Call();
return new TestObject
{
Prop1 = "test1",
Prop2 = 3
};
}
}
public void AnotherMethod()
{
Console.WriteLine("Another method");
}
public class TestObject
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
static int Main(string[] args)

View file

@ -10,14 +10,16 @@ ApplicationWindow {
title: qsTr("Hello World")
Item {
Timer {
interval: 5000; running: true; repeat: true
interval: 1000; running: true; repeat: true
onTriggered: {
console.log('calling function with callback')
var task = test.Test(function() {
console.log('in callback, going to call AnotherMethod')
test.AnotherMethod()
console.log('done calling AnotherMethod')
})
var netObject = test.getObject()
var toJson = Net.serialize(netObject)
console.log(toJson)
console.log(typeof toJson)
toJson = JSON.parse(toJson)
console.log(toJson)
console.log(typeof toJson)
console.log(toJson.prop1)
}
}
}

View file

@ -0,0 +1,46 @@
using Moq;
using Xunit;
namespace Qml.Net.Tests.Qml
{
public class SerializationTests : BaseQmlTests<SerializationTests.SerializationTestsQml>
{
public class SerializationTestsQml
{
public virtual JsonObject GetJsonObject()
{
return null;
}
public virtual string Result { get; set; }
}
public class JsonObject
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
[Fact]
public void Can_serialize_object()
{
var jsonObject = new JsonObject();
jsonObject.Prop1 = "test1";
jsonObject.Prop2 = 3;
var jsonObjectSerialized = Serializer.Current.Serialize(jsonObject);
Mock.Setup(x => x.GetJsonObject()).Returns(jsonObject);
Mock.SetupSet(x => x.Result = jsonObjectSerialized);
RunQmlTest(
"test",
@"
var jsonObject = test.getJsonObject()
test.result = Net.serialize(jsonObject)
");
Mock.Verify(x => x.GetJsonObject(), Times.Once);
Mock.VerifySet(x => x.Result = jsonObjectSerialized, Times.Once);
}
}
}

View file

@ -391,6 +391,25 @@ namespace Qml.Net.Internal
}
}
public bool Serialize(IntPtr i, IntPtr r)
{
using (var instance = new NetReference(i))
using (var result = new NetVariant(r))
{
try
{
result.String = Serializer.Current.Serialize(instance.Instance);
return true;
}
catch (Exception ex)
{
// TODO: Propagate this error to the user.
Console.Error.WriteLine($"Error serializing .NET object: {ex.Message}");
return false;
}
}
}
private NetVariantType GetPrefVariantType(Type typeInfo)
{
if (typeInfo == typeof(bool))

View file

@ -20,6 +20,7 @@ namespace Qml.Net.Internal.Types
public IntPtr GCCollect;
public IntPtr RaseNetSignals;
public IntPtr AwaitTask;
public IntPtr Serialize;
}
internal interface ICallbacksIterop
@ -74,6 +75,8 @@ namespace Qml.Net.Internal.Types
bool RaiseNetSignals(IntPtr target, string signalName, IntPtr parameters);
Task AwaitTask(IntPtr target, IntPtr succesCallback, IntPtr failureCallback);
bool Serialize(IntPtr instance, IntPtr result);
}
internal class CallbacksImpl
@ -91,6 +94,7 @@ namespace Qml.Net.Internal.Types
GCCollectDelegate _gcCollectDelegate;
RaiseNetSignalsDelegate _raiseNetSignalsDelegate;
AwaitTaskDelegate _awaitTaskDelegate;
SerializeDelegate _serializeDelegate;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate bool IsTypeValidDelegate([MarshalAs(UnmanagedType.LPWStr)]string typeName);
@ -128,6 +132,9 @@ namespace Qml.Net.Internal.Types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void AwaitTaskDelegate(IntPtr target, IntPtr successCallback, IntPtr failureCallback);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate bool SerializeDelegate(IntPtr instance, IntPtr result);
public CallbacksImpl(ICallbacks callbacks)
{
_callbacks = callbacks;
@ -167,6 +174,9 @@ namespace Qml.Net.Internal.Types
_awaitTaskDelegate = AwaitTask;
GCHandle.Alloc(_awaitTaskDelegate);
_serializeDelegate = Serialize;
GCHandle.Alloc(_serializeDelegate);
}
private bool IsTypeValid(string typeName)
@ -231,6 +241,11 @@ namespace Qml.Net.Internal.Types
_callbacks.AwaitTask(target, succesCallback, failureCallback);
}
private bool Serialize(IntPtr instance, IntPtr result)
{
return _callbacks.Serialize(instance, result);
}
public Callbacks Callbacks()
{
return new Callbacks
@ -246,7 +261,8 @@ namespace Qml.Net.Internal.Types
InvokeMethod = Marshal.GetFunctionPointerForDelegate(_invokeMethodDelegate),
GCCollect = Marshal.GetFunctionPointerForDelegate(_gcCollectDelegate),
RaseNetSignals = Marshal.GetFunctionPointerForDelegate(_raiseNetSignalsDelegate),
AwaitTask = Marshal.GetFunctionPointerForDelegate(_awaitTaskDelegate)
AwaitTask = Marshal.GetFunctionPointerForDelegate(_awaitTaskDelegate),
Serialize = Marshal.GetFunctionPointerForDelegate(_serializeDelegate)
};
}
}

View file

@ -4,5 +4,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdvancedDLSupport" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,7 @@
namespace Qml.Net.Serialization
{
public interface ISerializer
{
string Serialize(object value);
}
}

View file

@ -0,0 +1,18 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Qml.Net.Serialization
{
public class NewtonSerializer : ISerializer
{
private JsonSerializerSettings _defaultSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public string Serialize(object value)
{
return JsonConvert.SerializeObject(value, _defaultSerializerSettings);
}
}
}

View file

@ -0,0 +1,9 @@
using Qml.Net.Serialization;
namespace Qml.Net
{
public class Serializer
{
public static ISerializer Current = new NewtonSerializer();
}
}