Update the driver to support MONO_TYPE_I
[mono-project.git] / sdks / wasm / framework / src / WebAssembly.Bindings / Runtime.cs
blob5c2960637e09e0f32e54746e7df3e20d243cec01
1 using System;
2 using System.Collections.Generic;
3 using System.Reflection;
4 using System.Runtime.CompilerServices;
5 using System.Runtime.InteropServices;
6 using System.Threading.Tasks;
8 namespace WebAssembly {
9 /// <summary>
10 /// Provides access to the Mono/WebAssembly runtime to perform tasks like invoking JavaScript functions and retrieving global variables.
11 /// </summary>
12 public sealed class Runtime {
13 [MethodImplAttribute (MethodImplOptions.InternalCall)]
14 static extern string InvokeJS (string str, out int exceptional_result);
16 [MethodImplAttribute (MethodImplOptions.InternalCall)]
17 internal static extern object InvokeJSWithArgs (int js_obj_handle, string method, object [] _params, out int exceptional_result);
18 [MethodImplAttribute (MethodImplOptions.InternalCall)]
19 internal static extern object GetObjectProperty (int js_obj_handle, string propertyName, out int exceptional_result);
20 [MethodImplAttribute (MethodImplOptions.InternalCall)]
21 internal static extern object SetObjectProperty (int js_obj_handle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptional_result);
22 [MethodImplAttribute (MethodImplOptions.InternalCall)]
23 internal static extern object GetByIndex (int js_obj_handle, int index, out int exceptional_result);
24 [MethodImplAttribute (MethodImplOptions.InternalCall)]
25 internal static extern object SetByIndex (int js_obj_handle, int index, object value, out int exceptional_result);
26 [MethodImplAttribute (MethodImplOptions.InternalCall)]
27 internal static extern object GetGlobalObject (string globalName, out int exceptional_result);
29 [MethodImplAttribute (MethodImplOptions.InternalCall)]
30 internal static extern object ReleaseHandle (int js_obj_handle, out int exceptional_result);
31 [MethodImplAttribute (MethodImplOptions.InternalCall)]
32 internal static extern object ReleaseObject (int js_obj_handle, out int exceptional_result);
33 [MethodImplAttribute (MethodImplOptions.InternalCall)]
34 internal static extern object NewObjectJS (int js_obj_handle, object [] _params, out int exceptional_result);
35 [MethodImplAttribute (MethodImplOptions.InternalCall)]
36 internal static extern object BindCoreObject (int js_obj_handle, int gc_handle, out int exceptional_result);
37 [MethodImplAttribute (MethodImplOptions.InternalCall)]
38 internal static extern object BindHostObject (int js_obj_handle, int gc_handle, out int exceptional_result);
39 [MethodImplAttribute (MethodImplOptions.InternalCall)]
40 internal static extern object New (string className, object [] _params, out int exceptional_result);
41 [MethodImplAttribute (MethodImplOptions.InternalCall)]
42 internal static extern object TypedArrayToArray (int js_obj_handle, out int exceptional_result);
43 [MethodImplAttribute (MethodImplOptions.InternalCall)]
44 internal static extern object TypedArrayCopyTo (int js_obj_handle, int array_ptr, int begin, int end, int bytes_per_element, out int exceptional_result);
45 [MethodImplAttribute (MethodImplOptions.InternalCall)]
46 internal static extern object TypedArrayFrom (int array_ptr, int begin, int end, int bytes_per_element, int type, out int exceptional_result);
47 [MethodImplAttribute (MethodImplOptions.InternalCall)]
48 internal static extern object TypedArrayCopyFrom (int js_obj_handle, int array_ptr, int begin, int end, int bytes_per_element, out int exceptional_result);
50 /// <summary>
51 /// Execute the provided string in the JavaScript context
52 /// </summary>
53 /// <returns>The js.</returns>
54 /// <param name="str">String.</param>
55 public static string InvokeJS (string str)
57 var res = InvokeJS (str, out int exception);
58 if (exception != 0)
59 throw new JSException (res);
60 return res;
63 static Dictionary<int, JSObject> bound_objects = new Dictionary<int, JSObject> ();
64 static Dictionary<object, JSObject> raw_to_js = new Dictionary<object, JSObject> ();
66 static Runtime ()
67 { }
69 /// <summary>
70 /// Creates a new JavaScript object of the specified type
71 /// </summary>
72 /// <returns>The new.</returns>
73 /// <param name="_params">Parameters.</param>
74 /// <typeparam name="T">The 1st type parameter.</typeparam>
75 public static int New<T> (params object [] _params)
77 var res = New (typeof(T).Name, _params, out int exception);
78 if (exception != 0)
79 throw new JSException ((string)res);
80 return (int)res;
83 public static int New (string hostClassName, params object [] _params)
85 var res = New (hostClassName, _params, out int exception);
86 if (exception != 0)
87 throw new JSException ((string)res);
88 return (int)res;
91 /// <summary>
92 /// Creates a new JavaScript object
93 /// </summary>
94 /// <returns>The JSO bject.</returns>
95 /// <param name="js_func_ptr">Js func ptr.</param>
96 /// <param name="_params">Parameters.</param>
97 public static JSObject NewJSObject (JSObject js_func_ptr = null, params object [] _params)
99 var res = NewObjectJS (js_func_ptr?.JSHandle ?? 0, _params, out int exception);
100 if (exception != 0)
101 throw new JSException ((string)res);
102 return res as JSObject;
105 static int BindJSObject (int js_id, Type mappedType)
107 if (!bound_objects.TryGetValue (js_id, out JSObject obj)) {
108 if (mappedType != null) {
109 return BindJSType (js_id, mappedType);
110 } else {
111 bound_objects [js_id] = obj = new JSObject ((IntPtr)js_id);
115 return (int)(IntPtr)obj.Handle;
118 static int BindCoreCLRObject (int js_id, int gcHandle)
120 //Console.WriteLine ($"Registering CLR Object {js_id} with handle {gcHandle}");
121 GCHandle h = (GCHandle)(IntPtr)gcHandle;
122 JSObject obj = (JSObject)h.Target;
124 if (bound_objects.TryGetValue (js_id, out var existingObj)) {
125 if (existingObj.Handle != h && h.IsAllocated)
126 throw new JSException ($"Multiple handles pointing at js_id: {js_id}");
128 obj = existingObj;
129 } else
130 bound_objects [js_id] = obj;
132 return (int)(IntPtr)obj.Handle;
135 static int BindJSType (int js_id, Type mappedType)
137 if (!bound_objects.TryGetValue (js_id, out JSObject obj)) {
138 var jsobjectnew = mappedType.GetConstructor (BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.ExactBinding,
139 null, new Type [] { typeof (IntPtr) }, null);
140 bound_objects [js_id] = obj = (JSObject)jsobjectnew.Invoke (new object [] { (IntPtr)js_id });
142 return (int)(IntPtr)obj.Handle;
145 static int UnBindJSObject (int js_id)
147 if (bound_objects.TryGetValue (js_id, out var obj)) {
148 bound_objects.Remove (js_id);
149 return (int)(IntPtr)obj.Handle;
152 return 0;
155 static void UnBindJSObjectAndFree (int js_id)
157 if (bound_objects.TryGetValue (js_id, out var obj)) {
158 bound_objects [js_id].RawObject = null;
159 bound_objects.Remove (js_id);
160 obj.JSHandle = -1;
161 obj.IsDisposed = true;
162 obj.RawObject = null;
163 obj.Handle.Free ();
170 static void UnBindRawJSObjectAndFree (int gcHandle)
173 GCHandle h = (GCHandle)(IntPtr)gcHandle;
174 JSObject obj = (JSObject)h.Target;
175 if (obj != null && obj.RawObject != null) {
176 raw_to_js.Remove (obj.RawObject);
178 int exception;
179 ReleaseHandle (obj.JSHandle, out exception);
180 if (exception != 0)
181 throw new JSException ($"Error releasing handle on (js-obj js '{obj.JSHandle}' mono '{(IntPtr)obj.Handle} raw '{obj.RawObject != null})");
183 // Calling Release Handle above only removes the reference from the JavaScript side but does not
184 // release the bridged JSObject associated with the raw object so we have to do that ourselves.
185 obj.JSHandle = -1;
186 obj.IsDisposed = true;
187 obj.RawObject = null;
189 obj.Handle.Free ();
194 public static void FreeObject (object obj)
196 if (raw_to_js.TryGetValue (obj, out JSObject jsobj)) {
197 raw_to_js [obj].RawObject = null;
198 raw_to_js.Remove (obj);
200 int exception;
201 Runtime.ReleaseObject (jsobj.JSHandle, out exception);
202 if (exception != 0)
203 throw new JSException ($"Error releasing object on (raw-obj)");
205 jsobj.JSHandle = -1;
206 jsobj.RawObject = null;
207 jsobj.IsDisposed = true;
208 jsobj.Handle.Free ();
210 } else {
211 throw new JSException ($"Error releasing object on (obj)");
215 static object CreateTaskSource (int js_id)
217 return new TaskCompletionSource<object> ();
220 static void SetTaskSourceResult (TaskCompletionSource<object> tcs, object result)
222 tcs.SetResult (result);
225 static void SetTaskSourceFailure (TaskCompletionSource<object> tcs, string reason)
227 tcs.SetException (new JSException (reason));
230 static int GetTaskAndBind (TaskCompletionSource<object> tcs, int js_id)
232 return BindExistingObject (tcs.Task, js_id);
235 static int BindExistingObject (object raw_obj, int js_id)
237 JSObject obj = raw_obj as JSObject;
239 if (obj == null && !raw_to_js.TryGetValue (raw_obj, out obj))
240 raw_to_js [raw_obj] = obj = new JSObject (js_id, raw_obj);
242 return (int)(IntPtr)obj.Handle;
245 static int GetJSObjectId (object raw_obj)
247 JSObject obj = raw_obj as JSObject;
249 if (obj == null && !raw_to_js.TryGetValue (raw_obj, out obj))
250 return -1;
252 return obj != null ? obj.JSHandle : -1;
255 static object GetMonoObject (int gc_handle)
257 GCHandle h = (GCHandle)(IntPtr)gc_handle;
258 JSObject o = (JSObject)h.Target;
259 if (o != null && o.RawObject != null)
260 return o.RawObject;
261 return o;
264 static object BoxInt (int i)
266 return i;
268 static object BoxDouble (double d)
270 return d;
273 static object BoxBool (int b)
275 return b == 0 ? false : true;
278 static bool IsSimpleArray (object a)
280 if (a is Array arr) {
281 if (arr.Rank == 1 && arr.GetLowerBound (0) == 0)
282 return true;
284 return false;
288 static object GetCoreType (string coreObj)
290 Assembly asm = typeof (Runtime).Assembly;
291 Type type = asm.GetType (coreObj);
292 return type;
296 [StructLayout (LayoutKind.Explicit)]
297 internal struct IntPtrAndHandle {
298 [FieldOffset (0)]
299 internal IntPtr ptr;
301 [FieldOffset (0)]
302 internal RuntimeMethodHandle handle;
305 //FIXME this probably won't handle generics
306 static string GetCallSignature (IntPtr method_handle)
308 IntPtrAndHandle tmp = default (IntPtrAndHandle);
309 tmp.ptr = method_handle;
311 var mb = MethodBase.GetMethodFromHandle (tmp.handle);
313 string res = "";
314 foreach (var p in mb.GetParameters ()) {
315 var t = p.ParameterType;
317 switch (Type.GetTypeCode (t)) {
318 case TypeCode.Byte:
319 case TypeCode.SByte:
320 case TypeCode.Int16:
321 case TypeCode.UInt16:
322 case TypeCode.Int32:
323 case TypeCode.UInt32:
324 case TypeCode.Boolean:
325 // Enums types have the same code as their underlying numeric types
326 if (t.IsEnum)
327 res += "j";
328 else
329 res += "i";
330 break;
331 case TypeCode.Int64:
332 case TypeCode.UInt64:
333 // Enums types have the same code as their underlying numeric types
334 if (t.IsEnum)
335 res += "k";
336 else
337 res += "l";
338 break;
339 case TypeCode.Single:
340 res += "f";
341 break;
342 case TypeCode.Double:
343 res += "d";
344 break;
345 case TypeCode.String:
346 res += "s";
347 break;
348 default:
349 if (t == typeof(IntPtr)) {
350 res += "i";
351 } else {
352 if (t.IsValueType)
353 throw new Exception("Can't handle VT arguments");
354 res += "o";
356 break;
360 return res;
363 static object ObjectToEnum (IntPtr method_handle, int parm, object obj)
365 IntPtrAndHandle tmp = default (IntPtrAndHandle);
366 tmp.ptr = method_handle;
368 var mb = MethodBase.GetMethodFromHandle (tmp.handle);
369 var parmType = mb.GetParameters () [parm].ParameterType;
370 if (parmType.IsEnum)
371 return Runtime.EnumFromExportContract (parmType, obj);
372 else
373 return null;
378 static MethodInfo gsjsc;
379 static void GenericSetupJSContinuation<T> (Task<T> task, JSObject cont_obj)
381 task.GetAwaiter ().OnCompleted ((Action)(() => {
383 if (task.Exception != null)
384 cont_obj.Invoke ((string)"reject", task.Exception.ToString ());
385 else {
386 cont_obj.Invoke ((string)"resolve", task.Result);
389 cont_obj.Dispose ();
390 FreeObject (task);
392 }));
395 static void SetupJSContinuation (Task task, JSObject cont_obj)
397 if (task.GetType () == typeof (Task)) {
398 task.GetAwaiter ().OnCompleted ((Action)(() => {
400 if (task.Exception != null)
401 cont_obj.Invoke ((string)"reject", task.Exception.ToString ());
402 else
403 cont_obj.Invoke ((string)"resolve", (object [])null);
405 cont_obj.Dispose ();
406 FreeObject (task);
407 }));
408 } else {
409 //FIXME this is horrible codegen, we can do better with per-method glue
410 if (gsjsc == null)
411 gsjsc = typeof (Runtime).GetMethod ("GenericSetupJSContinuation", BindingFlags.NonPublic | BindingFlags.Static);
412 gsjsc.MakeGenericMethod (task.GetType ().GetGenericArguments ()).Invoke (null, new object [] { task, cont_obj });
417 /// <summary>
418 /// Fetches a global object from the Javascript world, either from the current brower window or from the node.js global context.
419 /// </summary>
420 /// <remarks>
421 /// This method returns the value of a global object marshalled for consumption in C#.
422 /// </remarks>
423 /// <returns>
424 /// <para>
425 /// The return value can either be a primitive (string, int, double), a
426 /// <see cref="T:WebAssembly.JSObject"/> for JavaScript objects, a
427 /// <see cref="T:System.Threading.Tasks.Task"/>(object) for JavaScript promises, an array of
428 /// a byte, int or double (for Javascript objects typed as ArrayBuffer) or a
429 /// <see cref="T:System.Func"/> to represent JavaScript functions. The specific version of
430 /// the Func that will be returned depends on the parameters of the Javascript function
431 /// and return value.
432 /// </para>
433 /// <para>
434 /// The value of a returned promise (The Task(object) return) can in turn be any of the above
435 /// valuews.
436 /// </para>
437 /// </returns>
438 /// <param name="str">The name of the global object, or null if you want to retrieve the 'global' object itself.
439 /// On a browser, this is the 'window' object, on node.js it is the 'global' object.
440 /// </param>
441 public static object GetGlobalObject (string str = null)
443 int exception;
444 var globalHandle = Runtime.GetGlobalObject (str, out exception);
446 if (exception != 0)
447 throw new JSException ($"Error obtaining a handle to global {str}");
449 return globalHandle;
452 static string ObjectToString (object o)
455 if (o is Enum)
456 return EnumToExportContract ((Enum)o).ToString ();
458 return o.ToString ();
461 // This is simple right now and will include FlagsAttribute later.
462 public static Enum EnumFromExportContract (Type enumType, object value)
465 if (!enumType.IsEnum) {
466 throw new ArgumentException ("Type provided must be an Enum.", nameof (enumType));
469 if (value is string) {
471 var fields = enumType.GetFields ();
472 foreach (var fi in fields) {
473 // Do not process special names
474 if (fi.IsSpecialName)
475 continue;
477 ExportAttribute [] attributes =
478 (ExportAttribute [])fi.GetCustomAttributes (typeof (ExportAttribute), false);
480 var enumConversionType = ConvertEnum.Default;
482 object contractName = null;
484 if (attributes != null && attributes.Length > 0) {
485 enumConversionType = attributes [0].EnumValue;
486 if (enumConversionType != ConvertEnum.Numeric)
487 contractName = attributes [0].ContractName;
491 if (contractName == null)
492 contractName = fi.Name;
494 switch (enumConversionType) {
495 case ConvertEnum.ToLower:
496 contractName = contractName.ToString ().ToLower ();
497 break;
498 case ConvertEnum.ToUpper:
499 contractName = contractName.ToString ().ToUpper ();
500 break;
501 case ConvertEnum.Numeric:
502 contractName = (int)Enum.Parse (value.GetType (), contractName.ToString ());
503 break;
504 default:
505 contractName = contractName.ToString ();
506 break;
509 if (contractName.ToString () == value.ToString ()) {
510 return (Enum)Enum.Parse (enumType, fi.Name);
515 throw new ArgumentException ($"Value is a name, but not one of the named constants defined for the enum of type: {enumType}.", nameof (value));
516 } else {
517 return (Enum)Enum.ToObject (enumType, value);
522 // This is simple right now and will include FlagsAttribute later.
523 public static object EnumToExportContract (Enum value)
526 FieldInfo fi = value.GetType ().GetField (value.ToString ());
528 ExportAttribute [] attributes =
529 (ExportAttribute [])fi.GetCustomAttributes (typeof (ExportAttribute), false);
531 var enumConversionType = ConvertEnum.Default;
533 object contractName = null;
535 if (attributes != null && attributes.Length > 0) {
536 enumConversionType = attributes [0].EnumValue;
537 if (enumConversionType != ConvertEnum.Numeric)
538 contractName = attributes [0].ContractName;
542 if (contractName == null)
543 contractName = value;
545 switch (enumConversionType) {
546 case ConvertEnum.ToLower:
547 contractName = contractName.ToString ().ToLower ();
548 break;
549 case ConvertEnum.ToUpper:
550 contractName = contractName.ToString ().ToUpper ();
551 break;
552 case ConvertEnum.Numeric:
553 contractName = (int)Enum.Parse (value.GetType (), contractName.ToString ());
554 break;
555 default:
556 contractName = contractName.ToString ();
557 break;
560 return contractName;