Skip to content

Commit 62e2fb2

Browse files
authored
Use mono_bind_static_method for invoking JS methods (#17942)
* Use mono_bind_static_method for invoking JS methods
1 parent b0568d5 commit 62e2fb2

File tree

6 files changed

+35
-137
lines changed

6 files changed

+35
-137
lines changed

src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void InvokeEntrypoint(string assemblyName, string[] args)
2222
{
2323
var assembly = Assembly.Load(assemblyName);
2424
var entrypoint = FindUnderlyingEntrypoint(assembly);
25-
var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args } : new object[] { };
25+
var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args ?? Array.Empty<string>() } : new object[] { };
2626
entrypointResult = entrypoint.Invoke(null, @params);
2727
}
2828
catch (Exception syncException)

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts

Lines changed: 27 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
1+
import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
22
import { getFileNameFromUrl } from '../Url';
33
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
44
import { showErrorNotification } from '../../BootErrors';
55

6-
const assemblyHandleCache: { [assemblyName: string]: number } = {};
7-
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
8-
const methodHandleCache: { [fullyQualifiedMethodName: string]: MethodHandle } = {};
9-
10-
let assembly_load: (assemblyName: string) => number;
11-
let find_class: (assemblyHandle: number, namespace: string, className: string) => number;
12-
let find_method: (typeHandle: number, methodName: string, unknownArg: number) => MethodHandle;
13-
let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object;
14-
let mono_string_array_new: (length: number) => System_Array<System_String>;
156
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
16-
let mono_string: (jsString: string) => System_String;
177
const appBinDirName = 'appBinDir';
188
const uint64HighOrderShift = Math.pow(2, 32);
199
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
@@ -38,49 +28,16 @@ export const monoPlatform: Platform = {
3828
});
3929
},
4030

41-
findMethod: findMethod,
42-
4331
callEntryPoint: function callEntryPoint(assemblyName: string) {
4432
// Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking
4533
// the entrypoint which adds support for async main.
4634
// Currently we disregard the return value from the entrypoint, whether it's sync or async.
4735
// In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the
4836
// outer promise reflects the startup process, and the inner one reflects the possibly-async
4937
// .NET entrypoint method.
50-
const invokeEntrypoint = findMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting', 'EntrypointInvoker', 'InvokeEntrypoint');
51-
this.callMethod(invokeEntrypoint, null, [
52-
this.toDotNetString(assemblyName),
53-
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[].
54-
]);
55-
},
56-
57-
callMethod: function callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object {
58-
if (args.length > 4) {
59-
// Hopefully this restriction can be eased soon, but for now make it clear what's going on
60-
throw new Error(`Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass ${args.length}.`);
61-
}
62-
63-
const stack = Module.stackSave();
64-
65-
try {
66-
const argsBuffer = Module.stackAlloc(args.length);
67-
const exceptionFlagManagedInt = Module.stackAlloc(4);
68-
for (let i = 0; i < args.length; ++i) {
69-
Module.setValue(argsBuffer + i * 4, args[i], 'i32');
70-
}
71-
Module.setValue(exceptionFlagManagedInt, 0, 'i32');
72-
73-
const res = invoke_method(method, target, argsBuffer, exceptionFlagManagedInt);
74-
75-
if (Module.getValue(exceptionFlagManagedInt, 'i32') !== 0) {
76-
// If the exception flag is set, the returned value is exception.ToString()
77-
throw new Error(monoPlatform.toJavaScriptString(<System_String>res));
78-
}
79-
80-
return res;
81-
} finally {
82-
Module.stackRestore(stack);
83-
}
38+
const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint');
39+
// Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved.
40+
invokeEntrypoint(assemblyName, null);
8441
},
8542

8643
toJavaScriptString: function toJavaScriptString(managedString: System_String) {
@@ -94,10 +51,6 @@ export const monoPlatform: Platform = {
9451
return res;
9552
},
9653

97-
toDotNetString: function toDotNetString(jsString: string): System_String {
98-
return mono_string(jsString);
99-
},
100-
10154
toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
10255
const dataPtr = getArrayDataPointer(array);
10356
const length = Module.getValue(dataPtr, 'i32');
@@ -158,44 +111,6 @@ export const monoPlatform: Platform = {
158111
},
159112
};
160113

