Use mono_bind_static_method for invoking JS methods (#17942)

* Use mono_bind_static_method for invoking JS methods
This commit is contained in:
Pranav K 2019-12-18 12:18:18 -08:00 committed by GitHub
parent b0568d5c28
commit 62e2fb22e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 35 additions and 137 deletions

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
{ {
var assembly = Assembly.Load(assemblyName); var assembly = Assembly.Load(assemblyName);
var entrypoint = FindUnderlyingEntrypoint(assembly); var entrypoint = FindUnderlyingEntrypoint(assembly);
var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args } : new object[] { }; var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args ?? Array.Empty<string>() } : new object[] { };
entrypointResult = entrypoint.Invoke(null, @params); entrypointResult = entrypoint.Invoke(null, @params);
} }
catch (Exception syncException) catch (Exception syncException)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,9 @@
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform'; import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getFileNameFromUrl } from '../Url'; import { getFileNameFromUrl } from '../Url';
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger'; import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
import { showErrorNotification } from '../../BootErrors'; import { showErrorNotification } from '../../BootErrors';
const assemblyHandleCache: { [assemblyName: string]: number } = {};
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
const methodHandleCache: { [fullyQualifiedMethodName: string]: MethodHandle } = {};
let assembly_load: (assemblyName: string) => number;
let find_class: (assemblyHandle: number, namespace: string, className: string) => number;
let find_method: (typeHandle: number, methodName: string, unknownArg: number) => MethodHandle;
let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object;
let mono_string_array_new: (length: number) => System_Array<System_String>;
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr; let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
let mono_string: (jsString: string) => System_String;
const appBinDirName = 'appBinDir'; const appBinDirName = 'appBinDir';
const uint64HighOrderShift = Math.pow(2, 32); const uint64HighOrderShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
@ -38,8 +28,6 @@ export const monoPlatform: Platform = {
}); });
}, },
findMethod: findMethod,
callEntryPoint: function callEntryPoint(assemblyName: string) { callEntryPoint: function callEntryPoint(assemblyName: string) {
// Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking // Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking
// the entrypoint which adds support for async main. // the entrypoint which adds support for async main.
@ -47,40 +35,9 @@ export const monoPlatform: Platform = {
// In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the // In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the
// outer promise reflects the startup process, and the inner one reflects the possibly-async // outer promise reflects the startup process, and the inner one reflects the possibly-async
// .NET entrypoint method. // .NET entrypoint method.
const invokeEntrypoint = findMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting', 'EntrypointInvoker', 'InvokeEntrypoint'); const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint');
this.callMethod(invokeEntrypoint, null, [ // Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved.
this.toDotNetString(assemblyName), invokeEntrypoint(assemblyName, null);
mono_string_array_new(0) // In the future, we may have a way of supplying arg strings. For now, we always supply an empty string[].
]);
},
callMethod: function callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object {
if (args.length > 4) {
// Hopefully this restriction can be eased soon, but for now make it clear what's going on
throw new Error(`Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass ${args.length}.`);
}
const stack = Module.stackSave();
try {
const argsBuffer = Module.stackAlloc(args.length);
const exceptionFlagManagedInt = Module.stackAlloc(4);
for (let i = 0; i < args.length; ++i) {
Module.setValue(argsBuffer + i * 4, args[i], 'i32');
}
Module.setValue(exceptionFlagManagedInt, 0, 'i32');
const res = invoke_method(method, target, argsBuffer, exceptionFlagManagedInt);
if (Module.getValue(exceptionFlagManagedInt, 'i32') !== 0) {
// If the exception flag is set, the returned value is exception.ToString()
throw new Error(monoPlatform.toJavaScriptString(<System_String>res));
}
return res;
} finally {
Module.stackRestore(stack);
}
}, },
toJavaScriptString: function toJavaScriptString(managedString: System_String) { toJavaScriptString: function toJavaScriptString(managedString: System_String) {
@ -94,10 +51,6 @@ export const monoPlatform: Platform = {
return res; return res;
}, },
toDotNetString: function toDotNetString(jsString: string): System_String {
return mono_string(jsString);
},
toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array { toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
const dataPtr = getArrayDataPointer(array); const dataPtr = getArrayDataPointer(array);
const length = Module.getValue(dataPtr, 'i32'); const length = Module.getValue(dataPtr, 'i32');
@ -158,44 +111,6 @@ export const monoPlatform: Platform = {
}, },
}; };
function findAssembly(assemblyName: string): number {
let assemblyHandle = assemblyHandleCache[assemblyName];
if (!assemblyHandle) {
assemblyHandle = assembly_load(assemblyName);
if (!assemblyHandle) {
throw new Error(`Could not find assembly "${assemblyName}"`);
}
assemblyHandleCache[assemblyName] = assemblyHandle;
}
return assemblyHandle;
}
function findType(assemblyName: string, namespace: string, className: string): number {
const fullyQualifiedTypeName = `[${assemblyName}]${namespace}.${className}`;
let typeHandle = typeHandleCache[fullyQualifiedTypeName];
if (!typeHandle) {
typeHandle = find_class(findAssembly(assemblyName), namespace, className);
if (!typeHandle) {
throw new Error(`Could not find type "${className}" in namespace "${namespace}" in assembly "${assemblyName}"`);
}
typeHandleCache[fullyQualifiedTypeName] = typeHandle;
}
return typeHandle;
}
function findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle {
const fullyQualifiedMethodName = `[${assemblyName}]${namespace}.${className}::${methodName}`;
let methodHandle = methodHandleCache[fullyQualifiedMethodName];
if (!methodHandle) {
methodHandle = find_method(findType(assemblyName, namespace, className), methodName, -1);
if (!methodHandle) {
throw new Error(`Could not find method "${methodName}" on type "${namespace}.${className}"`);
}
methodHandleCache[fullyQualifiedMethodName] = methodHandle;
}
return methodHandle;
}
function addScriptTagsToDocument() { function addScriptTagsToDocument() {
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
if (!browserSupportsNativeWebAssembly) { if (!browserSupportsNativeWebAssembly) {
@ -254,26 +169,8 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
'number', 'number',
'number', 'number',
]); ]);
assembly_load = Module.cwrap('mono_wasm_assembly_load', 'number', ['string']);
find_class = Module.cwrap('mono_wasm_assembly_find_class', 'number', [
'number',
'string',
'string',
]);
find_method = Module.cwrap('mono_wasm_assembly_find_method', 'number', [
'number',
'string',
'number',
]);
invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', [
'number',
'number',
'number',
]);
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']);
mono_string_array_new = Module.cwrap('mono_wasm_string_array_new', 'number', ['number']);
MONO.loaded_files = []; MONO.loaded_files = [];
@ -346,10 +243,16 @@ function getArrayDataPointer<T>(array: System_Array<T>): number {
return <number><any>array + 12; // First byte from here is length, then following bytes are entries return <number><any>array + 12; // First byte from here is length, then following bytes are entries
} }
function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any {
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
const fqn = `[${assembly}] ${typeName}:${method}`;
return Module.mono_bind_static_method(fqn);
}
function attachInteropInvoker(): void { function attachInteropInvoker(): void {
const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet'); const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet');
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet'); const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
const dotNetDispatcherEndInvokeJSMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'EndInvokeJS'); const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS');
DotNet.attachDispatcher({ DotNet.attachDispatcher({
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => { beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
@ -362,30 +265,25 @@ function attachInteropInvoker(): void {
? dotNetObjectId.toString() ? dotNetObjectId.toString()
: assemblyName; : assemblyName;
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [ dotNetDispatcherBeginInvokeMethodHandle(
callId ? monoPlatform.toDotNetString(callId.toString()) : null, callId ? callId.toString() : null,
monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId), assemblyNameOrDotNetObjectId,
monoPlatform.toDotNetString(methodIdentifier), methodIdentifier,
monoPlatform.toDotNetString(argsJson), argsJson,
]); );
}, },
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => { endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
monoPlatform.callMethod( dotNetDispatcherEndInvokeJSMethodHandle(
dotNetDispatcherEndInvokeJSMethodHandle, serializedArgs
null,
[monoPlatform.toDotNetString(serializedArgs)]
); );
}, },
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => { invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [ return dotNetDispatcherInvokeMethodHandle(
assemblyName ? monoPlatform.toDotNetString(assemblyName) : null, assemblyName ? assemblyName : null,
monoPlatform.toDotNetString(methodIdentifier), methodIdentifier,
dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null, dotNetObjectId ? dotNetObjectId.toString() : null,
monoPlatform.toDotNetString(argsJson), argsJson,
]) as System_String; ) as string;
return resultJsonStringPtr
? monoPlatform.toJavaScriptString(resultJsonStringPtr)
: null;
}, },
}); });
} }

