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
{
10 /// Provides access to the Mono/WebAssembly runtime to perform tasks like invoking JavaScript functions and retrieving global variables.
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
);
51 /// Execute the provided string in the JavaScript context
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
);
59 throw new JSException (res
);
63 static Dictionary
<int, JSObject
> bound_objects
= new Dictionary
<int, JSObject
> ();
64 static Dictionary
<object, JSObject
> raw_to_js
= new Dictionary
<object, JSObject
> ();
70 /// Creates a new JavaScript object of the specified type
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
);
79 throw new JSException ((string)res
);
83 public static int New (string hostClassName
, params object [] _params
)
85 var res
= New (hostClassName
, _params
, out int exception
);
87 throw new JSException ((string)res
);
92 /// Creates a new JavaScript object
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
);
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
);
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}");
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
;
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
);
161 obj
.IsDisposed
= true;
162 obj
.RawObject
= null;
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
);
179 ReleaseHandle (obj
.JSHandle
, out exception
);
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.
186 obj
.IsDisposed
= true;
187 obj
.RawObject
= null;
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
);
201 Runtime
.ReleaseObject (jsobj
.JSHandle
, out exception
);
203 throw new JSException ($"Error releasing object on (raw-obj)");
206 jsobj
.RawObject
= null;
207 jsobj
.IsDisposed
= true;
208 jsobj
.Handle
.Free ();
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
))
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)
264 static object BoxInt (int i
)
268 static object BoxDouble (double 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)
288 static object GetCoreType (string coreObj
)
290 Assembly asm
= typeof (Runtime
).Assembly
;
291 Type type
= asm
.GetType (coreObj
);
296 [StructLayout (LayoutKind
.Explicit
)]
297 internal struct IntPtrAndHandle
{
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
);
314 foreach (var p
in mb
.GetParameters ()) {
315 var t
= p
.ParameterType
;
317 switch (Type
.GetTypeCode (t
)) {
321 case TypeCode
.UInt16
:
323 case TypeCode
.UInt32
:
324 case TypeCode
.Boolean
:
325 // Enums types have the same code as their underlying numeric types
332 case TypeCode
.UInt64
:
333 // Enums types have the same code as their underlying numeric types
339 case TypeCode
.Single
:
342 case TypeCode
.Double
:
345 case TypeCode
.String
:
349 if (t
== typeof(IntPtr
)) {
353 throw new Exception("Can't handle VT arguments");
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
;
371 return Runtime
.EnumFromExportContract (parmType
, obj
);
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 ());
386 cont_obj
.Invoke ((string)"resolve", task
.Result
);
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 ());
403 cont_obj
.Invoke ((string)"resolve", (object [])null);
409 //FIXME this is horrible codegen, we can do better with per-method glue
411 gsjsc
= typeof (Runtime
).GetMethod ("GenericSetupJSContinuation", BindingFlags
.NonPublic
| BindingFlags
.Static
);
412 gsjsc
.MakeGenericMethod (task
.GetType ().GetGenericArguments ()).Invoke (null, new object [] { task, cont_obj }
);
418 /// Fetches a global object from the Javascript world, either from the current brower window or from the node.js global context.
421 /// This method returns the value of a global object marshalled for consumption in C#.
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.
434 /// The value of a returned promise (The Task(object) return) can in turn be any of the above
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.
441 public static object GetGlobalObject (string str
= null)
444 var globalHandle
= Runtime
.GetGlobalObject (str
, out exception
);
447 throw new JSException ($"Error obtaining a handle to global {str}");
452 static string ObjectToString (object o
)
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
)
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 ();
498 case ConvertEnum
.ToUpper
:
499 contractName
= contractName
.ToString ().ToUpper ();
501 case ConvertEnum
.Numeric
:
502 contractName
= (int)Enum
.Parse (value.GetType (), contractName
.ToString ());
505 contractName
= contractName
.ToString ();
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));
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 ();
549 case ConvertEnum
.ToUpper
:
550 contractName
= contractName
.ToString ().ToUpper ();
552 case ConvertEnum
.Numeric
:
553 contractName
= (int)Enum
.Parse (value.GetType (), contractName
.ToString ());
556 contractName
= contractName
.ToString ();