Skip to content

Commit

Permalink
[browser] Move reflection from C# to C (#98534)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Feb 16, 2024
1 parent 7185df8 commit de12fe7
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 136 deletions.
8 changes: 6 additions & 2 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ internal static unsafe partial class Runtime
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void CancelPromise(nint gcHandle);
#endif
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void SetEntryAssembly(Assembly assembly, int entryPointMetadataToken);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void AssemblyGetEntryPoint(IntPtr assemblyNamePtr, int auto_insert_breakpoint, void** monoMethodPtrPtr);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void BindAssemblyExports(IntPtr assemblyNamePtr);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, IntPtr* monoMethodPtrPtr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@
<data name="MissingManagedEntrypointHandle" xml:space="preserve">
<value>Managed entrypoint handle is not set.</value>
</data>
<data name="CannotResolveManagedEntrypoint" xml:space="preserve">
<value>Cannot resolve managed entrypoint {0} in assembly {1}.</value>
<data name="CannotResolveManagedEntrypointHandle" xml:space="preserve">
<value>Cannot resolve managed entrypoint handle.</value>
</data>
<data name="ReturnTypeNotSupportedForMain" xml:space="preserve">
<value>Return type '{0}' from main method in not supported.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace System.Runtime.InteropServices.JavaScript
internal static unsafe partial class JavaScriptExports
{
// the marshaled signature is:
// Task<int>? CallEntrypoint(string mainAssemblyName, string[] args, bool waitForDebugger)
// Task<int>? CallEntrypoint(char* assemblyNamePtr, string[] args)
public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
Expand All @@ -30,11 +30,11 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
arg_exc.AssertCurrentThreadContext();
#endif

arg_1.ToManaged(out string? mainAssemblyName);
arg_1.ToManaged(out IntPtr assemblyNamePtr);
arg_2.ToManaged(out string?[]? args);
arg_3.ToManaged(out bool waitForDebugger);

Task<int>? result = JSHostImplementation.CallEntrypoint(mainAssemblyName, args, waitForDebugger);
Task<int>? result = JSHostImplementation.CallEntrypoint(assemblyNamePtr, args, waitForDebugger);

arg_result.ToJS(result, (ref JSMarshalerArgument arg, int value) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,47 +200,19 @@ public static void LoadSatelliteAssembly(byte[] dllBytes)
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
}

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Dynamic access from JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Dynamic access from JavaScript")]
public static Task<int>? CallEntrypoint(string? assemblyName, string?[]? args, bool waitForDebugger)
public static unsafe Task<int>? CallEntrypoint(IntPtr assemblyNamePtr, string?[]? args, bool waitForDebugger)
{
try
{
if (string.IsNullOrEmpty(assemblyName))
{
throw new MissingMethodException(SR.MissingManagedEntrypointHandle);
}
if (!assemblyName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
{
assemblyName += ".dll";
}
Assembly mainAssembly = Assembly.LoadFrom(assemblyName);

MethodInfo? method = mainAssembly.EntryPoint;
void* ptr;
Interop.Runtime.AssemblyGetEntryPoint(assemblyNamePtr, waitForDebugger ? 1 : 0, &ptr);
RuntimeMethodHandle methodHandle = GetMethodHandleFromIntPtr((IntPtr)ptr);
// this would not work for generic types. But Main() could not be generic, so we are fine.
MethodInfo? method = MethodBase.GetMethodFromHandle(methodHandle) as MethodInfo;
if (method == null)
{
throw new InvalidOperationException(string.Format(SR.CannotResolveManagedEntrypoint, "Main", assemblyName));
throw new InvalidOperationException(SR.CannotResolveManagedEntrypointHandle);
}
if (method.IsSpecialName)
{
// we are looking for the original async method, rather than for the compiler generated wrapper like <Main>
// because we need to yield to browser event loop
var type = method.DeclaringType!;
var name = method.Name;
var asyncName = name + "$";
method = type.GetMethod(asyncName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null)
{
asyncName = name.Substring(1, name.Length - 2);
method = type.GetMethod(asyncName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}
if (method == null)
{
throw new InvalidOperationException(string.Format(SR.CannotResolveManagedEntrypoint, asyncName, assemblyName));
}
}

Interop.Runtime.SetEntryAssembly(mainAssembly, waitForDebugger ? method.MetadataToken : 0);

object[] argsToPass = System.Array.Empty<object>();
Task<int>? result = null;
Expand Down Expand Up @@ -293,75 +265,32 @@ public static void LoadSatelliteAssembly(byte[] dllBytes)
}
}

private static string GeneratedInitializerClassName = "System.Runtime.InteropServices.JavaScript.__GeneratedInitializer";
private static string GeneratedInitializerMethodName = "__Register_";

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Dynamic access from JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Dynamic access from JavaScript")]
public static Task BindAssemblyExports(string? assemblyName)
public static unsafe Task BindAssemblyExports(string? assemblyName)
{
try
{
if (string.IsNullOrEmpty(assemblyName))
{
throw new MissingMethodException("Missing assembly name");
}
if (!assemblyName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
{
assemblyName += ".dll";
}

Assembly assembly = Assembly.LoadFrom(assemblyName);
Type? type = assembly.GetType(GeneratedInitializerClassName);
if (type == null)
{
foreach (var module in assembly.Modules)
{
RuntimeHelpers.RunModuleConstructor(module.ModuleHandle);
}
}
else
{
MethodInfo? methodInfo = type.GetMethod(GeneratedInitializerMethodName, BindingFlags.NonPublic | BindingFlags.Static);
methodInfo?.Invoke(null, []);
}

return Task.CompletedTask;
}
catch (Exception ex)
{
if (ex is TargetInvocationException refEx && refEx.InnerException != null)
ex = refEx.InnerException;
return Task.FromException(ex);
}
Interop.Runtime.BindAssemblyExports(Marshal.StringToCoTaskMemUTF8(assemblyName));
return Task.CompletedTask;
}

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "TODO https://github.com/dotnet/runtime/issues/98366")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "TODO https://github.com/dotnet/runtime/issues/98366")]
public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
if (string.IsNullOrEmpty(fullyQualifiedName))
{
throw new ArgumentNullException(nameof(fullyQualifiedName));
}
var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
var wrapper_name = $"__Wrapper_{methodName}_{signatureHash}";
var dllName = assemblyName + ".dll";

var signature = GetMethodSignature(signatures, null, null);
var (assemblyName, className, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
IntPtr monoMethod;
Interop.Runtime.GetAssemblyExport(
Marshal.StringToCoTaskMemUTF8(dllName),
Marshal.StringToCoTaskMemUTF8(nameSpace),
Marshal.StringToCoTaskMemUTF8(shortClassName),
Marshal.StringToCoTaskMemUTF8(wrapper_name),
&monoMethod);

Assembly assembly = Assembly.LoadFrom(assemblyName + ".dll");
Type? type = assembly.GetType(className);
if (type == null)
if (monoMethod == IntPtr.Zero)
{
throw new InvalidOperationException("Class not found " + className);
}
var wrapper_name = $"__Wrapper_{methodName}_{signatureHash}";
var methodInfo = type.GetMethod(wrapper_name, BindingFlags.NonPublic | BindingFlags.Static);
if (methodInfo == null)
{
throw new InvalidOperationException("Method not found " + wrapper_name);
Environment.FailFast($"Can't find {nameSpace}{shortClassName}{methodName} in {assemblyName}.dll");
}

var monoMethod = GetIntPtrFromMethodHandle(methodInfo.MethodHandle);
var signature = GetMethodSignature(signatures, null, null);

JavaScriptImports.BindCSFunction(monoMethod, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);

Expand All @@ -381,14 +310,13 @@ public static void SetHasExternalEventLoop(Thread thread)
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetIntPtrFromMethodHandle(RuntimeMethodHandle methodHandle)
public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
{
var temp = new IntPtrAndHandle { methodHandle = methodHandle };
return temp.ptr;
var temp = new IntPtrAndHandle { ptr = ptr };
return temp.methodHandle;
}


public static (string assemblyName, string className, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn)
public static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn)
{
var assembly = fqn.Substring(fqn.IndexOf('[') + 1, fqn.IndexOf(']') - 1).Trim();
fqn = fqn.Substring(fqn.IndexOf(']') + 1).Trim();
Expand All @@ -410,7 +338,7 @@ public static (string assemblyName, string className, string nameSpace, string s
throw new InvalidOperationException("No class name specified " + fqn);
if (string.IsNullOrEmpty(methodName))
throw new InvalidOperationException("No method name specified " + fqn);
return (assembly, className, nameSpace, shortClassName, methodName);
return (assembly, nameSpace, shortClassName, methodName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ try {
try {
const monoMethodPtr = App.exports.DebuggerTests.BindStaticMethod.GetMonoMethodPtr(method_name);
// this is only implemented for void methods with no arguments
const invoker = runtime.Module.cwrap("mono_wasm_invoke_method", "number", ["number", "number", "number"]);
const invoker = runtime.Module.cwrap("mono_wasm_invoke_method", "void", ["number", "number"]);
return function () {
try {
return invoker(monoMethodPtr, 0, 0);
Expand Down
Loading

0 comments on commit de12fe7

Please sign in to comment.