diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs index 57a2cac48c6188..4c2a55c745810c 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs @@ -262,12 +262,12 @@ private struct IntPtrAndHandle internal RuntimeMethodHandle handle; } - public static string GetCallSignature(IntPtr methodHandle) + public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeType) { IntPtrAndHandle tmp = default(IntPtrAndHandle); tmp.ptr = methodHandle; - MethodBase? mb = MethodBase.GetMethodFromHandle(tmp.handle); + MethodBase? mb = objForRuntimeType == null ? MethodBase.GetMethodFromHandle(tmp.handle) : MethodBase.GetMethodFromHandle(tmp.handle, Type.GetTypeHandle(objForRuntimeType)); if (mb == null) return string.Empty; diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj index 11dec891deeeb0..e97ca510a53848 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj @@ -12,8 +12,9 @@ + - + diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs new file mode 100644 index 00000000000000..2414a6ca17ccd4 --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices.JavaScript; +using System.Collections.Generic; +using Xunit; + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public static class DelegateTests + { + private static Function _objectPrototype; + + [Fact] + public static void InvokeFunction() + { + HelperMarshal._functionResultValue = 0; + HelperMarshal._i32Value = 0; + + Runtime.InvokeJS(@" + var funcDelegate = App.call_test_method (""CreateFunctionDelegate"", [ ]); + var res = funcDelegate (10, 20); + App.call_test_method (""InvokeI32"", [ res, res ]); + "); + + Assert.Equal(30, HelperMarshal._functionResultValue); + Assert.Equal(60, HelperMarshal._i32Value); + } + + [Fact] + public static void InvokeFunctionInLoopUsingConstanceValues() + { + HelperMarshal._functionResultValue = 0; + HelperMarshal._i32Value = 0; + + Runtime.InvokeJS(@" + var funcDelegate = App.call_test_method (""CreateFunctionDelegate"", [ ]); + var res = funcDelegate (10, 20); + for (x = 0; x < 1000; x++) + { + res = funcDelegate (10, 20); + } + App.call_test_method (""InvokeI32"", [ res, res ]); + "); + + Assert.Equal(30, HelperMarshal._functionResultValue); + Assert.Equal(60, HelperMarshal._i32Value); + } + + [Fact] + public static void InvokeFunctionInLoopUsingIncrementedValues() + { + HelperMarshal._functionResultValue = 0; + HelperMarshal._i32Value = 0; + Runtime.InvokeJS(@" + var funcDelegate = App.call_test_method (""CreateFunctionDelegate"", [ ]); + var res = funcDelegate (10, 20); + for (x = 0; x < 1000; x++) + { + res = funcDelegate (x, x); + } + App.call_test_method (""InvokeI32"", [ res, res ]); + "); + + Assert.Equal(1998, HelperMarshal._functionResultValue); + Assert.Equal(3996, HelperMarshal._i32Value); + } + + [Fact] + public static void InvokeActionTReturnedByInvokingFuncT() + { + HelperMarshal._functionActionResultValue = 0; + HelperMarshal._functionActionResultValueOfAction = 0; + + Runtime.InvokeJS(@" + var funcDelegate = App.call_test_method (""CreateFunctionDelegateWithAction"", [ ]); + var actionDelegate = funcDelegate (10, 20); + actionDelegate(30,40); + "); + + Assert.Equal(30, HelperMarshal._functionActionResultValue); + Assert.Equal(70, HelperMarshal._functionActionResultValueOfAction); + } + + [Fact] + public static void InvokeActionIntInt() + { + HelperMarshal._actionResultValue = 0; + + Runtime.InvokeJS(@" + var actionDelegate = App.call_test_method (""CreateActionDelegate"", [ ]); + actionDelegate(30,40); + "); + + Assert.Equal(70, HelperMarshal._actionResultValue); + } + + [Fact] + public static void InvokeActionFloatIntToIntInt() + { + HelperMarshal._actionResultValue = 0; + Runtime.InvokeJS(@" + var actionDelegate = App.call_test_method (""CreateActionDelegate"", [ ]); + actionDelegate(3.14,40); + "); + + Assert.Equal(43, HelperMarshal._actionResultValue); + } + + [Fact] + public static void InvokeDelegateMethod() + { + HelperMarshal._delMethodResultValue = string.Empty; + Runtime.InvokeJS(@" + var del = App.call_test_method (""CreateDelegateMethod"", [ ]); + del(""Hic sunt dracones""); + "); + + Assert.Equal("Hic sunt dracones", HelperMarshal._delMethodResultValue); + } + + [Fact] + public static void InvokeDelegateMethodReturnString() + { + HelperMarshal._delMethodStringResultValue = string.Empty; + Runtime.InvokeJS(@" + var del = App.call_test_method (""CreateDelegateMethodReturnString"", [ ]); + var res = del(""Hic sunt dracones""); + App.call_test_method (""SetTestString1"", [ res ]); + "); + + Assert.Equal("Received: Hic sunt dracones", HelperMarshal._delMethodStringResultValue); + } + + [Theory] + [InlineData("CreateCustomMultiCastDelegate_VoidString", "Moin")] + [InlineData("CreateMultiCastAction_VoidString", "MoinMoin")] + public static void InvokeMultiCastDelegate_VoidString(string creator, string testStr) + { + HelperMarshal._delegateCallResult = string.Empty; + Runtime.InvokeJS($@" + var del = App.call_test_method (""{creator}"", [ ]); + del(""{testStr}""); + "); + Assert.Equal($" Hello, {testStr}! GoodMorning, {testStr}!", HelperMarshal._delegateCallResult); + } + + [Theory] + [InlineData("CreateDelegateFromAnonymousMethod_VoidString")] + [InlineData("CreateDelegateFromLambda_VoidString")] + [InlineData("CreateDelegateFromMethod_VoidString")] + [InlineData("CreateActionT_VoidString")] + public static void InvokeDelegate_VoidString(string creator) + { + HelperMarshal._delegateCallResult = string.Empty; + var s = Runtime.InvokeJS($@" + var del = App.call_test_method (""{creator}"", [ ]); + del(""Hic sunt dracones""); + "); + + Assert.Equal("Notification received for: Hic sunt dracones", HelperMarshal._delegateCallResult); + } + + public static IEnumerable ArrayType_TestData() + { + _objectPrototype ??= new Function("return Object.prototype.toString;"); + yield return new object[] { _objectPrototype.Call(), "Uint8Array", Uint8Array.From(new byte[10]) }; + yield return new object[] { _objectPrototype.Call(), "Uint8ClampedArray", Uint8ClampedArray.From(new byte[10]) }; + yield return new object[] { _objectPrototype.Call(), "Int8Array", Int8Array.From(new sbyte[10]) }; + yield return new object[] { _objectPrototype.Call(), "Uint16Array", Uint16Array.From(new ushort[10]) }; + yield return new object[] { _objectPrototype.Call(), "Int16Array", Int16Array.From(new short[10]) }; + yield return new object[] { _objectPrototype.Call(), "Uint32Array", Uint32Array.From(new uint[10]) }; + yield return new object[] { _objectPrototype.Call(), "Int32Array", Int32Array.From(new int[10]) }; + yield return new object[] { _objectPrototype.Call(), "Float32Array", Float32Array.From(new float[10]) }; + yield return new object[] { _objectPrototype.Call(), "Float64Array", Float64Array.From(new double[10]) }; + yield return new object[] { _objectPrototype.Call(), "Array", new Array(10) }; + } + + [Theory] + [MemberData(nameof(ArrayType_TestData))] + public static void InvokeFunctionAcceptingArrayTypes(Function objectPrototype, string creator, JSObject arrayType ) + { + HelperMarshal._funcActionBufferObjectResultValue = arrayType; + Assert.Equal(10, HelperMarshal._funcActionBufferObjectResultValue.Length); + Assert.Equal($"[object {creator}]", objectPrototype.Call(HelperMarshal._funcActionBufferObjectResultValue)); + + Runtime.InvokeJS($@" + var buffer = new {creator}(50); + var del = App.call_test_method (""CreateFunctionAccepting{creator}"", [ ]); + var setAction = del(buffer); + setAction(buffer); + "); + + Assert.Equal(50, HelperMarshal._funcActionBufferObjectResultValue.Length); + Assert.Equal(HelperMarshal._funcActionBufferObjectResultValue.Length, HelperMarshal._funcActionBufferResultLengthValue); + Assert.Equal($"[object {creator}]", objectPrototype.Call(HelperMarshal._funcActionBufferObjectResultValue)); + } + } +} diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs index 968d386c909214..8c2e8fe5527fe0 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs @@ -128,16 +128,6 @@ private static void UseAsFunction(Function func) _jsAddAsFunctionResult = (int)func.Call(null, 20, 30); } - internal static int _functionResultValue; - private static Func CreateFunctionDelegate() - { - return (a, b) => - { - _functionResultValue = a + b; - return _functionResultValue; - }; - } - internal static int _intValue; private static void InvokeInt(int value) { @@ -382,6 +372,259 @@ private static UInt64 GetUInt64() { return UInt64.MaxValue; } + + internal static int _functionResultValue; + private static Func CreateFunctionDelegate() + { + return (a, b) => + { + _functionResultValue = a + b; + return _functionResultValue; + }; + } + + internal static int _functionActionResultValue; + internal static int _functionActionResultValueOfAction; + private static Func> CreateFunctionDelegateWithAction() + { + return (a, b) => + { + _functionActionResultValue = a + b; + return (i1, i2) => + { + _functionActionResultValueOfAction = i1 + i2; + }; + }; + } + + internal static int _actionResultValue; + private static Action CreateActionDelegate() + { + return (a1, a2) => + { + _actionResultValue = a1 + a2; + }; + } + + private static bool AreEqual(int a, int b) + { + return a == b; + } + + private static string TestString1(string a) + { + return "Received: " + a; + } + + private static void SetTestString1(string a) + { + _delMethodStringResultValue = a; + } + + // Create a method for a delegate. + public static void DelegateMethod(string message) + { + _delMethodResultValue = message; + } + + delegate void Del(string message); + internal static string _delMethodResultValue; + private static Del CreateDelegateMethod() + { + // Instantiate the delegate. + Del handler = DelegateMethod; + return handler; + } + + delegate string Del2(string message); + internal static string _delMethodStringResultValue; + private static Del2 CreateDelegateMethodReturnString() + { + // Instantiate the delegate. + Del2 handler = TestString1; + return handler; + } + + internal static string _delegateCallResult; + private static Del CreateDelegateFromAnonymousMethod_VoidString() + { + // Instantiate the delegate. + Del handler = delegate(string name) { _delegateCallResult = $"Notification received for: {name}"; }; + return handler; + } + + private static Del CreateDelegateFromLambda_VoidString() + { + // Instantiate the delegate. + Del handler = (string name) => { _delegateCallResult = $"Notification received for: {name}"; }; + return handler; + } + + public static void DelegateMethod_VoidString(string name) => _delegateCallResult = $"Notification received for: {name}"; + + private static Del CreateDelegateFromMethod_VoidString() + { + // Instantiate the delegate. + Del handler = DelegateMethod_VoidString; + return handler; + } + + private static Action CreateActionT_VoidString() + => (string name) => _delegateCallResult = $"Notification received for: {name}"; + + static void Hello(string s) + { + _delegateCallResult += $" Hello, {s}!"; + } + + static void GoodMorning(string s) + { + _delegateCallResult += $" GoodMorning, {s}!"; + } + + delegate void CustomDelStr(string s); + private static CustomDelStr CreateCustomMultiCastDelegate_VoidString() + { + CustomDelStr hiDel, mornDel, multiDel; + hiDel = Hello; + mornDel = GoodMorning; + multiDel = hiDel + mornDel; + + return multiDel; + } + + private static Action CreateMultiCastAction_VoidString() + { + Action hiDel, mornDel, multiDel; + hiDel = Hello; + mornDel = GoodMorning; + multiDel = hiDel + mornDel; + + return multiDel; + } + + internal static JSObject _funcActionBufferObjectResultValue; + internal static int _funcActionBufferResultLengthValue; + private static Func> CreateFunctionAcceptingUint8ClampedArray() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingUint8Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingInt8Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingUint16Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingInt16Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingUint32Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingInt32Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingFloat32Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingFloat64Array() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + + private static Func> CreateFunctionAcceptingArray() + { + return (buffer) => + { + _funcActionBufferObjectResultValue = buffer; + return (i1) => + { + _funcActionBufferResultLengthValue = i1.Length; + }; + }; + } + } public enum TestEnum : uint { diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs index e15716f4f348e4..934673c31d2376 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs @@ -254,20 +254,6 @@ public static void JSObjectAsFunction() Assert.Equal(50, HelperMarshal._jsAddAsFunctionResult); } - [Fact] - public static void MarshalDelegate() - { - HelperMarshal._object1 = null; - Runtime.InvokeJS(@" - var funcDelegate = App.call_test_method (""CreateFunctionDelegate"", [ ]); - var res = funcDelegate (10, 20); - App.call_test_method (""InvokeI32"", [ res, res ]); - "); - - Assert.Equal(30, HelperMarshal._functionResultValue); - Assert.Equal(60, HelperMarshal._i32Value); - } - [Fact] public static void BindStaticMethod() { diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 34dac476e1ea5d..4617772d64bbe9 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -69,6 +69,7 @@ var BindingSupportLib = { this.mono_wasm_box_primitive = Module.cwrap ('mono_wasm_box_primitive', 'number', ['number', 'number', 'number']); this.mono_wasm_intern_string = Module.cwrap ('mono_wasm_intern_string', 'number', ['number']); this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']); + this.mono_wasm_get_delegate_invoke = Module.cwrap ('mono_wasm_get_delegate_invoke', 'number', ['number']); this._box_buffer = Module._malloc(16); this._unbox_buffer = Module._malloc(16); @@ -780,10 +781,10 @@ var BindingSupportLib = { return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); }, - mono_method_get_call_signature: function(method) { + mono_method_get_call_signature: function(method, mono_obj) { this.bindings_lazy_init (); - return this.call_method (this.get_call_sig, null, "i", [ method ]); + return this.call_method (this.get_call_sig, null, "im", [ method, mono_obj ]); }, get_task_and_bind: function (tcs, js_obj) { @@ -1457,25 +1458,19 @@ var BindingSupportLib = { throw new Error("The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd."); } - var [delegateRoot, argsRoot] = MONO.mono_wasm_new_roots ([this.extract_mono_obj (delegate_obj), undefined]); + var [delegateRoot] = MONO.mono_wasm_new_roots ([this.extract_mono_obj (delegate_obj)]); try { - if (!this.delegate_dynamic_invoke) { - if (!this.corlib) - this.corlib = this.assembly_load ("System.Private.CoreLib"); - if (!this.delegate_class) - this.delegate_class = this.find_class (this.corlib, "System", "Delegate"); - if (!this.delegate_class) - { - throw new Error("System.Delegate class can not be resolved."); - } - this.delegate_dynamic_invoke = this.find_method (this.delegate_class, "DynamicInvoke", -1); - } - argsRoot.value = this.js_array_to_mono_array (js_args); - if (!this.delegate_dynamic_invoke) - throw new Error("System.Delegate.DynamicInvoke method can not be resolved."); - return this.call_method (this.delegate_dynamic_invoke, delegateRoot.value, "m", [ argsRoot.value ]); + if (typeof delegate_obj.__mono_delegate_invoke__ === "undefined") + delegate_obj.__mono_delegate_invoke__ = this.mono_wasm_get_delegate_invoke(delegateRoot.value); + if (!delegate_obj.__mono_delegate_invoke__) + throw new Error("System.Delegate Invoke method can not be resolved."); + + if (typeof delegate_obj.__mono_delegate_invoke_sig__ === "undefined") + delegate_obj.__mono_delegate_invoke_sig__ = Module.mono_method_get_call_signature (delegate_obj.__mono_delegate_invoke__, delegateRoot.value); + + return this.call_method (delegate_obj.__mono_delegate_invoke__, delegateRoot.value, delegate_obj.__mono_delegate_invoke_sig__, js_args); } finally { - MONO.mono_wasm_release_roots (delegateRoot, argsRoot); + MONO.mono_wasm_release_roots (delegateRoot); } }, diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 9b6d6836fca7c0..c2ed732aab881d 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -602,6 +602,12 @@ mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int argument return mono_class_get_method_from_name (klass, name, arguments); } +EMSCRIPTEN_KEEPALIVE MonoMethod* +mono_wasm_get_delegate_invoke (MonoObject *delegate) +{ + return mono_get_delegate_invoke(mono_object_get_class (delegate)); +} + EMSCRIPTEN_KEEPALIVE MonoObject* mono_wasm_box_primitive (MonoClass *klass, void *value, int value_size) {