View File

@ -10,7 +10,7 @@ declare namespace Module {
function FS_createPath(parent, path, canRead, canWrite); function FS_createPath(parent, path, canRead, canWrite);
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn); function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
function mono_call_assembly_entry_point(assemblyName: string, args: any[]): any; function mono_bind_static_method(fqn: string): BoundStaticMethod;
} }
// Emscripten declares these globals // Emscripten declares these globals
@ -28,3 +28,7 @@ declare namespace MONO {
var mono_wasm_runtime_is_ready: boolean; var mono_wasm_runtime_is_ready: boolean;
function mono_wasm_setenv (name: string, value: string): void; function mono_wasm_setenv (name: string, value: string): void;
} }
// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are
// artifically limiting it to a subset of types that we actually use.
declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null);

View File

@ -2,12 +2,8 @@ export interface Platform {
start(loadAssemblyUrls: string[]): Promise<void>; start(loadAssemblyUrls: string[]): Promise<void>;
callEntryPoint(assemblyName: string): void; callEntryPoint(assemblyName: string): void;
findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle;
callMethod(method: MethodHandle, target: System_Object | null, args: (System_Object | null)[]): System_Object;
toJavaScriptString(dotNetString: System_String): string; toJavaScriptString(dotNetString: System_String): string;
toDotNetString(javaScriptString: string): System_String;
toUint8Array(array: System_Array<any>): Uint8Array; toUint8Array(array: System_Array<any>): Uint8Array;
getArrayLength(array: System_Array<any>): number; getArrayLength(array: System_Array<any>): number;