Use mono_bind_static_method for invoking JS methods (#17942)
* Use mono_bind_static_method for invoking JS methods
This commit is contained in:
parent
b0568d5c28
commit
62e2fb22e5
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
|||
{
|
||||
var assembly = Assembly.Load(assemblyName);
|
||||
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);
|
||||
}
|
||||
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
|
|
@ -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 { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||
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: (jsString: string) => System_String;
|
||||
const appBinDirName = 'appBinDir';
|
||||
const uint64HighOrderShift = Math.pow(2, 32);
|
||||
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) {
|
||||
// Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking
|
||||
// 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
|
||||
// outer promise reflects the startup process, and the inner one reflects the possibly-async
|
||||
// .NET entrypoint method.
|
||||
const invokeEntrypoint = findMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting', 'EntrypointInvoker', 'InvokeEntrypoint');
|
||||
this.callMethod(invokeEntrypoint, null, [
|
||||
this.toDotNetString(assemblyName),
|
||||
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);
|
||||
}
|
||||
const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint');
|
||||
// Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved.
|
||||
invokeEntrypoint(assemblyName, null);
|
||||
},
|
||||
|
||||
toJavaScriptString: function toJavaScriptString(managedString: System_String) {
|
||||
|
|
@ -94,10 +51,6 @@ export const monoPlatform: Platform = {
|
|||
return res;
|
||||
},
|
||||
|
||||
toDotNetString: function toDotNetString(jsString: string): System_String {
|
||||
return mono_string(jsString);
|
||||
},
|
||||
|
||||
toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
|
||||
const dataPtr = getArrayDataPointer(array);
|
||||
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() {
|
||||
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
|
||||
if (!browserSupportsNativeWebAssembly) {
|
||||
|
|
@ -254,26 +169,8 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
'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 = 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 = [];
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
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 {
|
||||
const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet');
|
||||
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
||||
const dotNetDispatcherEndInvokeJSMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'EndInvokeJS');
|
||||
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet');
|
||||
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
||||
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS');
|
||||
|
||||
DotNet.attachDispatcher({
|
||||
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
|
||||
|
|
@ -362,30 +265,25 @@ function attachInteropInvoker(): void {
|
|||
? dotNetObjectId.toString()
|
||||
: assemblyName;
|
||||
|
||||
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [
|
||||
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
|
||||
monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId),
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
monoPlatform.toDotNetString(argsJson),
|
||||
]);
|
||||
dotNetDispatcherBeginInvokeMethodHandle(
|
||||
callId ? callId.toString() : null,
|
||||
assemblyNameOrDotNetObjectId,
|
||||
methodIdentifier,
|
||||
argsJson,
|
||||
);
|
||||
},
|
||||
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
|
||||
monoPlatform.callMethod(
|
||||
dotNetDispatcherEndInvokeJSMethodHandle,
|
||||
null,
|
||||
[monoPlatform.toDotNetString(serializedArgs)]
|
||||
dotNetDispatcherEndInvokeJSMethodHandle(
|
||||
serializedArgs
|
||||
);
|
||||
},
|
||||
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
|
||||
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
|
||||
assemblyName ? monoPlatform.toDotNetString(assemblyName) : null,
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null,
|
||||
monoPlatform.toDotNetString(argsJson),
|
||||
]) as System_String;
|
||||
return resultJsonStringPtr
|
||||
? monoPlatform.toJavaScriptString(resultJsonStringPtr)
|
||||
: null;
|
||||
return dotNetDispatcherInvokeMethodHandle(
|
||||
assemblyName ? assemblyName : null,
|
||||
methodIdentifier,
|
||||
dotNetObjectId ? dotNetObjectId.toString() : null,
|
||||
argsJson,
|
||||
) as string;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ declare namespace Module {
|
|||
function FS_createPath(parent, path, canRead, canWrite);
|
||||
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
|
||||
|
|
@ -28,3 +28,7 @@ declare namespace MONO {
|
|||
var mono_wasm_runtime_is_ready: boolean;
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,8 @@ export interface Platform {
|
|||
start(loadAssemblyUrls: string[]): Promise<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;
|
||||
toDotNetString(javaScriptString: string): System_String;
|
||||
|
||||
toUint8Array(array: System_Array<any>): Uint8Array;
|
||||
|
||||
getArrayLength(array: System_Array<any>): number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue