diff options
author | Kenneth Pouncey <kjpou@pt.lu> | 2018-08-07 12:50:16 +0300 |
---|---|---|
committer | Alexander Köplinger <alex.koeplinger@outlook.com> | 2018-08-07 12:50:16 +0300 |
commit | 00f64d2998c36660cbc4dd848fe25d1a7343630a (patch) | |
tree | 5d8da98e860a563813badcedc4e7cff31597e105 /sdks | |
parent | 371de430b67c1bbda41884c3d28ef3b9d3154182 (diff) |
Wasm Bindings enhancements (#9865)
* Fix Tasks and Promises in bindings.
- Tasks were not being marshalled correctly in some circumstances. The only place that I could reproduce this was in the bindings tests. Running from outside the test harness would not reproduce this error.
- Promises would intermittently throw Exceptions: NullReferenceException, ArrayIndex out of bounds.
- When using Promises from JS to CS there were random Memory access exceptions. Looks like memory was being trampled on. Adding an integer variable to the create_task_completion_source call fixed these problems as the CreateTaskSource is defined as receiving an int variable. This also fixed the NullReferenceExceptions and ArrayIndex out of bounds exceptions.
* Make JSObject disposable
- Add the ability to dispose of a JSObject.
- The dispose will remove the object from the tracked object list and will allow the reference counter to be recycled later.
- By keeping a recycled list of references and reusing those will keep the tracked objects array from growing out of hand.
- Add test to the Test Harness.
- Modifiy the way the bindings init works by passing in of the Assembly and Class `[asm]namespace.class`. Modfiy test.js to use the new format.
* Add support for Get and Set of javascript properties on a JSObject.
* The namespace problem was fixed in https://github.com/mono/mono/pull/9836
* Cleanup unused and duplicate definitions
Diffstat (limited to 'sdks')
-rw-r--r-- | sdks/README.md | 10 | ||||
-rw-r--r-- | sdks/wasm/binding_support.js | 191 | ||||
-rw-r--r-- | sdks/wasm/bindings-test.cs | 184 | ||||
-rw-r--r-- | sdks/wasm/bindings.cs | 100 | ||||
-rw-r--r-- | sdks/wasm/driver.c | 20 | ||||
-rw-r--r-- | sdks/wasm/test.js | 2 |
6 files changed, 472 insertions, 35 deletions
diff --git a/sdks/README.md b/sdks/README.md index 16621e5b379..b7da0d0dccb 100644 --- a/sdks/README.md +++ b/sdks/README.md @@ -44,11 +44,11 @@ make build-bcl package-bcl Go to the `wasm` directory for building and testing WebAssembly. Right now the following targets are available: - build: Build the test runner and test suites -- run-mini: Run mini test suite -- run-corlib: Run corlib test suite -- run-system: Run System test suite -- run-system-core: Run System.Core test suite - +- run-all-mini: Run mini test suite +- run-all-corlib: Run corlib test suite +- run-all-system: Run System test suite +- run-all-system-core: Run System.Core test suite +- run-all-binding: Run bindings test suite For bcl or runtime changes, you must manually run the corresponding build/package steps in `builds`. For test suite changes, it's enough to just rerun the local target. diff --git a/sdks/wasm/binding_support.js b/sdks/wasm/binding_support.js index 8d898709a62..7e5ca206d4c 100644 --- a/sdks/wasm/binding_support.js +++ b/sdks/wasm/binding_support.js @@ -2,9 +2,10 @@ var BindingSupportLib = { $BINDING__postset: 'BINDING.export_functions (Module);', $BINDING: { - BINDING_ASM: "binding_tests", - js_objects_table: [], - + BINDING_ASM: "[binding_tests]WebAssembly.Runtime", + mono_wasm_object_registry: [], + mono_wasm_ref_counter: 0, + mono_wasm_free_list: [], mono_bindings_init: function (binding_asm) { this.BINDING_ASM = binding_asm; }, @@ -36,19 +37,38 @@ var BindingSupportLib = { this.mono_obj_array_new = Module.cwrap ('mono_wasm_obj_array_new', 'number', ['number']); this.mono_obj_array_set = Module.cwrap ('mono_wasm_obj_array_set', 'void', ['number', 'number', 'number']); - this.binding_module = this.assembly_load (this.BINDING_ASM); - var wasm_runtime_class = this.find_class (this.binding_module, "WebAssembly", "Runtime") + var binding_fqn_asm = this.BINDING_ASM.substring(this.BINDING_ASM.indexOf ("[") + 1, this.BINDING_ASM.indexOf ("]")).trim(); + var binding_fqn_class = this.BINDING_ASM.substring (this.BINDING_ASM.indexOf ("]") + 1).trim(); + + this.binding_module = this.assembly_load (binding_fqn_asm); + if (!this.binding_module) + throw "Can't find bindings module assembly: " + binding_fqn_asm; + + if (binding_fqn_class !== null && typeof binding_fqn_class !== "undefined") + { + var namespace = "WebAssembly"; + var classname = binding_fqn_class.length > 0 ? binding_fqn_class : "Runtime"; + if (binding_fqn_class.indexOf(".") != -1) { + var idx = binding_fqn_class.lastIndexOf("."); + namespace = binding_fqn_class.substring (0, idx); + classname = binding_fqn_class.substring (idx + 1); + } + } + + var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname) if (!wasm_runtime_class) - throw "Can't find WebAssembly.Runtime class"; + throw "Can't find " + binding_fqn_class + " class"; var get_method = function(method_name) { var res = BINDING.find_method (wasm_runtime_class, method_name, -1) if (!res) - throw "Can't find method WebAssembly.Runtime:" + method_name; + throw "Can't find method " + namespace + "." + classname + ":" + method_name; return res; } this.bind_js_obj = get_method ("BindJSObject"); this.bind_existing_obj = get_method ("BindExistingObject"); + this.unbind_js_obj = get_method ("UnBindJSObject"); + this.unbind_js_obj_and_fee = get_method ("UnBindJSObjectAndFree"); this.get_js_id = get_method ("GetJSObjectId"); this.get_raw_mono_obj = get_method ("GetMonoObject"); @@ -68,7 +88,7 @@ var BindingSupportLib = { get_js_obj: function (js_handle) { if (js_handle > 0) - return this.js_objects_table [js_handle - 1]; + return this.mono_wasm_require_handle(js_handle); return null; }, @@ -125,6 +145,10 @@ var BindingSupportLib = { }; } case 6: {// Task + + if (typeof Promise === "undefined" || typeof Promise.resolve === "undefined") + throw new Error ("Promises are not supported thus C# Tasks can not work in this context."); + var obj = this.extract_js_obj (mono_obj); var cont_obj = null; var promise = new Promise (function (resolve, reject) { @@ -150,7 +174,7 @@ var BindingSupportLib = { }, create_task_completion_source: function () { - return this.call_method (this.create_tcs, null, "", []); + return this.call_method (this.create_tcs, null, "i", [ -1 ]); }, set_task_result: function (tcs, result) { @@ -205,6 +229,16 @@ var BindingSupportLib = { return this.call_method (this.bind_existing_obj, null, "mi", [mono_obj, js_id]); }, + wasm_unbind_js_obj: function (js_obj_id) + { + return this.call_method (this.unbind_js_obj, null, "i", [js_obj_id]); + }, + + wasm_unbind_js_obj_and_free: function (js_obj_id) + { + return this.call_method (this.unbind_js_obj_and_fee, null, "i", [js_obj_id]); + }, + wasm_get_js_id: function (mono_obj) { return this.call_method (this.get_js_id, null, "m", [mono_obj]); @@ -216,7 +250,7 @@ var BindingSupportLib = { }, try_extract_mono_obj:function (js_obj) { - if (js_obj == null || js_obj == undefined || !js_obj.__mono_gchandle__) + if (js_obj === null || typeof js_obj === "undefined" || typeof js_obj.__mono_gchandle__ === "undefined") return 0; return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); }, @@ -228,20 +262,20 @@ var BindingSupportLib = { }, get_task_and_bind: function (tcs, js_obj) { - var task_gchandle = this.call_method (this.tcs_get_task_and_bind, null, "oi", [ tcs, this.js_objects_table.length + 1 ]); + var gc_handle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; + var task_gchandle = this.call_method (this.tcs_get_task_and_bind, null, "oi", [ tcs, gc_handle + 1 ]); js_obj.__mono_gchandle__ = task_gchandle; - this.js_objects_table.push (js_obj); + this.mono_wasm_object_registry[gc_handle] = js_obj; return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); }, extract_mono_obj: function (js_obj) { - //halp JS ppl, is this enough? - if (js_obj == null || js_obj == undefined) + //help JS ppl, is this enough? + if (js_obj === null || typeof js_obj === "undefined") return 0; if (!js_obj.__mono_gchandle__) { - js_obj.__mono_gchandle__ = this.wasm_binding_obj_new(this.js_objects_table.length + 1); - this.js_objects_table.push(js_obj); + this.mono_wasm_register_obj(js_obj); } return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); @@ -253,15 +287,15 @@ var BindingSupportLib = { var js_id = this.wasm_get_js_id (mono_obj); if (js_id > 0) - return this.js_objects_table [js_id - 1]; + return this.mono_wasm_require_handle(js_id); + var gcHandle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; var js_obj = { - __mono_gchandle__: this.wasm_bind_existing(mono_obj, this.js_objects_table.length + 1), + __mono_gchandle__: this.wasm_bind_existing(mono_obj, gcHandle + 1), is_mono_bridged_obj: true }; - this.js_objects_table.push(js_obj); - + this.mono_wasm_object_registry[gcHandle] = js_obj; return js_obj; }, @@ -401,7 +435,46 @@ var BindingSupportLib = { return function() { return BINDING.call_method (method, null, signature, arguments); }; - } + }, + // Object wrapping helper functions to handle reference handles that will + // be used in managed code. + mono_wasm_register_obj: function(obj) { + + var gc_handle = undefined; + if (obj !== null && typeof obj !== "undefined") { + gc_handle = obj.__mono_gchandle__; + if (typeof gc_handle === "undefined") { + var handle = this.mono_wasm_free_list.length ? + this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; + gc_handle = handle + 1; + obj.__mono_gchandle__ = this.wasm_binding_obj_new(gc_handle); + + } + this.mono_wasm_object_registry[handle] = obj; + } + return gc_handle; + }, + mono_wasm_require_handle: function(handle) { + if (handle > 0) + return this.mono_wasm_object_registry[handle - 1]; + return null; + }, + mono_wasm_unregister_obj: function(js_id) { + var obj = this.mono_wasm_object_registry[js_id - 1] + if (typeof obj !== "undefined" && obj !== null) { + var gc_handle = obj.__mono_gchandle__; + if (typeof gc_handle !== "undefined") { + this.wasm_unbind_js_obj_and_free(js_id); + delete obj.__mono_gchandle__; + this.mono_wasm_free_list.push(js_id - 1); + return obj; + } + } + return null; + }, + mono_wasm_free_handle: function(handle) { + this.mono_wasm_unregister_obj(handle); + }, }, mono_wasm_invoke_js_with_args: function(js_handle, method_name, args, is_exception) { @@ -434,6 +507,82 @@ var BindingSupportLib = { return BINDING.js_string_to_mono_string (res); } }, + mono_wasm_get_object_property: function(js_handle, property_name, is_exception) { + BINDING.bindings_lazy_init (); + + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var js_name = BINDING.conv_string (property_name); + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + js_name + "'"); + } + + var res; + try { + var m = obj [js_name]; + if (m === Object(m) && obj.__is_mono_proxied__) + m.__is_mono_proxied__ = true; + + return BINDING.js_to_mono_obj (m); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + }, + mono_wasm_set_object_property: function (js_handle, property_name, value, createIfNotExist, hasOwnProperty, is_exception) { + + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var property = BINDING.conv_string (property_name); + if (!property) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'"); + } + + var result = false; + + var js_value = BINDING.unbox_mono_obj(value); + + if (createIfNotExist) { + requireObject[property] = js_value; + result = true; + } + else { + result = false; + if (!createIfNotExist) + { + if (!requireObject.hasOwnProperty(property)) + return false; + } + if (hasOwnProperty === true) { + if (requireObject.hasOwnProperty(property)) { + requireObject[property] = js_value; + result = true; + } + } + else { + requireObject[property] = js_value; + result = true; + } + + } + return BINDING.call_method (BINDING.box_js_bool, null, "im", [ result ]); + }, + }; autoAddDeps(BindingSupportLib, '$BINDING') diff --git a/sdks/wasm/bindings-test.cs b/sdks/wasm/bindings-test.cs index c3774240bf9..120d5bce10e 100644 --- a/sdks/wasm/bindings-test.cs +++ b/sdks/wasm/bindings-test.cs @@ -100,6 +100,21 @@ public class TestClass { return task; } + public static TaskCompletionSource<object> tcs3; + public static Task task3; + public static object MkTaskNull () { + tcs3 = new TaskCompletionSource<object> (); + task3 = tcs3.Task; + return task3; + } + + public static Task<object> taskString; + public static object MkTaskString () { + tcs3 = new TaskCompletionSource<object> (); + taskString = tcs3.Task; + return taskString; + } + public static Task<object> the_promise; public static void InvokePromise (object obj) { the_promise = (Task<object>)obj; @@ -107,6 +122,31 @@ public class TestClass { Console.WriteLine ("Promise result is {0}", t.Result); }, null, TaskContinuationOptions.ExecuteSynchronously); //must be sync cuz the mainloop pump is gone } + + public static List<JSObject> js_objs_to_dispose = new List<JSObject>(); + public static void DisposeObject(JSObject obj) + { + js_objs_to_dispose.Add(obj); + obj.Dispose(); + } + + public static object[] js_props; + public static void RetrieveObjectProperties (JSObject obj) { + js_props = new object[4]; + js_props [0] = obj.GetObjectProperty ("myInt"); + js_props [1] = obj.GetObjectProperty ("myDouble"); + js_props [2] = obj.GetObjectProperty ("myString"); + js_props [3] = obj.GetObjectProperty ("myBoolean"); + } + + public static void PopulateObjectProperties (JSObject obj, bool createIfNotExist) { + js_props = new object[4]; + obj.SetObjectProperty ("myInt", 100, createIfNotExist); + obj.SetObjectProperty ("myDouble", 4.5, createIfNotExist); + obj.SetObjectProperty ("myString", "qwerty", createIfNotExist); + obj.SetObjectProperty ("myBoolean", true, createIfNotExist); + } + } [TestFixture] @@ -248,14 +288,14 @@ public class BindingTests { Runtime.InvokeJS (@" var tsk = call_test_method (""MkTask"", """", [ ]); tsk.then (function (value) { - print ('PassTaskToJS cont with value ' + value); + Module.print ('PassTaskToJS cont with value ' + value); }); "); Assert.AreEqual (0, TestClass.int_val); TestClass.tcs.SetResult (99); //FIXME our test harness doesn't suppport async tests. // So manually verify it for now by checking stdout for `PassTaskToJS cont with value 99` - // Assert.AreEqual (99, TestClass.int_val); + //Assert.AreEqual (99, TestClass.int_val); } @@ -266,7 +306,7 @@ public class BindingTests { var tsk = call_test_method (""MkTask"", """", [ ]); tsk.then (function (value) {}, function (reason) { - print ('PassTaskToJS2 cont failed due to ' + reason); + Module.print ('PassTaskToJS2 cont failed due to ' + reason); }); "); Assert.AreEqual (0, TestClass.int_val); @@ -276,6 +316,65 @@ public class BindingTests { // Assert.AreEqual (99, TestClass.int_val); } + [Test] + public static void PassTaskToJS3 () { + TestClass.int_val = 0; + Runtime.InvokeJS (@" + var tsk = call_test_method (""MkTaskNull"", """", [ ]); + tsk.then( () => { + Module.print('PassTaskToJS3 cont without value '); // Success! + }, reason => { + Module.print('PassTaskToJS3 cont failed due to ' + reason); // Error! + } ); + "); + Assert.AreEqual (0, TestClass.int_val); + TestClass.tcs3.SetResult (null); + } + + [Test] + public static void PassTaskToJS4 () { + TestClass.int_val = 0; + Runtime.InvokeJS (@" + var tsk = call_test_method (""MkTaskNull"", """", [ ]); + tsk.then( value => { + Module.print(value); // Success! + }, reason => { + Module.print('PassTaskToJS4 cont failed due to ' + reason); // Error! + } ); + "); + Assert.AreEqual (0, TestClass.int_val); + TestClass.tcs3.SetException (new Exception ("it failed")); + } + + [Test] + public static void PassTaskToJS5 () { + TestClass.int_val = 0; + Runtime.InvokeJS (@" + var tsk = call_test_method (""MkTaskString"", """", [ ]); + tsk.then( success => { + Module.print('PassTaskToJS5 cont with value ' + success); // Success! + }, reason => { + Module.print('PassTaskToJS5 cont failed due to ' + reason); // Error! + } ); + "); + Assert.AreEqual (0, TestClass.int_val); + TestClass.tcs3.SetResult ("Success"); + } + + [Test] + public static void PassTaskToJS6 () { + TestClass.int_val = 0; + Runtime.InvokeJS (@" + var tsk = call_test_method (""MkTaskString"", """", [ ]); + tsk.then( success => { + Module.print('PassTaskToJS6 cont with value ' + success); // Success! + }, reason => { + Module.print('PassTaskToJS6 cont failed due to ' + reason); // Error! + } ); + "); + Assert.AreEqual (0, TestClass.int_val); + TestClass.tcs3.SetException (new Exception ("it failed")); + } [Test] public static void PassPromiseToCS () { @@ -324,4 +423,83 @@ public class BindingTests { Assert.AreNotEqual (0, TestClass.int_val); } + + [Test] + public static void DisposeObject () { + Runtime.InvokeJS (@" + var obj1 = { + }; + var obj2 = { + }; + var obj3 = { + }; + call_test_method (""DisposeObject"", ""o"", [ obj3 ]); + call_test_method (""DisposeObject"", ""o"", [ obj2 ]); + call_test_method (""DisposeObject"", ""o"", [ obj1 ]); + "); + + Assert.AreEqual (-1, TestClass.js_objs_to_dispose [0].JSHandle); + Assert.AreEqual (-1, TestClass.js_objs_to_dispose [1].JSHandle); + Assert.AreEqual (-1, TestClass.js_objs_to_dispose [2].JSHandle); + } + + [Test] + public static void GetObjectProperties () { + Runtime.InvokeJS (@" + var obj = {myInt: 100, myDouble: 4.5, myString: ""qwerty"", myBoolean: true}; + call_test_method (""RetrieveObjectProperties"", ""o"", [ obj ]); + "); + + Assert.AreEqual (100, TestClass.js_props [0]); + Assert.AreEqual (4.5, TestClass.js_props [1]); + Assert.AreEqual ("qwerty", TestClass.js_props [2]); + Assert.AreEqual (true, TestClass.js_props [3]); + } + + [Test] + public static void SetObjectProperties () { + Runtime.InvokeJS (@" + var obj = {myInt: 200, myDouble: 0, myString: ""foo"", myBoolean: false}; + call_test_method (""PopulateObjectProperties"", ""oi"", [ obj, false ]); + call_test_method (""RetrieveObjectProperties"", ""o"", [ obj ]); + "); + + Assert.AreEqual (100, TestClass.js_props [0]); + Assert.AreEqual (4.5, TestClass.js_props [1]); + Assert.AreEqual ("qwerty", TestClass.js_props [2]); + Assert.AreEqual (true, TestClass.js_props [3]); + } + + [Test] + public static void SetObjectPropertiesIfNotExistsFalse () { + // This test will not create the properties if they do not already exist + Runtime.InvokeJS (@" + var obj = {myInt: 200}; + call_test_method (""PopulateObjectProperties"", ""oi"", [ obj, false ]); + call_test_method (""RetrieveObjectProperties"", ""o"", [ obj ]); + "); + + Assert.AreEqual (100, TestClass.js_props [0]); + Assert.AreEqual (null, TestClass.js_props [1]); + Assert.AreEqual (null, TestClass.js_props [2]); + Assert.AreEqual (null, TestClass.js_props [3]); + } + + [Test] + public static void SetObjectPropertiesIfNotExistsTrue () { + // This test will set the value of the property if it exists and will create and + // set the value if it does not exists + Runtime.InvokeJS (@" + var obj = {myInt: 200}; + call_test_method (""PopulateObjectProperties"", ""oi"", [ obj, true ]); + call_test_method (""RetrieveObjectProperties"", ""o"", [ obj ]); + "); + + Assert.AreEqual (100, TestClass.js_props [0]); + Assert.AreEqual (4.5, TestClass.js_props [1]); + Assert.AreEqual ("qwerty", TestClass.js_props [2]); + Assert.AreEqual (true, TestClass.js_props [3]); + } + + } diff --git a/sdks/wasm/bindings.cs b/sdks/wasm/bindings.cs index 333a0dadfbc..9306fbfcb2a 100644 --- a/sdks/wasm/bindings.cs +++ b/sdks/wasm/bindings.cs @@ -23,6 +23,10 @@ namespace WebAssembly { [MethodImplAttribute (MethodImplOptions.InternalCall)] internal static extern object InvokeJSWithArgs (int js_obj_handle, string method, object[] _params, out int exceptional_result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object GetObjectProperty(int js_obj_handle, string propertyName, out int exceptional_result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object SetObjectProperty(int js_obj_handle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptional_result); public static string InvokeJS (string str) { @@ -46,6 +50,36 @@ namespace WebAssembly { return (int)(IntPtr)obj.Handle; } + static int UnBindJSObject(int js_id) + { + if (bound_objects.ContainsKey(js_id)) + { + var obj = bound_objects[js_id]; + bound_objects.Remove(js_id); + return (int)(IntPtr)obj.Handle; + } + + return 0; + + } + + static int UnBindJSObjectAndFree(int js_id) + { + if (bound_objects.ContainsKey(js_id)) + { + var obj = bound_objects[js_id]; + bound_objects.Remove(js_id); + var gCHandle = obj.Handle; + gCHandle.Free(); + obj.JSHandle = -1; + return (int)(IntPtr)gCHandle; + } + + return 0; + + } + + static object CreateTaskSource (int js_id) { return new TaskCompletionSource<object> (); } @@ -132,6 +166,7 @@ namespace WebAssembly { case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: + case TypeCode.Boolean: res += "i"; break; case TypeCode.Int64: @@ -191,11 +226,14 @@ namespace WebAssembly { public JSException (string msg) : base (msg) {} } - public class JSObject { + public class JSObject : IDisposable { internal int JSHandle; internal GCHandle Handle; internal object RawObject; + // Flag: Has Dispose already been called? + bool disposed = false; + internal JSObject (int js_handle) { this.JSHandle = js_handle; this.Handle = GCHandle.Alloc (this); @@ -215,8 +253,68 @@ namespace WebAssembly { return res; } + + public object GetObjectProperty(string expr) + { + + int exception = 0; + var propertyValue = Runtime.GetObjectProperty(JSHandle, expr, out exception); + + if (exception != 0) + throw new JSException((string)propertyValue); + + return propertyValue; + + } + + public void SetObjectProperty(string expr, object value, bool createIfNotExists = true, bool hasOwnProperty = false) + { + + int exception = 0; + var setPropResult = Runtime.SetObjectProperty(JSHandle, expr, value, createIfNotExists, hasOwnProperty, out exception); + if (exception != 0) + throw new JSException($"Error setting {expr} on (js-obj js '{JSHandle}' mono '{(IntPtr)Handle} raw '{RawObject != null})"); + + } + + public override string ToString () { return $"(js-obj js '{JSHandle}' mono '{(IntPtr)Handle} raw '{RawObject != null})"; } + + + protected void FreeHandle() + { + + Runtime.InvokeJS("BINDING.mono_wasm_free_handle(" + JSHandle + ");"); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (disposing) + { + + // Free any other managed objects here. + // + } + + // Free any unmanaged objects here. + // + FreeHandle(); + + disposed = true; + } } } diff --git a/sdks/wasm/driver.c b/sdks/wasm/driver.c index 41b8bf835ff..ee339bae7fa 100644 --- a/sdks/wasm/driver.c +++ b/sdks/wasm/driver.c @@ -87,6 +87,8 @@ typedef struct _MonoAssemblyName MonoAssemblyName; //JS funcs extern MonoObject* mono_wasm_invoke_js_with_args (int js_handle, MonoString *method, MonoArray *args, int *is_exception); +extern MonoObject* mono_wasm_get_object_property (int js_handle, MonoString *method, int *is_exception); +extern MonoObject* mono_wasm_set_object_property (int js_handle, MonoString *method, MonoObject *value, int createIfNotExist, int hasOwnProperty, int *is_exception); // Blazor specific custom routines - see dotnet_support.js for backing code extern void* mono_wasm_invoke_js_marshalled (MonoString **exceptionMessage, void *asyncHandleLongPtr, MonoString *funcName, MonoString *argsJson); @@ -119,7 +121,6 @@ MonoAssembly* mono_assembly_load (MonoAssemblyName *aname, const char *basedir, MonoAssemblyName* mono_assembly_name_new (const char *name); void mono_assembly_name_free (MonoAssemblyName *aname); const char* mono_image_get_name (MonoImage *image); -const char* mono_class_get_name (MonoClass *klass); MonoString* mono_string_new (MonoDomain *domain, const char *text); void mono_add_internal_call (const char *name, const void* method); MonoString * mono_string_from_utf16 (char *data); @@ -131,8 +132,6 @@ int mono_class_is_delegate (MonoClass* klass); const char* mono_class_get_name (MonoClass *klass); const char* mono_class_get_namespace (MonoClass *klass); - - #define mono_array_get(array,type,index) ( *(type*)mono_array_addr ((array), type, (index)) ) #define mono_array_addr(array,type,index) ((type*)(void*) mono_array_addr_with_size (array, sizeof (type), index)) #define mono_array_setref(array,index,value) \ @@ -210,6 +209,8 @@ mono_wasm_load_runtime (const char *managed_path, int enable_debugging) mono_add_internal_call ("WebAssembly.Runtime::InvokeJS", mono_wasm_invoke_js); mono_add_internal_call ("WebAssembly.Runtime::InvokeJSWithArgs", mono_wasm_invoke_js_with_args); + mono_add_internal_call ("WebAssembly.Runtime::GetObjectProperty", mono_wasm_get_object_property); + mono_add_internal_call ("WebAssembly.Runtime::SetObjectProperty", mono_wasm_set_object_property); // Blazor specific custom routines - see dotnet_support.js for backing code mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJSMarshalled", mono_wasm_invoke_js_marshalled); @@ -276,6 +277,16 @@ mono_wasm_string_from_js (const char *str) } +static int +class_is_task (MonoClass *klass) +{ + if (!strcmp ("System.Threading.Tasks", mono_class_get_namespace (klass)) && + (!strcmp ("Task", mono_class_get_name (klass)) || !strcmp ("Task`1", mono_class_get_name (klass)))) + return 1; + + return 0; +} + #define MARSHAL_TYPE_INT 1 #define MARSHAL_TYPE_FP 2 #define MARSHAL_TYPE_STRING 3 @@ -316,8 +327,9 @@ mono_wasm_get_obj_type (MonoObject *obj) return MARSHAL_TYPE_VT; if (mono_class_is_delegate (klass)) return MARSHAL_TYPE_DELEGATE; - if (!strcmp ("System.Threading.Tasks", mono_class_get_namespace (klass)) && (!strcmp ("Task", mono_class_get_name (klass)) || !strcmp ("Task`1", mono_class_get_name (klass)))) + if (class_is_task(klass)) return MARSHAL_TYPE_TASK; + return MARSHAL_TYPE_OBJECT; } } diff --git a/sdks/wasm/test.js b/sdks/wasm/test.js index aab5570b96b..32eadfb59f8 100644 --- a/sdks/wasm/test.js +++ b/sdks/wasm/test.js @@ -123,7 +123,7 @@ if (!send_message) //Ok, this is temporary //this is a super big hack (move it to a decently named assembly, at the very least) var binding_test_module = assembly_load ("binding_tests"); -Module.mono_bindings_init("binding_tests"); +Module.mono_bindings_init("[binding_tests]WebAssembly.Runtime"); //binding test suite support code var binding_test_class = find_class (binding_test_module, "", "TestClass"); |