161-
function findAssembly(assemblyName: string): number {
162-
let assemblyHandle = assemblyHandleCache[assemblyName];
163-
if (!assemblyHandle) {
164-
assemblyHandle = assembly_load(assemblyName);
165-
if (!assemblyHandle) {
166-
throw new Error(`Could not find assembly "${assemblyName}"`);
167-
}
168-
assemblyHandleCache[assemblyName] = assemblyHandle;
169-
}
170-
return assemblyHandle;
171-
}
172-
173-
function findType(assemblyName: string, namespace: string, className: string): number {
174-
const fullyQualifiedTypeName = `[${assemblyName}]${namespace}.${className}`;
175-
let typeHandle = typeHandleCache[fullyQualifiedTypeName];
176-
if (!typeHandle) {
177-
typeHandle = find_class(findAssembly(assemblyName), namespace, className);
178-
if (!typeHandle) {
179-
throw new Error(`Could not find type "${className}" in namespace "${namespace}" in assembly "${assemblyName}"`);
180-
}
181-
typeHandleCache[fullyQualifiedTypeName] = typeHandle;
182-
}
183-
return typeHandle;
184-
}
185-
186-
function findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle {
187-
const fullyQualifiedMethodName = `[${assemblyName}]${namespace}.${className}::${methodName}`;
188-
let methodHandle = methodHandleCache[fullyQualifiedMethodName];
189-
if (!methodHandle) {
190-
methodHandle = find_method(findType(assemblyName, namespace, className), methodName, -1);
191-
if (!methodHandle) {
192-
throw new Error(`Could not find method "${methodName}" on type "${namespace}.${className}"`);
193-
}
194-
methodHandleCache[fullyQualifiedMethodName] = methodHandle;
195-
}
196-
return methodHandle;
197-
}
198-
199114
function addScriptTagsToDocument() {
200115
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
201116
if (!browserSupportsNativeWebAssembly) {
@@ -254,26 +169,8 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
254169
'number',
255170
'number',
256171
]);
257-
assembly_load = Module.cwrap('mono_wasm_assembly_load', 'number', ['string']);
258-
find_class = Module.cwrap('mono_wasm_assembly_find_class', 'number', [
259-
'number',
260-
'string',
261-
'string',
262-
]);
263-
find_method = Module.cwrap('mono_wasm_assembly_find_method', 'number', [
264-
'number',
265-
'string',
266-
'number',
267-
]);
268-
invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', [
269-
'number',
270-
'number',
271-
'number',
272-
]);
273172

274173
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
275-
mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']);
276-
mono_string_array_new = Module.cwrap('mono_wasm_string_array_new', 'number', ['number']);
277174

278175
MONO.loaded_files = [];
279176

@@ -346,10 +243,16 @@ function getArrayDataPointer<T>(array: System_Array<T>): number {
346243
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
347244
}
348245

246+
function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any {
247+
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
248+
const fqn = `[${assembly}] ${typeName}:${method}`;
249+
return Module.mono_bind_static_method(fqn);
250+
}
251+
349252
function attachInteropInvoker(): void {
350-
const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet');
351-
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
352-
const dotNetDispatcherEndInvokeJSMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'EndInvokeJS');
253+
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet');
254+
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
255+
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS');
353256

354257
DotNet.attachDispatcher({
355258
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
@@ -362,30 +265,25 @@ function attachInteropInvoker(): void {
362265
? dotNetObjectId.toString()
363266
: assemblyName;
364267

365-
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [
366-
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
367-
monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId),
368-
monoPlatform.toDotNetString(methodIdentifier),
369-
monoPlatform.toDotNetString(argsJson),
370-
]);
268+
dotNetDispatcherBeginInvokeMethodHandle(
269+
callId ? callId.toString() : null,
270+
assemblyNameOrDotNetObjectId,
271+
methodIdentifier,
272+
argsJson,
273+
);
371274
},
372275
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
373-
monoPlatform.callMethod(
374-
dotNetDispatcherEndInvokeJSMethodHandle,
375-
null,
376-
[monoPlatform.toDotNetString(serializedArgs)]
276+
dotNetDispatcherEndInvokeJSMethodHandle(
277+
serializedArgs
377278
);
378279
},
379280
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
380-
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
381-
assemblyName ? monoPlatform.toDotNetString(assemblyName) : null,
382-
monoPlatform.toDotNetString(methodIdentifier),
383-
dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null,
384-
monoPlatform.toDotNetString(argsJson),
385-
]) as System_String;
386-
return resultJsonStringPtr
387-
? monoPlatform.toJavaScriptString(resultJsonStringPtr)
388-
: null;
281+
return dotNetDispatcherInvokeMethodHandle(
282+
assemblyName ? assemblyName : null,
283+
methodIdentifier,
284+
dotNetObjectId ? dotNetObjectId.toString() : null,
285+
argsJson,
286+
) as string;
389287
},
390288
});
391289
}

src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ declare namespace Module {
1010
function FS_createPath(parent, path, canRead, canWrite);
1111
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
1212

13-
function mono_call_assembly_entry_point(assemblyName: string, args: any[]): any;
13+
function mono_bind_static_method(fqn: string): BoundStaticMethod;
1414
}
1515

1616
// Emscripten declares these globals
@@ -28,3 +28,7 @@ declare namespace MONO {
2828
var mono_wasm_runtime_is_ready: boolean;
2929
function mono_wasm_setenv (name: string, value: string): void;
3030
}
31+
32+
// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are
33+
// artifically limiting it to a subset of types that we actually use.
34+
declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null);

src/Components/Web.JS/src/Platform/Platform.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@ export interface Platform {
22
start(loadAssemblyUrls: string[]): Promise<void>;
33

44
callEntryPoint(assemblyName: string): void;
5-
findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle;
6-
callMethod(method: MethodHandle, target: System_Object | null, args: (System_Object | null)[]): System_Object;
75

86
toJavaScriptString(dotNetString: System_String): string;
9-
toDotNetString(javaScriptString: string): System_String;
10-
117
toUint8Array(array: System_Array<any>): Uint8Array;
128

139
getArrayLength(array: System_Array<any>): number;

0 commit comments

Comments
 (0)