Skip to content

Commit

Permalink
[wasm] Redesign of managed objects marshaling and lifecycle (#56538)
Browse files Browse the repository at this point in the history
file cycle of JS owned C# instances is driven by FinalizationRegistry

- got rid of Runtime._weakDelegateTable and Runtime._rawToJS
- got rid of JSObject.WeakRawObject and JSObject.RawObject
- GCHandle instead of JSObject double proxy for plain managed ref types
- GCHandle instead of int sequence for delegates + redesign of invocation
- GCHandle for task + redesign of invocation
- improved in-flight retention of thenable/promise and Task
- explicitly delegate type of parameter for EventListener
- moved and renamed some binding functions
- renamed all handles to jsHandle or gcHandle as appropriate
- removed jsHandle math
- cleanup of unused functions
- improved error messages for invalid handles
- more unit tests
  • Loading branch information
pavelsavara authored Aug 7, 2021
1 parent 4803ce2 commit e49b9bd
Show file tree
Hide file tree
Showing 12 changed files with 688 additions and 600 deletions.
28 changes: 12 additions & 16 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,37 @@ internal static partial class Runtime
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object CompileFunction(string str, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object InvokeJSWithArgs(int jsObjHandle, string method, object?[] parms, out int exceptionalResult);
internal static extern object InvokeJSWithArgs(int jsHandle, string method, object?[] parms, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object GetObjectProperty(int jsObjHandle, string propertyName, out int exceptionalResult);
internal static extern object GetObjectProperty(int jsHandle, string propertyName, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object SetObjectProperty(int jsObjHandle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult);
internal static extern object SetObjectProperty(int jsHandle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object GetByIndex(int jsObjHandle, int index, out int exceptionalResult);
internal static extern object GetByIndex(int jsHandle, int index, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object SetByIndex(int jsObjHandle, int index, object? value, out int exceptionalResult);
internal static extern object SetByIndex(int jsHandle, int index, object? value, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object GetGlobalObject(string? globalName, out int exceptionalResult);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object ReleaseHandle(int jsObjHandle, out int exceptionalResult);
internal static extern object ReleaseHandle(int jsHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object ReleaseObject(int jsObjHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object BindCoreObject(int jsObjHandle, int gcHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object BindHostObject(int jsObjHandle, int gcHandle, out int exceptionalResult);
internal static extern object BindCoreObject(int jsHandle, int gcHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object New(string className, object[] parms, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayToArray(int jsObjHandle, out int exceptionalResult);
internal static extern object TypedArrayToArray(int jsHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayCopyTo(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
internal static extern object TypedArrayCopyTo(int jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayFrom(int arrayPtr, int begin, int end, int bytesPerElement, int type, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayCopyFrom(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
internal static extern object TypedArrayCopyFrom(int jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern string? AddEventListener(int jsObjHandle, string name, int weakDelegateHandle, int optionsObjHandle);
internal static extern string? AddEventListener(int jsHandle, string name, int gcHandle, int optionsJsHandle);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern string? RemoveEventListener(int jsObjHandle, string name, int weakDelegateHandle, bool capture);
internal static extern string? RemoveEventListener(int jsHandle, string name, int gcHandle, bool capture);

// / <summary>
// / Execute the provided string in the JavaScript context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

Expand All @@ -16,17 +14,14 @@ public abstract class AnyRef : SafeHandleMinusOneIsInvalid
private GCHandle AnyRefHandle;
public int JSHandle => (int)handle;

internal AnyRef(int jsHandle, bool ownsHandle) : this((IntPtr)jsHandle, ownsHandle)
{ }

internal AnyRef(IntPtr jsHandle, bool ownsHandle) : base(ownsHandle)
{
SetHandle(jsHandle);
AnyRefHandle = GCHandle.Alloc(this, ownsHandle ? GCHandleType.Weak : GCHandleType.Normal);
InFlight = null;
InFlightCounter = 0;
}
internal int Int32Handle => (int)(IntPtr)AnyRefHandle;
internal int GCHandleValue => (int)(IntPtr)AnyRefHandle;

internal void AddInFlight()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class CoreObject : JSObject
{
protected CoreObject(int jsHandle) : base(jsHandle, true)
{
object result = Interop.Runtime.BindCoreObject(jsHandle, Int32Handle, out int exception);
object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
if (exception != 0)
throw new JSException(SR.Format(SR.CoreObjectErrorBinding, result));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public HostObject(string hostName, params object[] _params) : base(Interop.Runti

public abstract class HostObjectBase : JSObject, IHostObject
{
protected HostObjectBase(int jHandle) : base(jHandle, true)
protected HostObjectBase(int jsHandle) : base(jsHandle, true)
{
object result = Interop.Runtime.BindHostObject(jHandle, Int32Handle, out int exception);
object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
if (exception != 0)
throw new JSException(SR.Format(SR.HostObjectErrorBinding, result));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@ public interface IJSObject
/// </summary>
public class JSObject : AnyRef, IJSObject, IDisposable
{
internal object? RawObject;

private WeakReference<Delegate>? WeakRawObject;

// to detect redundant calls
public bool IsDisposed { get; private set; }

public JSObject() : this(Interop.Runtime.New<object>(), true)
{
object result = Interop.Runtime.BindCoreObject(JSHandle, Int32Handle, out int exception);
object result = Interop.Runtime.BindCoreObject(JSHandle, GCHandleValue, out int exception);
if (exception != 0)
throw new JSException(SR.Format(SR.JSObjectErrorBinding, result));

Expand All @@ -39,16 +35,6 @@ internal JSObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
internal JSObject(int jsHandle, bool ownsHandle) : base((IntPtr)jsHandle, ownsHandle)
{ }

internal JSObject(int jsHandle, object rawObj) : base(jsHandle, false)
{
RawObject = rawObj;
}

internal JSObject(int jsHandle, Delegate rawDelegate, bool ownsHandle = true) : base(jsHandle, ownsHandle)
{
WeakRawObject = new WeakReference<Delegate>(rawDelegate, trackResurrection: false);
}

/// <summary>
/// Invoke a named method of the object, or throws a JSException on error.
/// </summary>
Expand Down Expand Up @@ -84,7 +70,7 @@ public struct EventListenerOptions {
public object? Signal;
}

public int AddEventListener(string name, Delegate listener, EventListenerOptions? options = null)
public int AddEventListener(string name, Action<JSObject> listener, EventListenerOptions? options = null)
{
var optionsDict = options.HasValue
? new JSObject()
Expand All @@ -94,7 +80,7 @@ public int AddEventListener(string name, Delegate listener, EventListenerOptions
if (options?.Signal != null)
throw new NotImplementedException("EventListenerOptions.Signal");

var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener);
// int exception;
if (options.HasValue) {
// TODO: Optimize this
Expand All @@ -117,17 +103,17 @@ public int AddEventListener(string name, Delegate listener, EventListenerOptions
}
}

public void RemoveEventListener(string name, Delegate? listener, EventListenerOptions? options = null)
public void RemoveEventListener(string name, Action<JSObject>? listener, EventListenerOptions? options = null)
{
if (listener == null)
return;
var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener);
RemoveEventListener(name, jsfunc, options);
}

public void RemoveEventListener(string name, int listenerHandle, EventListenerOptions? options = null)
public void RemoveEventListener(string name, int listenerGCHandle, EventListenerOptions? options = null)
{
var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerHandle, options?.Capture ?? false);
var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerGCHandle, options?.Capture ?? false);
if (ret != null)
throw new JSException(ret);
}
Expand Down Expand Up @@ -178,7 +164,7 @@ public void SetObjectProperty(string name, object value, bool createIfNotExists
{
object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception);
if (exception != 0)
throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{Int32Handle} raw '{RawObject != null})");
throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{GCHandleValue})");
}

/// <summary>
Expand All @@ -205,19 +191,11 @@ public int Length
/// <param name="prop">The String name or Symbol of the property to test.</param>
public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop);

internal bool IsWeakWrapper => WeakRawObject?.TryGetTarget(out _) == true;

internal object? GetWrappedObject()
{
return RawObject ?? (WeakRawObject is WeakReference<Delegate> wr && wr.TryGetTarget(out Delegate? d) ? d : null);
}
internal void FreeHandle()
{
Runtime.ReleaseJSObject(this);
SetHandleAsInvalid();
IsDisposed = true;
RawObject = null;
WeakRawObject = null;
FreeGCHandle();
}

Expand Down Expand Up @@ -258,7 +236,7 @@ protected override bool ReleaseHandle()

public override string ToString()
{
return $"(js-obj js '{Int32Handle}' raw '{RawObject != null}' weak_raw '{WeakRawObject != null}')";
return $"(js-obj js '{GCHandleValue}')";
}
}
}
Loading

0 comments on commit e49b9bd

Please sign in to comment.