Assert.Same(HelperMarshal._object1, HelperMarshal._object2);
}
+ [Theory]
+ [InlineData(byte.MinValue)]
+ [InlineData(byte.MaxValue)]
+ [InlineData(SByte.MinValue)]
+ [InlineData(SByte.MaxValue)]
+ [InlineData(uint.MaxValue)]
+ [InlineData(uint.MinValue)]
+ [InlineData(int.MaxValue)]
+ [InlineData(int.MinValue)]
+ [InlineData(double.MaxValue)]
+ [InlineData(double.MinValue)]
+ public static void InvokeUnboxNumberString(object o)
+ {
+ HelperMarshal._marshalledObject = o;
+ HelperMarshal._object1 = HelperMarshal._object2 = null;
+ var value = Runtime.InvokeJS(@"
+ var obj = App.call_test_method (""InvokeReturnMarshalObj"");
+ var res = App.call_test_method (""InvokeObj1"", [ obj.toString() ]);
+ ");
+
+ Assert.Equal(o.ToString().ToLower(), HelperMarshal._object1);
+ }
+
+ [Theory]
+ [InlineData(byte.MinValue, 0)]
+ [InlineData(byte.MaxValue, 255)]
+ [InlineData(SByte.MinValue, -128)]
+ [InlineData(SByte.MaxValue, 127)]
+ [InlineData(uint.MaxValue)]
+ [InlineData(uint.MinValue, 0)]
+ [InlineData(int.MaxValue)]
+ [InlineData(int.MinValue)]
+ [InlineData(double.MaxValue)]
+ [InlineData(double.MinValue)]
+ public static void InvokeUnboxNumber(object o, object expected = null)
+ {
+ HelperMarshal._marshalledObject = o;
+ HelperMarshal._object1 = HelperMarshal._object2 = null;
+ Runtime.InvokeJS(@"
+ var obj = App.call_test_method (""InvokeReturnMarshalObj"");
+ var res = App.call_test_method (""InvokeObj1"", [ obj ]);
+ ");
+
+ Assert.Equal(expected ?? o, HelperMarshal._object1);
+ }
+
+ [Theory]
+ [InlineData(byte.MinValue, 0)]
+ [InlineData(byte.MaxValue, 255)]
+ [InlineData(SByte.MinValue, -128)]
+ [InlineData(SByte.MaxValue, 127)]
+ [InlineData(uint.MaxValue)]
+ [InlineData(uint.MinValue, 0)]
+ [InlineData(int.MaxValue)]
+ [InlineData(int.MinValue)]
+ [InlineData(double.MaxValue)]
+ [InlineData(double.MinValue)]
+ public static void InvokeUnboxStringNumber(object o, object expected = null)
+ {
+ HelperMarshal._marshalledObject = HelperMarshal._object1 = HelperMarshal._object2 = null;
+ Runtime.InvokeJS(String.Format (@"
+ var res = App.call_test_method (""InvokeObj1"", [ {0} ]);
+ ", o));
+
+ Assert.Equal (expected ?? o, HelperMarshal._object1);
+ }
+
[Fact]
public static void JSInvokeInt()
{
Assert.Equal(30, HelperMarshal._functionResultValue);
Assert.Equal(60, HelperMarshal._i32Value);
}
+
[Fact]
public static void BindStaticMethod()
{
}
[Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
public static void MarshalTypedArray()
{
Runtime.InvokeJS(@"
Assert.Equal(7.5f, HelperMarshal._doubleBuffer[3]);
}
- [Fact]
- public static void MarshalTypedArraySByte()
- {
+ private static void RunMarshalTypedArrayJS(string type) {
Runtime.InvokeJS(@"
var obj = { };
- App.call_test_method (""SetTypedArraySByte"", [ obj ]);
- App.call_test_method (""GetTypedArraySByte"", [ obj ]);
+ App.call_test_method (""SetTypedArray" + type + @""", [ obj ]);
+ App.call_test_method (""GetTypedArray" + type + @""", [ obj ]);
");
+ }
+
+ [Fact]
+ public static void MarshalTypedArraySByte()
+ {
+ RunMarshalTypedArrayJS("SByte");
Assert.Equal(11, HelperMarshal._taSByte.Length);
Assert.Equal(32, HelperMarshal._taSByte[0]);
Assert.Equal(32, HelperMarshal._taSByte[HelperMarshal._taSByte.Length - 1]);
}
[Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
public static void MarshalTypedArrayByte()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayByte"", [ obj ]);
- App.call_test_method (""GetTypedArrayByte"", [ obj ]);
- ");
- Assert.Equal(11, HelperMarshal._taSByte.Length);
+ RunMarshalTypedArrayJS("Byte");
+ Assert.Equal(17, HelperMarshal._taByte.Length);
Assert.Equal(104, HelperMarshal._taByte[0]);
Assert.Equal(115, HelperMarshal._taByte[HelperMarshal._taByte.Length - 1]);
Assert.Equal("hic sunt dracones", System.Text.Encoding.Default.GetString(HelperMarshal._taByte));
[Fact]
public static void MarshalTypedArrayShort()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayShort"", [ obj ]);
- App.call_test_method (""GetTypedArrayShort"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("Short");
Assert.Equal(13, HelperMarshal._taShort.Length);
Assert.Equal(32, HelperMarshal._taShort[0]);
Assert.Equal(32, HelperMarshal._taShort[HelperMarshal._taShort.Length - 1]);
[Fact]
public static void MarshalTypedArrayUShort()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayUShort"", [ obj ]);
- App.call_test_method (""GetTypedArrayUShort"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("UShort");
Assert.Equal(14, HelperMarshal._taUShort.Length);
Assert.Equal(32, HelperMarshal._taUShort[0]);
Assert.Equal(32, HelperMarshal._taUShort[HelperMarshal._taUShort.Length - 1]);
[Fact]
public static void MarshalTypedArrayInt()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayInt"", ""o"", [ obj ]);
- App.call_test_method (""GetTypedArrayInt"", ""o"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("Int");
Assert.Equal(15, HelperMarshal._taInt.Length);
Assert.Equal(32, HelperMarshal._taInt[0]);
Assert.Equal(32, HelperMarshal._taInt[HelperMarshal._taInt.Length - 1]);
[Fact]
public static void MarshalTypedArrayUInt()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayUInt"", [ obj ]);
- App.call_test_method (""GetTypedArrayUInt"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("UInt");
Assert.Equal(16, HelperMarshal._taUInt.Length);
Assert.Equal(32, (int)HelperMarshal._taUInt[0]);
Assert.Equal(32, (int)HelperMarshal._taUInt[HelperMarshal._taUInt.Length - 1]);
[Fact]
public static void MarshalTypedArrayFloat()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayFloat"", [ obj ]);
- App.call_test_method (""GetTypedArrayFloat"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("Float");
Assert.Equal(17, HelperMarshal._taFloat.Length);
Assert.Equal(3.14f, HelperMarshal._taFloat[0]);
Assert.Equal(3.14f, HelperMarshal._taFloat[HelperMarshal._taFloat.Length - 1]);
[Fact]
public static void MarshalTypedArrayDouble()
{
- Runtime.InvokeJS(@"
- var obj = { };
- App.call_test_method (""SetTypedArrayDouble"", ""o"", [ obj ]);
- App.call_test_method (""GetTypedArrayDouble"", ""o"", [ obj ]);
- ");
+ RunMarshalTypedArrayJS("Double");
Assert.Equal(18, HelperMarshal._taDouble.Length);
Assert.Equal(3.14d, HelperMarshal._taDouble[0]);
Assert.Equal(3.14d, HelperMarshal._taDouble[HelperMarshal._taDouble.Length - 1]);
{
HelperMarshal._sumValue = 0;
Runtime.InvokeJS(@"
- App.call_test_method (""CreateFunctionSum"", null, [ ]);
- App.call_test_method (""CallFunctionSum"", null, [ ]);
+ App.call_test_method (""CreateFunctionSum"", []);
+ App.call_test_method (""CallFunctionSum"", []);
");
Assert.Equal(8, HelperMarshal._sumValue);
}
[Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
public static void TestFunctionApply()
{
HelperMarshal._minValue = 0;
Runtime.InvokeJS(@"
- App.call_test_method (""CreateFunctionApply"", null, [ ]);
- App.call_test_method (""CallFunctionApply"", null, [ ]);
+ App.call_test_method (""CreateFunctionApply"", []);
+ App.call_test_method (""CallFunctionApply"", []);
");
Assert.Equal(2, HelperMarshal._minValue);
}
+
+ [Fact]
+ public static void BoundStaticMethodMissingArgs()
+ {
+ // TODO: We currently have code that relies on this behavior (missing args default to 0) but
+ // it would be better if it threw an exception about the missing arguments. This test is here
+ // to ensure we do not break things by accidentally changing this behavior -kg
+
+ HelperMarshal._intValue = 1;
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int ();
+ ");
+ Assert.Equal(0, HelperMarshal._intValue);
+ }
+
+ [Fact]
+ public static void BoundStaticMethodExtraArgs()
+ {
+ HelperMarshal._intValue = 0;
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int (200, 400);
+ ");
+ Assert.Equal(200, HelperMarshal._intValue);
+ }
+
+ [Fact]
+ public static void BoundStaticMethodArgumentTypeCoercion()
+ {
+ // TODO: As above, the type coercion behavior on display in this test is not ideal, but
+ // changing it risks breakage in existing code so for now it is verified by a test -kg
+
+ HelperMarshal._intValue = 0;
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int (""200"");
+ ");
+ Assert.Equal(200, HelperMarshal._intValue);
+
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int (400.5);
+ ");
+ Assert.Equal(400, HelperMarshal._intValue);
+ }
+
+ [Fact]
+ public static void BoundStaticMethodUnpleasantArgumentTypeCoercion()
+ {
+ HelperMarshal._intValue = 100;
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int (""hello"");
+ ");
+ Assert.Equal(0, HelperMarshal._intValue);
+
+ // In this case at the very least, the leading "7" is not turned into the number 7
+ Runtime.InvokeJS(@$"
+ var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+ invoke_int (""7apples"");
+ ");
+ Assert.Equal(0, HelperMarshal._intValue);
+ }
+
+ [Fact]
+ public static void PassUintArgument()
+ {
+ HelperMarshal._uintValue = 0;
+ Runtime.InvokeJS(@$"
+ var invoke_uint = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeUInt"");
+ invoke_uint (0xFFFFFFFE);
+ ");
+
+ Assert.Equal(0xFFFFFFFEu, HelperMarshal._uintValue);
+ }
+
+ [Fact]
+ public static void ReturnUintEnum ()
+ {
+ HelperMarshal._uintValue = 0;
+ HelperMarshal._enumValue = TestEnum.BigValue;
+ Runtime.InvokeJS(@$"
+ var get_value = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetEnumValue"");
+ var e = get_value ();
+ var invoke_uint = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeUInt"");
+ invoke_uint (e);
+ ");
+ Assert.Equal((uint)TestEnum.BigValue, HelperMarshal._uintValue);
+ }
+
+ [Fact]
+ public static void PassUintEnumByValue ()
+ {
+ HelperMarshal._enumValue = TestEnum.Zero;
+ Runtime.InvokeJS(@$"
+ var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j"");
+ set_enum (0xFFFFFFFE);
+ ");
+ Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
+ }
+
+ [Fact]
+ public static void PassUintEnumByValueMasqueradingAsInt ()
+ {
+ HelperMarshal._enumValue = TestEnum.Zero;
+ // HACK: We're explicitly telling the bindings layer to pass an int here, not an enum
+ // Because we know the enum is : uint, this is compatible, so it works.
+ Runtime.InvokeJS(@$"
+ var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""i"");
+ set_enum (0xFFFFFFFE);
+ ");
+ Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
+ }
+
+ [Fact]
+ public static void PassUintEnumByNameIsNotImplemented ()
+ {
+ HelperMarshal._enumValue = TestEnum.Zero;
+ var exc = Assert.Throws<JSException>( () =>
+ Runtime.InvokeJS(@$"
+ var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j"");
+ set_enum (""BigValue"");
+ ")
+ );
+ Assert.StartsWith("Error: Expected numeric value for enum argument, got 'BigValue'", exc.Message);
+ }
+
+ [Fact]
+ public static void CannotUnboxUint64 ()
+ {
+ var exc = Assert.Throws<JSException>( () =>
+ Runtime.InvokeJS(@$"
+ var get_u64 = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetUInt64"", """");
+ var u64 = get_u64();
+ ")
+ );
+ Assert.StartsWith("Error: int64 not available", exc.Message);
+ }
}
}
mono_wasm_free_list: [],
mono_wasm_owned_objects_frames: [],
mono_wasm_owned_objects_LMF: [],
- mono_wasm_marshal_enum_as_int: false,
+ mono_wasm_marshal_enum_as_int: true,
mono_bindings_init: function (binding_asm) {
this.BINDING_ASM = binding_asm;
},
export_functions: function (module) {
module ["mono_bindings_init"] = BINDING.mono_bindings_init.bind(BINDING);
+ module ["mono_bind_method"] = BINDING.bind_method.bind(BINDING);
module ["mono_method_invoke"] = BINDING.call_method.bind(BINDING);
module ["mono_method_get_call_signature"] = BINDING.mono_method_get_call_signature.bind(BINDING);
module ["mono_method_resolve"] = BINDING.resolve_method_fqn.bind(BINDING);
bindings_lazy_init: function () {
if (this.init)
return;
+
+ // avoid infinite recursion
+ this.init = true;
Array.prototype[Symbol.for("wasm type")] = 1;
ArrayBuffer.prototype[Symbol.for("wasm type")] = 2;
Float64Array.prototype[Symbol.for("wasm type")] = 18;
this.assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']);
+ this.find_corlib_class = Module.cwrap ('mono_wasm_find_corlib_class', 'number', ['string', 'string']);
this.find_class = Module.cwrap ('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']);
- this.find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']);
+ this._find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']);
this.invoke_method = Module.cwrap ('mono_wasm_invoke_method', 'number', ['number', 'number', 'number', 'number']);
this.mono_string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'number', ['number']);
this.mono_wasm_string_from_utf16 = Module.cwrap ('mono_wasm_string_from_utf16', 'number', ['number', 'number']);
this.mono_get_obj_type = Module.cwrap ('mono_wasm_get_obj_type', 'number', ['number']);
- this.mono_unbox_int = Module.cwrap ('mono_unbox_int', 'number', ['number']);
- this.mono_unbox_float = Module.cwrap ('mono_wasm_unbox_float', 'number', ['number']);
this.mono_array_length = Module.cwrap ('mono_wasm_array_length', 'number', ['number']);
this.mono_array_get = Module.cwrap ('mono_wasm_array_get', 'number', ['number', 'number']);
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.mono_wasm_register_bundled_satellite_assemblies = Module.cwrap ('mono_wasm_register_bundled_satellite_assemblies', 'void', [ ]);
- this.mono_unbox_enum = Module.cwrap ('mono_wasm_unbox_enum', 'number', ['number']);
+ this.mono_wasm_try_unbox_primitive_and_get_type = Module.cwrap ('mono_wasm_try_unbox_primitive_and_get_type', 'number', ['number', 'number']);
+ this.mono_wasm_box_primitive = Module.cwrap ('mono_wasm_box_primitive', 'number', ['number', 'number', 'number']);
this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']);
+
+ this._box_buffer = Module._malloc(16);
+ this._unbox_buffer = Module._malloc(16);
+ this._class_int32 = this.find_corlib_class ("System", "Int32");
+ this._class_uint32 = this.find_corlib_class ("System", "UInt32");
+ this._class_double = this.find_corlib_class ("System", "Double");
+ this._class_boolean = this.find_corlib_class ("System", "Boolean");
// receives a byteoffset into allocated Heap with a size.
this.mono_typed_array_new = Module.cwrap ('mono_wasm_typed_array_new', 'number', ['number','number','number','number']);
if (!this.binding_module)
throw "Can't find bindings module assembly: " + binding_fqn_asm;
+ var namespace = null, classname = null;
if (binding_fqn_class !== null && typeof binding_fqn_class !== "undefined")
{
- var namespace = "System.Runtime.InteropServices.JavaScript";
- var classname = binding_fqn_class.length > 0 ? binding_fqn_class : "Runtime";
+ namespace = "System.Runtime.InteropServices.JavaScript";
+ 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);
}
}
- var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname)
+ var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname);
if (!wasm_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)
+ var res = BINDING.find_method (wasm_runtime_class, method_name, -1);
if (!res)
throw "Can't find method " + namespace + "." + classname + ":" + method_name;
return res;
- }
- this.bind_js_obj = get_method ("BindJSObject");
- this.bind_core_clr_obj = get_method ("BindCoreCLRObject");
- this.bind_existing_obj = get_method ("BindExistingObject");
- this.unbind_raw_obj_and_free = get_method ("UnBindRawJSObjectAndFree");
- this.get_js_id = get_method ("GetJSObjectId");
- this.get_raw_mono_obj = get_method ("GetDotNetObject");
-
- this.box_js_int = get_method ("BoxInt");
- this.box_js_double = get_method ("BoxDouble");
- this.box_js_bool = get_method ("BoxBool");
- this.is_simple_array = get_method ("IsSimpleArray");
+ };
+
+ var bind_runtime_method = function (method_name, signature) {
+ var method = get_method (method_name);
+ return BINDING.bind_method (method, 0, signature, "BINDINGS_" + method_name);
+ };
+
+ // NOTE: The bound methods have a _ prefix on their names to ensure
+ // that any code relying on the old get_method/call_method pattern will
+ // break in a more understandable way.
+
+ this._bind_js_obj = bind_runtime_method ("BindJSObject", "iii");
+ this._bind_core_clr_obj = bind_runtime_method ("BindCoreCLRObject", "ii");
+ this._bind_existing_obj = bind_runtime_method ("BindExistingObject", "mi");
+ this._unbind_raw_obj_and_free = bind_runtime_method ("UnBindRawJSObjectAndFree", "ii");
+ this._get_js_id = bind_runtime_method ("GetJSObjectId", "m");
+ this._get_raw_mono_obj = bind_runtime_method ("GetDotNetObject", "i!");
+
+ this._is_simple_array = bind_runtime_method ("IsSimpleArray", "m");
this.setup_js_cont = get_method ("SetupJSContinuation");
this.create_tcs = get_method ("CreateTaskSource");
this.tcs_get_task_and_bind = get_method ("GetTaskAndBind");
this.get_call_sig = get_method ("GetCallSignature");
- this.object_to_string = get_method ("ObjectToString");
+ this._object_to_string = bind_runtime_method ("ObjectToString", "m");
this.get_date_value = get_method ("GetDateValue");
this.create_date_time = get_method ("CreateDateTime");
this.create_uri = get_method ("CreateUri");
this.safehandle_get_handle = get_method ("SafeHandleGetHandle");
this.safehandle_release_by_handle = get_method ("SafeHandleReleaseByHandle");
- this.init = true;
+ this._are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");
+ },
+
+ js_string_to_mono_string: function (string) {
+ var buffer = Module._malloc ((string.length + 1) * 2);
+ var buffer16 = (buffer / 2) | 0;
+ for (var i = 0; i < string.length; i++)
+ Module.HEAP16[buffer16 + i] = string.charCodeAt (i);
+ Module.HEAP16[buffer16 + string.length] = 0;
+ var result = this.mono_wasm_string_from_utf16 (buffer, string.length);
+ Module._free (buffer);
+ return result;
+ },
+
+ find_method: function (klass, name, n) {
+ var result = this._find_method(klass, name, n);
+ if (result) {
+ if (!this._method_descriptions)
+ this._method_descriptions = new Map();
+ this._method_descriptions.set(result, name);
+ }
+ return result;
},
get_js_obj: function (js_handle) {
},
is_nested_array: function (ele) {
- return this.call_method (this.is_simple_array, null, "mi", [ ele ]);
+ return this._is_simple_array(ele);
},
js_string_to_mono_string: function (string) {
let elemRoot = MONO.mono_wasm_new_root ();
try {
- var res = [];
var len = this.mono_array_length (arrayRoot.value);
+ var res = new Array (len);
for (var i = 0; i < len; ++i)
{
elemRoot.value = this.mono_array_get (arrayRoot.value, i);
if (this.is_nested_array (elemRoot.value))
- res.push (this._mono_array_to_js_array_rooted (elemRoot));
+ res[i] = this._mono_array_to_js_array_rooted (elemRoot);
else
- res.push (this._unbox_mono_obj_rooted (elemRoot));
+ res[i] = this._unbox_mono_obj_rooted (elemRoot);
}
} finally {
- elemRoot.release();
+ elemRoot.release ();
}
return res;
}
},
- _unbox_mono_obj_rooted: function (root) {
- var mono_obj = root.value;
- if (mono_obj === 0)
- return undefined;
-
- var type = this.mono_get_obj_type (mono_obj);
- //See MARSHAL_TYPE_ defines in driver.c
- switch (type) {
- case 1: // int
- return this.mono_unbox_int (mono_obj);
- case 2: // float
- return this.mono_unbox_float (mono_obj);
- case 3: //string
- return this.conv_string (mono_obj);
- case 4: //vts
- throw new Error ("no idea on how to unbox value types");
- case 5: { // delegate
- var obj = this.extract_js_obj (mono_obj);
- obj.__mono_delegate_alive__ = true;
- // FIXME: Should we root the object as long as this function has not been GCd?
- return function () {
- return BINDING.invoke_delegate (obj, arguments);
- };
- }
- 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) {
- cont_obj = {
- resolve: resolve,
- reject: reject
- };
- });
-
- this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]);
- obj.__mono_js_cont__ = cont_obj.__mono_gchandle__;
- cont_obj.__mono_js_task__ = obj.__mono_gchandle__;
- return promise;
- }
-
- case 7: // ref type
- return this.extract_js_obj (mono_obj);
+ _unbox_delegate_rooted: function (mono_obj) {
+ var obj = this.extract_js_obj (mono_obj);
+ obj.__mono_delegate_alive__ = true;
+ // FIXME: Should we root the object as long as this function has not been GCd?
+ return function () {
+ // TODO: Just use Function.bind
+ return BINDING.invoke_delegate (obj, arguments);
+ };
+ },
- case 8: // bool
- return this.mono_unbox_int (mono_obj) != 0;
+ _unbox_task_rooted: function (mono_obj) {
+ if (!this._are_promises_supported)
+ throw new Error ("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context.");
- case 9: // enum
+ var obj = this.extract_js_obj (mono_obj);
+ var cont_obj = null;
+ var promise = new Promise (function (resolve, reject) {
+ cont_obj = {
+ resolve: resolve,
+ reject: reject
+ };
+ });
- if(this.mono_wasm_marshal_enum_as_int)
- {
- return this.mono_unbox_enum (mono_obj);
- }
- else
- {
- enumValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
- }
+ this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]);
+ obj.__mono_js_cont__ = cont_obj.__mono_gchandle__;
+ cont_obj.__mono_js_task__ = obj.__mono_gchandle__;
+ return promise;
+ },
- return enumValue;
-
- case 10: // arrays
- case 11:
- case 12:
- case 13:
- case 14:
- case 15:
- case 16:
- case 17:
- case 18:
+ _unbox_safehandle_rooted: function (mono_obj) {
+ var addRef = true;
+ var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ mono_obj, addRef ]);
+ var requiredObject = BINDING.mono_wasm_require_handle (js_handle);
+ if (addRef)
{
- throw new Error ("Marshalling of primitive arrays are not supported. Use the corresponding TypedArray instead.");
+ if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
+ this.mono_wasm_owned_objects_LMF = [];
+
+ this.mono_wasm_owned_objects_LMF.push(js_handle);
}
- case 20: // clr .NET DateTime
- var dateValue = this.call_method(this.get_date_value, null, "md", [ mono_obj ]);
- return new Date(dateValue);
- case 21: // clr .NET DateTimeOffset
- var dateoffsetValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
- return dateoffsetValue;
- case 22: // clr .NET Uri
- var uriValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
- return uriValue;
- case 23: // clr .NET SafeHandle
- var addRef = true;
- var js_handle = this.call_method(this.safehandle_get_handle, null, "mii", [ mono_obj, addRef ]);
- // FIXME: Is this a GC object that needs to be rooted?
- var requiredObject = BINDING.mono_wasm_require_handle (js_handle);
- if (addRef)
- {
- if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
- this.mono_wasm_owned_objects_LMF = [];
+ return requiredObject;
+ },
- this.mono_wasm_owned_objects_LMF.push(js_handle);
- }
- return requiredObject;
- default:
- throw new Error ("no idea on how to unbox object kind " + type + " at offset " + mono_obj);
+ _unbox_mono_obj_rooted_with_known_nonprimitive_type: function (mono_obj, type) {
+ //See MARSHAL_TYPE_ defines in driver.c
+ switch (type) {
+ case 26: // int64
+ case 27: // uint64
+ // TODO: Fix this once emscripten offers HEAPI64/HEAPU64 or can return them
+ throw new Error ("int64 not available");
+ case 3: //string
+ return this.conv_string (mono_obj);
+ case 4: //vts
+ throw new Error ("no idea on how to unbox value types");
+ case 5: // delegate
+ return this._unbox_delegate_rooted (mono_obj);
+ case 6: // Task
+ return this._unbox_task_rooted (mono_obj);
+ case 7: // ref type
+ return this.extract_js_obj (mono_obj);
+ case 10: // arrays
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ throw new Error ("Marshalling of primitive arrays are not supported. Use the corresponding TypedArray instead.");
+ case 20: // clr .NET DateTime
+ var dateValue = this.call_method(this.get_date_value, null, "md", [ mono_obj ]);
+ return new Date(dateValue);
+ case 21: // clr .NET DateTimeOffset
+ var dateoffsetValue = this._object_to_string (mono_obj);
+ return dateoffsetValue;
+ case 22: // clr .NET Uri
+ var uriValue = this._object_to_string (mono_obj);
+ return uriValue;
+ case 23: // clr .NET SafeHandle
+ return this._unbox_safehandle_rooted (mono_obj);
+ default:
+ throw new Error ("no idea on how to unbox object kind " + type + " at offset " + mono_obj);
+ }
+ },
+
+ _unbox_mono_obj_rooted: function (root) {
+ var mono_obj = root.value;
+ if (mono_obj === 0)
+ return undefined;
+
+ var type = this.mono_wasm_try_unbox_primitive_and_get_type (mono_obj, this._unbox_buffer);
+ switch (type) {
+ case 1: // int
+ return Module.HEAP32[this._unbox_buffer / 4];
+ case 25: // uint32
+ return Module.HEAPU32[this._unbox_buffer / 4];
+ case 24: // float32
+ return Module.HEAPF32[this._unbox_buffer / 4];
+ case 2: // float64
+ return Module.HEAPF64[this._unbox_buffer / 8];
+ case 8: // boolean
+ return (Module.HEAP32[this._unbox_buffer / 4]) !== 0;
+ case 28: // char
+ return String.fromCharCode(Module.HEAP32[this._unbox_buffer / 4]);
+ default:
+ return this._unbox_mono_obj_rooted_with_known_nonprimitive_type (mono_obj, type);
}
},
heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, numBytes));
return heapBytes;
},
+
+ _box_js_int: function (js_obj) {
+ Module.HEAP32[this._box_buffer / 4] = js_obj;
+ return this.mono_wasm_box_primitive (this._class_int32, this._box_buffer, 4);
+ },
+
+ _box_js_uint: function (js_obj) {
+ Module.HEAPU32[this._box_buffer / 4] = js_obj;
+ return this.mono_wasm_box_primitive (this._class_uint32, this._box_buffer, 4);
+ },
+
+ _box_js_double: function (js_obj) {
+ Module.HEAPF64[this._box_buffer / 8] = js_obj;
+ return this.mono_wasm_box_primitive (this._class_double, this._box_buffer, 8);
+ },
+
+ _box_js_bool: function (js_obj) {
+ Module.HEAP32[this._box_buffer / 4] = js_obj ? 1 : 0;
+ return this.mono_wasm_box_primitive (this._class_boolean, this._box_buffer, 4);
+ },
+
js_to_mono_obj: function (js_obj) {
this.bindings_lazy_init ();
case js_obj === null:
case typeof js_obj === "undefined":
return 0;
- case typeof js_obj === "number":
- if (parseInt(js_obj) == js_obj)
- return this.call_method (this.box_js_int, null, "im", [ js_obj ]);
- return this.call_method (this.box_js_double, null, "dm", [ js_obj ]);
- case typeof js_obj === "string":
+ case typeof js_obj === "number": {
+ if ((js_obj | 0) === js_obj)
+ result = this._box_js_int (js_obj);
+ else if ((js_obj >>> 0) === js_obj)
+ result = this._box_js_uint (js_obj);
+ else
+ result = this._box_js_double (js_obj);
+
+ if (!result)
+ throw new Error (`Boxing failed for ${js_obj}`);
+
+ return result;
+ } case typeof js_obj === "string":
return this.js_string_to_mono_string (js_obj);
case typeof js_obj === "boolean":
- return this.call_method (this.box_js_bool, null, "im", [ js_obj ]);
+ return this._box_js_bool (js_obj);
case isThenable() === true:
var the_task = this.try_extract_mono_obj (js_obj);
if (the_task)
return the_task;
- // FIXME: We need to root tcs for an appropriate timespan
+ // FIXME: We need to root tcs for an appropriate timespan, at least until the Task
+ // is resolved
var tcs = this.create_task_completion_source ();
js_obj.then (function (result) {
BINDING.set_task_result (tcs, result);
return this.get_task_and_bind (tcs, js_obj);
case js_obj.constructor.name === "Date":
// We may need to take into account the TimeZone Offset
- return this.call_method(this.create_date_time, null, "dm", [ js_obj.getTime() ]);
+ return this.call_method(this.create_date_time, null, "d!", [ js_obj.getTime() ]);
default:
return this.extract_mono_obj (js_obj);
}
case typeof js_obj === "undefined":
return 0;
case typeof js_obj === "string":
- return this.call_method(this.create_uri, null, "sm", [ js_obj ])
+ return this.call_method(this.create_uri, null, "s!", [ js_obj ])
default:
return this.extract_mono_obj (js_obj);
}
this.typedarray_copy_from(newTypedArray, pinned_array, begin, end, bytes_per_element);
return newTypedArray;
},
- js_to_mono_enum: function (method, parmIdx, js_obj) {
+ js_to_mono_enum: function (js_obj, method, parmIdx) {
this.bindings_lazy_init ();
- if (js_obj === null || typeof js_obj === "undefined")
- return 0;
+ if (typeof (js_obj) !== "number")
+ throw new Error (`Expected numeric value for enum argument, got '${js_obj}'`);
- var monoObj, monoEnum;
- try {
- monoObj = MONO.mono_wasm_new_root (this.js_to_mono_obj (js_obj));
- // Check enum contract
- monoEnum = MONO.mono_wasm_new_root (this.call_method (this.object_to_enum, null, "iimm", [ method, parmIdx, monoObj.value ]))
- // return the unboxed enum value.
- return this.mono_unbox_enum (monoEnum.value);
- } finally {
- MONO.mono_wasm_release_roots (monoObj, monoEnum);
- }
+ return js_obj | 0;
},
wasm_binding_obj_new: function (js_obj_id, ownsHandle, type)
{
- return this.call_method (this.bind_js_obj, null, "iii", [js_obj_id, ownsHandle, type]);
+ return this._bind_js_obj (js_obj_id, ownsHandle, type);
},
wasm_bind_existing: function (mono_obj, js_id)
{
- return this.call_method (this.bind_existing_obj, null, "mi", [mono_obj, js_id]);
+ return this._bind_existing_obj (mono_obj, js_id);
},
wasm_bind_core_clr_obj: function (js_id, gc_handle)
{
- return this.call_method (this.bind_core_clr_obj, null, "ii", [js_id, gc_handle]);
+ return this._bind_core_clr_obj (js_id, gc_handle);
},
wasm_get_js_id: function (mono_obj)
{
- return this.call_method (this.get_js_id, null, "m", [mono_obj]);
+ return this._get_js_id (mono_obj);
},
wasm_get_raw_obj: function (gchandle)
{
- return this.call_method (this.get_raw_mono_obj, null, "im", [gchandle]);
+ return this._get_raw_mono_obj (gchandle);
},
try_extract_mono_obj:function (js_obj) {
free_task_completion_source: function (tcs) {
if (tcs.is_mono_tcs_result_set)
{
- this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_gchandle__ ]);
+ this._unbind_raw_obj_and_free (tcs.__mono_gchandle__);
}
if (tcs.__mono_bound_task__)
{
- this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_bound_task__ ]);
+ this._unbind_raw_obj_and_free (tcs.__mono_bound_task__);
}
},
extract_mono_obj: function (js_obj) {
-
if (js_obj === null || typeof js_obj === "undefined")
return 0;
- if (!js_obj.is_mono_bridged_obj) {
- var gc_handle = this.mono_wasm_register_obj(js_obj);
- return this.wasm_get_raw_obj (gc_handle);
+ var result = null;
+ var gc_handle = js_obj.__mono_gchandle__;
+ if (gc_handle) {
+ result = this.wasm_get_raw_obj (gc_handle);
+
+ // It's possible the managed object corresponding to this JS object was collected,
+ // in which case we need to make a new one.
+ if (!result) {
+ delete js_obj.__mono_gchandle__;
+ delete js_obj.is_mono_bridged_obj;
+ }
}
+ if (!result) {
+ gc_handle = this.mono_wasm_register_obj(js_obj);
+ result = this.wasm_get_raw_obj (gc_handle);
+ }
- return this.wasm_get_raw_obj (js_obj.__mono_gchandle__);
+ return result;
},
extract_js_obj: function (mono_obj) {
return js_obj;
},
+ _create_named_function: function (name, argumentNames, body, closure) {
+ var result = null, keys = null, closureArgumentList = null, closureArgumentNames = null;
+
+ if (closure) {
+ closureArgumentNames = Object.keys (closure);
+ closureArgumentList = new Array (closureArgumentNames.length);
+ for (var i = 0, l = closureArgumentNames.length; i < l; i++)
+ closureArgumentList[i] = closure[closureArgumentNames[i]];
+ }
+
+ var constructor = this._create_rebindable_named_function (name, argumentNames, body, closureArgumentNames);
+ result = constructor.apply (null, closureArgumentList);
+
+ return result;
+ },
+
+ _create_rebindable_named_function: function (name, argumentNames, body, closureArgNames) {
+ var strictPrefix = "\"use strict\";\r\n";
+ var uriPrefix = "", escapedFunctionIdentifier = "";
+
+ if (name) {
+ uriPrefix = "//# sourceURL=https://mono-wasm.invalid/" + name + "\r\n";
+ escapedFunctionIdentifier = name;
+ } else {
+ escapedFunctionIdentifier = "unnamed";
+ }
+
+ var rawFunctionText = "function " + escapedFunctionIdentifier + "(" +
+ argumentNames.join(", ") +
+ ") {\r\n" +
+ body +
+ "\r\n};\r\n";
+
+ var lineBreakRE = /\r(\n?)/g;
+
+ rawFunctionText =
+ uriPrefix + strictPrefix +
+ rawFunctionText.replace(lineBreakRE, "\r\n ") +
+ ` return ${escapedFunctionIdentifier};\r\n`;
+
+ var result = null, keys = null;
+
+ if (closureArgNames) {
+ keys = closureArgNames.concat ([rawFunctionText]);
+ } else {
+ keys = [rawFunctionText];
+ }
+
+ result = Function.apply (Function, keys);
+ return result;
+ },
+
+ _create_primitive_converters: function () {
+ var result = new Map ();
+ result.set ('m', { steps: [{ }], size: 0});
+ result.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this) }], size: 0, needs_root: true });
+ result.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this) }], size: 0, needs_root: true });
+ result.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this) }], size: 0, needs_root: true });
+
+ // result.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
+ result.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8});
+
+ result.set ('i', { steps: [{ indirect: 'i32'}], size: 8});
+ result.set ('l', { steps: [{ indirect: 'i64'}], size: 8});
+ result.set ('f', { steps: [{ indirect: 'float'}], size: 8});
+ result.set ('d', { steps: [{ indirect: 'double'}], size: 8});
+
+ this._primitive_converters = result;
+ return result;
+ },
+
+ _create_converter_for_marshal_string: function (args_marshal) {
+ var primitiveConverters = this._primitive_converters;
+ if (!primitiveConverters)
+ primitiveConverters = this._create_primitive_converters ();
+
+ var steps = [];
+ var size = 0;
+ var is_result_definitely_unmarshaled = false,
+ is_result_possibly_unmarshaled = false,
+ result_unmarshaled_if_argc = -1,
+ needs_root_buffer = false;
+
+ for (var i = 0; i < args_marshal.length; ++i) {
+ var key = args_marshal[i];
+
+ if (i === args_marshal.length - 1) {
+ if (key === "!") {
+ is_result_definitely_unmarshaled = true;
+ continue;
+ } else if (key === "m") {
+ is_result_possibly_unmarshaled = true;
+ result_unmarshaled_if_argc = args_marshal.length - 1;
+ }
+ } else if (key === "!")
+ throw new Error ("! must be at the end of the signature");
+
+ var conv = primitiveConverters.get (key);
+ if (!conv)
+ throw new Error ("Unknown parameter type " + type);
+
+ var localStep = Object.create (conv.steps[0]);
+ localStep.size = conv.size;
+ if (conv.needs_root)
+ needs_root_buffer = true;
+ localStep.needs_root = conv.needs_root;
+ localStep.key = args_marshal[i];
+ steps.push (localStep);
+ size += conv.size;
+ }
+
+ return {
+ steps: steps, size: size, args_marshal: args_marshal,
+ is_result_definitely_unmarshaled: is_result_definitely_unmarshaled,
+ is_result_possibly_unmarshaled: is_result_possibly_unmarshaled,
+ result_unmarshaled_if_argc: result_unmarshaled_if_argc,
+ needs_root_buffer: needs_root_buffer
+ };
+ },
+
+ _get_converter_for_marshal_string: function (args_marshal) {
+ if (!this._signature_converters)
+ this._signature_converters = new Map();
+
+ var converter = this._signature_converters.get (args_marshal);
+ if (!converter) {
+ converter = this._create_converter_for_marshal_string (args_marshal);
+ this._signature_converters.set (args_marshal, converter);
+ }
+
+ return converter;
+ },
+
+ _compile_converter_for_marshal_string: function (args_marshal) {
+ var converter = this._get_converter_for_marshal_string (args_marshal);
+ if (typeof (converter.args_marshal) !== "string")
+ throw new Error ("Corrupt converter for '" + args_marshal + "'");
+
+ if (converter.compiled_function && converter.compiled_variadic_function)
+ return converter;
+
+ var converterName = args_marshal.replace("!", "_result_unmarshaled");
+ converter.name = converterName;
+
+ var body = [];
+ var argumentNames = ["buffer", "rootBuffer", "method"];
+
+ // worst-case allocation size instead of allocating dynamically, plus padding
+ var bufferSizeBytes = converter.size + (args_marshal.length * 4) + 16;
+ var rootBufferSize = args_marshal.length;
+ // ensure the indirect values are 8-byte aligned so that aligned loads and stores will work
+ var indirectBaseOffset = ((((args_marshal.length * 4) + 7) / 8) | 0) * 8;
+
+ var closure = {};
+ var indirectLocalOffset = 0;
+
+ body.push (
+ `if (!buffer) buffer = Module._malloc (${bufferSizeBytes});`,
+ `var indirectStart = buffer + ${indirectBaseOffset};`,
+ "var indirect32 = (indirectStart / 4) | 0, indirect64 = (indirectStart / 8) | 0;",
+ "var buffer32 = (buffer / 4) | 0;",
+ ""
+ );
+
+ for (let i = 0; i < converter.steps.length; i++) {
+ var step = converter.steps[i];
+ var closureKey = "step" + i;
+ var valueKey = "value" + i;
+
+ var argKey = "arg" + i;
+ argumentNames.push (argKey);
+
+ if (step.convert) {
+ closure[closureKey] = step.convert;
+ body.push (`var ${valueKey} = ${closureKey}(${argKey}, method, ${i});`);
+ } else {
+ body.push (`var ${valueKey} = ${argKey};`);
+ }
+
+ if (step.needs_root)
+ body.push (`rootBuffer.set (${i}, ${valueKey});`);
+
+ if (step.indirect) {
+ var heapArrayName = null;
+
+ switch (step.indirect) {
+ case "u32":
+ heapArrayName = "HEAPU32";
+ break;
+ case "i32":
+ heapArrayName = "HEAP32";
+ break;
+ case "float":
+ heapArrayName = "HEAPF32";
+ break;
+ case "double":
+ body.push (`Module.HEAPF64[indirect64 + ${(indirectLocalOffset / 8)}] = ${valueKey};`);
+ break;
+ case "i64":
+ body.push (`Module.setValue (indirectStart + ${indirectLocalOffset}, ${valueKey}, 'i64');`);
+ break;
+ default:
+ throw new Error ("Unimplemented indirect type: " + step.indirect);
+ }
+
+ if (heapArrayName)
+ body.push (`Module.${heapArrayName}[indirect32 + ${(indirectLocalOffset / 4)}] = ${valueKey};`);
+
+ body.push (`Module.HEAP32[buffer32 + ${i}] = indirectStart + ${indirectLocalOffset};`, "");
+ indirectLocalOffset += step.size;
+ } else {
+ body.push (`Module.HEAP32[buffer32 + ${i}] = ${valueKey};`, "");
+ indirectLocalOffset += 4;
+ }
+ }
+
+ body.push ("return buffer;");
+
+ var bodyJs = body.join ("\r\n"), compiledFunction = null, compiledVariadicFunction = null;
+ try {
+ compiledFunction = this._create_named_function("converter_" + converterName, argumentNames, bodyJs, closure);
+ converter.compiled_function = compiledFunction;
+ } catch (exc) {
+ converter.compiled_function = null;
+ console.warn("compiling converter failed for", bodyJs, "with error", exc);
+ throw exc;
+ }
+
+ argumentNames = ["existingBuffer", "rootBuffer", "method", "args"];
+ closure = {
+ converter: compiledFunction
+ };
+ body = [
+ "return converter(",
+ " existingBuffer, rootBuffer, method,"
+ ];
+
+ for (let i = 0; i < converter.steps.length; i++) {
+ body.push(
+ " args[" + i +
+ (
+ (i == converter.steps.length - 1)
+ ? "]"
+ : "], "
+ )
+ );
+ }
+
+ body.push(");");
+
+ bodyJs = body.join ("\r\n");
+ try {
+ compiledVariadicFunction = this._create_named_function("variadic_converter_" + converterName, argumentNames, bodyJs, closure);
+ converter.compiled_variadic_function = compiledVariadicFunction;
+ } catch (exc) {
+ converter.compiled_variadic_function = null;
+ console.warn("compiling converter failed for", bodyJs, "with error", exc);
+ throw exc;
+ }
+
+ converter.scratchRootBuffer = null;
+ converter.scratchBuffer = 0 | 0;
+
+ return converter;
+ },
+
+ _verify_args_for_method_call: function (args_marshal, args) {
+ var has_args = args && (typeof args === "object") && args.length > 0;
+ var has_args_marshal = typeof args_marshal === "string";
+
+ if (has_args) {
+ if (!has_args_marshal)
+ throw new Error ("No signature provided for method call.");
+ else if (args.length > args_marshal.length)
+ throw new Error ("Too many parameter values. Expected at most " + args_marshal.length + " value(s) for signature " + args_marshal);
+ }
+
+ return has_args_marshal && has_args;
+ },
+
+ _get_buffer_for_method_call: function (converter) {
+ if (!converter)
+ return 0;
+
+ var result = converter.scratchBuffer;
+ converter.scratchBuffer = 0;
+ return result;
+ },
+
+ _get_args_root_buffer_for_method_call: function (converter) {
+ if (!converter)
+ return null;
+
+ if (!converter.needs_root_buffer)
+ return null;
+
+ var result;
+ if (converter.scratchRootBuffer) {
+ result = converter.scratchRootBuffer;
+ converter.scratchRootBuffer = null;
+ } else {
+ // TODO: Expand the converter's heap allocation and then use
+ // mono_wasm_new_root_buffer_from_pointer instead. Not that important
+ // at present because the scratch buffer will be reused unless we are
+ // recursing through a re-entrant call
+ result = MONO.mono_wasm_new_root_buffer (converter.steps.length);
+ result.converter = converter;
+ }
+ return result;
+ },
+
+ _release_args_root_buffer_from_method_call: function (converter, argsRootBuffer) {
+ if (!argsRootBuffer || !converter)
+ return;
+
+ // Store the arguments root buffer for re-use in later calls
+ if (!converter.scratchRootBuffer) {
+ argsRootBuffer.clear ();
+ converter.scratchRootBuffer = argsRootBuffer;
+ } else {
+ argsRootBuffer.release ();
+ }
+ },
+
+ _release_buffer_from_method_call: function (converter, buffer) {
+ if (!converter || !buffer)
+ return;
+
+ if (!converter.scratchBuffer)
+ converter.scratchBuffer = buffer | 0;
+ else
+ Module._free (buffer | 0);
+ },
+
+ _convert_exception_for_method_call: function (result, exception) {
+ if (exception === 0)
+ return null;
+
+ var msg = this.conv_string (result);
+ var err = new Error (msg); //the convention is that invoke_method ToString () any outgoing exception
+ // console.warn ("error", msg, "at location", err.stack);
+ return err;
+ },
+
+ _maybe_produce_signature_warning: function (converter) {
+ if (converter.has_warned_about_signature)
+ return;
+
+ console.warn ("MONO_WASM: Deprecated raw return value signature: '" + converter.args_marshal + "'. End the signature with '!' instead of 'm'.");
+ converter.has_warned_about_signature = true;
+ },
+
+ _decide_if_result_is_marshaled: function (converter, argc) {
+ if (!converter)
+ return true;
+
+ if (
+ converter.is_result_possibly_unmarshaled &&
+ (argc === converter.result_unmarshaled_if_argc)
+ ) {
+ if (argc < converter.result_unmarshaled_if_argc)
+ throw new Error(["Expected >= ", converter.result_unmarshaled_if_argc, "argument(s) but got", argc, "for signature " + converter.args_marshal].join(" "));
+
+ this._maybe_produce_signature_warning (converter);
+ return false;
+ } else {
+ if (argc < converter.steps.length)
+ throw new Error(["Expected", converter.steps.length, "argument(s) but got", argc, "for signature " + converter.args_marshal].join(" "));
+
+ return !converter.is_result_definitely_unmarshaled;
+ }
+ },
+
/*
args_marshal is a string with one character per parameter that tells how to marshal it, here are the valid values:
o: js object will be converted to a C# object (this will box numbers/bool/promises)
m: raw mono object. Don't use it unless you know what you're doing
- additionally you can append 'm' to args_marshal beyond `args.length` if you don't want the return value marshaled
+ to suppress marshaling of the return value, place '!' at the end of args_marshal, i.e. 'ii!' instead of 'ii'
*/
call_method: function (method, this_arg, args_marshal, args) {
this.bindings_lazy_init ();
- // Allocate memory for error
- var has_args = args !== null && typeof args !== "undefined" && args.length > 0;
- var has_args_marshal = args_marshal !== null && typeof args_marshal !== "undefined" && args_marshal.length > 0;
+ // HACK: Sometimes callers pass null or undefined, coerce it to 0 since that's what wasm expects
+ this_arg = this_arg | 0;
- if (has_args_marshal && (!has_args || args.length > args_marshal.length))
- throw Error("Parameter count mismatch.");
+ // Detect someone accidentally passing the wrong type of value to method
+ if ((method | 0) !== method)
+ throw new Error (`method must be an address in the native heap, but was '${method}'`);
+ if (!method)
+ throw new Error ("no method specified");
+
+ var needs_converter = this._verify_args_for_method_call (args_marshal, args);
- var args_start = null;
- var buffer = null;
- var [resultRoot, exceptionRoot] = MONO.mono_wasm_new_roots (2);
- var argsRootBuffer = null;
+ var buffer = 0, converter = null, argsRootBuffer = null;
+ var is_result_marshaled = true;
// check if the method signature needs argument mashalling
- if (has_args_marshal && has_args) {
- var i;
-
- var converters = this.converters;
- if (!converters) {
- converters = new Map ();
- converters.set ('m', { steps: [{ }], size: 0});
- converters.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this)}], size: 0});
- converters.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this)}], size: 0});
- converters.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this)}], size: 0});
- converters.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
- converters.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8});
- converters.set ('i', { steps: [{ indirect: 'i32'}], size: 8});
- converters.set ('l', { steps: [{ indirect: 'i64'}], size: 8});
- converters.set ('f', { steps: [{ indirect: 'float'}], size: 8});
- converters.set ('d', { steps: [{ indirect: 'double'}], size: 8});
- this.converters = converters;
- }
+ if (needs_converter) {
+ converter = this._compile_converter_for_marshal_string (args_marshal);
- var converter = converters.get (args_marshal);
- if (!converter) {
- var steps = [];
- var size = 0;
+ is_result_marshaled = this._decide_if_result_is_marshaled (converter, args.length);
+
+ argsRootBuffer = this._get_args_root_buffer_for_method_call (converter);
- for (i = 0; i < args_marshal.length; ++i) {
- var conv = this.converters.get (args_marshal[i]);
- if (!conv)
- throw Error ("Unknown parameter type " + type);
+ var scratchBuffer = this._get_buffer_for_method_call (converter);
- steps.push (conv.steps[0]);
- size += conv.size;
- }
- converter = { steps: steps, size: size };
- converters.set (args_marshal, converter);
- }
+ buffer = converter.compiled_variadic_function (scratchBuffer, argsRootBuffer, method, args);
+ }
- // FIXME: Allocate a root buffer to contain all the managed objects like strings so that they aren't
- // collected until the method call completes.
-
- // assume at least 8 byte alignment from malloc
- var bufferSizeBytes = converter.size + (args.length * 4);
- var bufferSizeElements = (bufferSizeBytes / 4) | 0;
- argsRootBuffer = MONO.mono_wasm_new_root_buffer (bufferSizeElements);
- buffer = Module._malloc (bufferSizeBytes);
- var indirect_start = buffer; // buffer + buffer % 8
- args_start = indirect_start + converter.size;
-
- var slot = args_start;
- var indirect_value = indirect_start;
- for (i = 0; i < args.length; ++i) {
- var handler = converter.steps[i];
- var obj = handler.convert ? handler.convert (args[i], method, i) : args[i];
-
- if (handler.indirect) {
- Module.setValue (indirect_value, obj, handler.indirect);
- obj = indirect_value;
- indirect_value += 8;
- } else {
- argsRootBuffer.set (i, obj);
- }
+ return this._call_method_with_converted_args (method, this_arg, converter, buffer, is_result_marshaled, argsRootBuffer);
+ },
- Module.setValue (slot, obj, "*");
- slot += 4;
- }
- }
+ _handle_exception_for_call: function (
+ converter, buffer, resultRoot, exceptionRoot, argsRootBuffer
+ ) {
+ var exc = this._convert_exception_for_method_call (resultRoot.value, exceptionRoot.value);
+ if (!exc)
+ return;
- try {
- resultRoot.value = this.invoke_method (method, this_arg, args_start, exceptionRoot.get_address ());
- Module._free (buffer);
+ this._teardown_after_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
+ throw exc;
+ },
- if (exceptionRoot.value != 0) {
- var msg = this.conv_string (resultRoot.value);
- throw new Error (msg); //the convention is that invoke_method ToString () any outgoing exception
- }
+ _handle_exception_and_produce_result_for_call: function (
+ converter, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled
+ ) {
+ this._handle_exception_for_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
- if (has_args_marshal && has_args) {
- if (args_marshal.length >= args.length && args_marshal [args.length] === "m")
- return resultRoot.value;
+ if (is_result_marshaled)
+ result = this._unbox_mono_obj_rooted (resultRoot);
+ else
+ result = resultRoot.value;
+
+ this._teardown_after_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
+ return result;
+ },
+
+ _teardown_after_call: function (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer) {
+ this._release_args_root_buffer_from_method_call (converter, argsRootBuffer);
+ this._release_buffer_from_method_call (converter, buffer | 0);
+
+ if (resultRoot)
+ resultRoot.release ();
+ if (exceptionRoot)
+ exceptionRoot.release ();
+ },
+
+ _get_method_description: function (method) {
+ if (!this._method_descriptions)
+ this._method_descriptions = new Map();
+
+ var result = this._method_descriptions.get (method);
+ if (!result)
+ result = "method#" + method;
+ return result;
+ },
+
+ _call_method_with_converted_args: function (method, this_arg, converter, buffer, is_result_marshaled, argsRootBuffer) {
+ var resultRoot = MONO.mono_wasm_new_root (), exceptionRoot = MONO.mono_wasm_new_root ();
+ resultRoot.value = this.invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());
+ return this._handle_exception_and_produce_result_for_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled);
+ },
+
+ bind_method: function (method, this_arg, args_marshal, friendly_name) {
+ this.bindings_lazy_init ();
+
+ this_arg = this_arg | 0;
+
+ var converter = null;
+ if (typeof (args_marshal) === "string")
+ converter = this._compile_converter_for_marshal_string (args_marshal);
+
+ var closure = {
+ library_mono: MONO,
+ binding_support: this,
+ method: method,
+ this_arg: this_arg
+ };
+
+ var converterKey = "converter_" + converter.name;
+
+ if (converter)
+ closure[converterKey] = converter;
+
+ var argumentNames = [];
+ var body = [
+ "var resultRoot = library_mono.mono_wasm_new_root (), exceptionRoot = library_mono.mono_wasm_new_root ();",
+ ""
+ ];
+
+ if (converter) {
+ body.push(
+ `var argsRootBuffer = binding_support._get_args_root_buffer_for_method_call (${converterKey});`,
+ `var scratchBuffer = binding_support._get_buffer_for_method_call (${converterKey});`,
+ `var buffer = ${converterKey}.compiled_function (`,
+ " scratchBuffer, argsRootBuffer, method,"
+ );
+
+ for (var i = 0; i < converter.steps.length; i++) {
+ var argName = "arg" + i;
+ argumentNames.push(argName);
+ body.push(
+ " " + argName +
+ (
+ (i == converter.steps.length - 1)
+ ? ""
+ : ", "
+ )
+ );
}
- return this._unbox_mono_obj_rooted (resultRoot);
- } finally {
- MONO.mono_wasm_release_roots (resultRoot, exceptionRoot, argsRootBuffer);
+ body.push(");");
+
+ } else {
+ body.push("var argsRootBuffer = null, buffer = 0;");
+ }
+
+ if (converter.is_result_definitely_unmarshaled) {
+ body.push ("var is_result_marshaled = false;");
+ } else if (converter.is_result_possibly_unmarshaled) {
+ body.push (`var is_result_marshaled = arguments.length !== ${converter.result_unmarshaled_if_argc};`);
+ } else {
+ body.push ("var is_result_marshaled = true;");
+ }
+
+ // We inline a bunch of the invoke and marshaling logic here in order to eliminate the GC pressure normally
+ // created by the unboxing part of the call process. Because unbox_mono_obj(_rooted) can return non-numeric
+ // types, v8 and spidermonkey allocate and store its result on the heap (in the nursery, to be fair).
+ // For a bound method however, we know the result will always be the same type because C# methods have known
+ // return types. Inlining the invoke and marshaling logic means that even though the bound method has logic
+ // for handling various types, only one path through the method (for its appropriate return type) will ever
+ // be taken, and the JIT will see that the 'result' local and thus the return value of this function are
+ // always of the exact same type. All of the branches related to this end up being predicted and low-cost.
+ // The end result is that bound method invocations don't always allocate, so no more nursery GCs. Yay! -kg
+ body.push(
+ "",
+ "resultRoot.value = binding_support.invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());",
+ `binding_support._handle_exception_for_call (${converterKey}, buffer, resultRoot, exceptionRoot, argsRootBuffer);`,
+ "",
+ "var resultPtr = resultRoot.value, result = undefined;",
+ "if (!is_result_marshaled) ",
+ " result = resultPtr;",
+ "else if (resultPtr !== 0) {",
+ // For the common scenario where the return type is a primitive, we want to try and unbox it directly
+ // into our existing heap allocation and then read it out of the heap. Doing this all in one operation
+ // means that we only need to enter a gc safe region twice (instead of 3+ times with the normal,
+ // slower check-type-and-then-unbox flow which has extra checks since unbox verifies the type).
+ " var resultType = binding_support.mono_wasm_try_unbox_primitive_and_get_type (resultPtr, buffer);",
+ " switch (resultType) {",
+ " case 1:", // int
+ " result = Module.HEAP32[buffer / 4]; break;",
+ " case 25:", // uint32
+ " result = Module.HEAPU32[buffer / 4]; break;",
+ " case 24:", // float32
+ " result = Module.HEAPF32[buffer / 4]; break;",
+ " case 2:", // float64
+ " result = Module.HEAPF64[buffer / 8]; break;",
+ " case 8:", // boolean
+ " result = (Module.HEAP32[buffer / 4]) !== 0; break;",
+ " case 28:", // char
+ " result = String.fromCharCode(Module.HEAP32[buffer / 4]); break;",
+ " default:",
+ " result = binding_support._unbox_mono_obj_rooted_with_known_nonprimitive_type (resultRoot, resultType); break;",
+ " }",
+ "}",
+ "",
+ `binding_support._teardown_after_call (${converterKey}, buffer, resultRoot, exceptionRoot, argsRootBuffer);`,
+ "return result;"
+ );
+
+ bodyJs = body.join ("\r\n");
+
+ if (friendly_name) {
+ var escapeRE = /[^A-Za-z0-9_]/g;
+ friendly_name = friendly_name.replace(escapeRE, "_");
}
+
+ var displayName = "managed_" + (friendly_name || method);
+
+ if (this_arg)
+ displayName += "_with_this_" + this_arg;
+
+ return this._create_named_function(displayName, argumentNames, bodyJs, closure);
},
invoke_delegate: function (delegate_obj, js_args) {
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.");
- // Note: the single 'm' passed here is causing problems with AOT. Changed to "mo" again.
- // This may need more analysis if causes problems again.
- return this.call_method (this.delegate_dynamic_invoke, delegateRoot.value, "mo", [ argsRoot.value ]);
+ return this.call_method (this.delegate_dynamic_invoke, delegateRoot.value, "m", [ argsRoot.value ]);
} finally {
MONO.mono_wasm_release_roots (delegateRoot, argsRoot);
}
var klass = this.find_class(asm, namespace, classname);
if (!klass)
- throw new Error ("Could not find class: " + namespace + ":" +classname);
+ throw new Error ("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly);
var method = this.find_method (klass, methodname, -1);
if (!method)
if (typeof signature === "undefined")
signature = Module.mono_method_get_call_signature (method);
- return function() {
- return BINDING.call_method (method, null, signature, arguments);
- };
+ return BINDING.bind_method (method, null, signature, fqn);
},
+
bind_assembly_entry_point: function (assembly) {
this.bindings_lazy_init ();
}
BINDING.mono_wasm_unwind_LMF();
- return BINDING.call_method (BINDING.box_js_bool, null, "im", [ result ]);
+ return BINDING._box_js_bool (result);
},
mono_wasm_get_by_index: function(js_handle, property_index, is_exception) {
BINDING.bindings_lazy_init ();
var js_name = BINDING.conv_string (global_name);
- var globalObj = undefined;
+ var globalObj;
if (!js_name) {
globalObj = globalThis;
var argsList = new Array();
argsList[0] = constructor;
if (js_args)
- argsList = argsList.concat(js_args);
- var obj = new (constructor.bind.apply(constructor, argsList ));
+ argsList = argsList.concat (js_args);
+ var tempCtor = constructor.bind.apply (constructor, argsList);
+ var obj = new tempCtor ();
return obj;
};