Skip to content

Byte Array Interop in WASM #33104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/Components/Web.JS/src/Boot.WebAssembly.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
/* eslint-disable array-element-newline */
import { DotNet } from '@microsoft/dotnet-js-interop';
import { Blazor } from './GlobalExports';
import * as Environment from './Environment';
import { monoPlatform } from './Platform/Mono/MonoPlatform';
import { byteArrayBeingTransferred, monoPlatform } from './Platform/Mono/MonoPlatform';
import { renderBatch, getRendererer, attachRootComponentToElement, attachRootComponentToLogicalElement } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { shouldAutoStart } from './BootCommon';
import { setEventDispatcher } from './Rendering/Events/EventDispatcher';
import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader';
import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
import { BootConfigResult } from './Platform/BootConfig';
import { Pointer, System_Boolean, System_String } from './Platform/Platform';
import { Pointer, System_Array, System_Boolean, System_Byte, System_Int, System_Object, System_String } from './Platform/Platform';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
import { discoverComponents, discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
Expand Down Expand Up @@ -45,6 +46,8 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
// Configure JS interop
Blazor._internal.invokeJSFromDotNet = invokeJSFromDotNet;
Blazor._internal.endInvokeDotNetFromJS = endInvokeDotNetFromJS;
Blazor._internal.receiveByteArray = receiveByteArray;
Blazor._internal.retrieveByteArray = retrieveByteArray;

// Configure environment for execution under Mono WebAssembly with shared-memory rendering
const platform = Environment.setPlatform(monoPlatform);
Expand Down Expand Up @@ -161,6 +164,21 @@ function endInvokeDotNetFromJS(callId: System_String, success: System_Boolean, r
DotNet.jsCallDispatcher.endInvokeDotNetFromJS(callIdString, successBool, resultJsonOrErrorMessageString);
}

function receiveByteArray(id: System_Int, data: System_Array<System_Byte>): void {
const idLong = id as any as number;
const dataByteArray = monoPlatform.toUint8Array(data);
DotNet.jsCallDispatcher.receiveByteArray(idLong, dataByteArray);
}

function retrieveByteArray(): System_Object {
if (byteArrayBeingTransferred === null) {
throw new Error('Byte array not available for transfer');
}

const typedArray = BINDING.js_typed_array_to_array(byteArrayBeingTransferred);
return typedArray;
}

Blazor.start = boot;
if (shouldAutoStart()) {
boot().catch(error => {
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Web.JS/src/Boot.WebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { shouldAutoStart } from './BootCommon';
import { internalFunctions as navigationManagerFunctions } from './Services/NavigationManager';
import { setEventDispatcher } from './Rendering/Events/EventDispatcher';
import { startIpcReceiver } from './Platform/WebView/WebViewIpcReceiver';
import { sendBrowserEvent, sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendSupplyByteArray, sendLocationChanged } from './Platform/WebView/WebViewIpcSender';
import { sendBrowserEvent, sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendByteArray, sendLocationChanged } from './Platform/WebView/WebViewIpcSender';
import { InputFile } from './InputFile';

let started = false;
Expand All @@ -20,7 +20,7 @@ async function boot(): Promise<void> {
DotNet.attachDispatcher({
beginInvokeDotNetFromJS: sendBeginInvokeDotNetFromJS,
endInvokeJSFromDotNet: sendEndInvokeJSFromDotNet,
supplyByteArray: sendSupplyByteArray,
sendByteArray: sendByteArray,
});

Blazor._internal.InputFile = InputFile;
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Web.JS/src/GlobalExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { InputFile } from './InputFile';
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
import { CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { Platform, Pointer, System_String, System_Array, System_Object, System_Boolean } from './Platform/Platform';
import { Platform, Pointer, System_String, System_Array, System_Object, System_Boolean, System_Byte, System_Int } from './Platform/Platform';

interface IBlazor {
navigateTo: (uri: string, forceLoad: boolean, replace: boolean) => void;
Expand All @@ -27,6 +27,8 @@ interface IBlazor {
InputFile?: typeof InputFile,
invokeJSFromDotNet?: (callInfo: Pointer, arg0: any, arg1: any, arg2: any) => any;
endInvokeDotNetFromJS?: (callId: System_String, success: System_Boolean, resultJsonOrErrorMessage: System_String) => void;
receiveByteArray?: (id: System_Int, data: System_Array<System_Byte>) => void;
retrieveByteArray?: () => System_Object;
getPersistedState?: () => System_String;
attachRootComponentToElement?: (arg0: any, arg1: any, arg2: any) => void;
registeredComponents?: {
Expand Down
9 changes: 6 additions & 3 deletions src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,11 +481,13 @@ function bindStaticMethod(assembly: string, typeName: string, method: string) {
return BINDING.bind_static_method(fqn);
}

export let byteArrayBeingTransferred: Uint8Array | null = null;
function attachInteropInvoker(): void {
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'InvokeDotNet');
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'BeginInvokeDotNet');
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'EndInvokeJS');
const dotNetDispatcherSupplyByteArrayMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'SupplyByteArray');
const dotNetDispatcherNotifyByteArrayAvailableMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'NotifyByteArrayAvailable');


DotNet.attachDispatcher({
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
Expand All @@ -509,8 +511,9 @@ function attachInteropInvoker(): void {
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
dotNetDispatcherEndInvokeJSMethodHandle(serializedArgs);
},
supplyByteArray: (id: number, data: Uint8Array): void => {
dotNetDispatcherSupplyByteArrayMethodHandle(id, data);
sendByteArray: (id: number, data: Uint8Array): void => {
byteArrayBeingTransferred = data;
dotNetDispatcherNotifyByteArrayAvailableMethodHandle(id);
},
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
assertHeapIsNotLocked();
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Web.JS/src/Platform/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface HeapLock {
export interface MethodHandle { MethodHandle__DO_NOT_IMPLEMENT: any }
export interface System_Object { System_Object__DO_NOT_IMPLEMENT: any }
export interface System_Boolean { System_Boolean__DO_NOT_IMPLEMENT: any }
export interface System_Byte { System_Byte__DO_NOT_IMPLEMENT: any }
export interface System_Int { System_Int__DO_NOT_IMPLEMENT: any }
export interface System_String extends System_Object { System_String__DO_NOT_IMPLEMENT: any }
export interface System_Array<T> extends System_Object { System_Array__DO_NOT_IMPLEMENT: any }
export interface Pointer { Pointer__DO_NOT_IMPLEMENT: any }
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export function startIpcReceiver() {

'EndInvokeDotNet': DotNet.jsCallDispatcher.endInvokeDotNetFromJS,

'ReceiveByteArray': DotNet.jsCallDispatcher.receiveByteArray,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebView still needs to be appropriately tested, this is just a stopgap right now.


'Navigate': navigationManagerFunctions.navigateTo,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export function sendEndInvokeJSFromDotNet(asyncHandle: number, succeeded: boolea
send('EndInvokeJS', asyncHandle, succeeded, argsJson);
}

export function sendSupplyByteArray(id: number, data: Uint8Array) {
send('SupplyByteArray', id, data);
export function sendByteArray(id: number, data: Uint8Array) {
send('ReceiveByteArrayFromJS', id, data);
}

export function sendLocationChanged(uri: string, intercepted: boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeUnmarshalled<T0, T1,
Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeUnmarshalled<T0, TResult>(string! identifier, T0 arg0) -> TResult
Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeUnmarshalled<TResult>(string! identifier) -> TResult
override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeJS(string! identifier, string? argsJson, Microsoft.JSInterop.JSCallResultType resultType, long targetInstanceId) -> string!
override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.SendByteArray(int id, byte[]! data) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.JSInterop.Infrastructure;
using WebAssembly.JSInterop;
Expand Down Expand Up @@ -68,6 +70,12 @@ protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNet
callInfo.CallId, dispatchResult.Success, resultJsonOrErrorMessage);
}

/// <inheritdoc />
protected override void SendByteArray(int id, byte[] data)
{
InvokeUnmarshalled<int, byte[], object>("Blazor._internal.receiveByteArray", id, data);
}

internal TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2, long targetInstanceId)
{
var resultType = JSCallResultTypeHelper.FromGeneric<TResult>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services
{
internal sealed class DefaultWebAssemblyJSRuntime : WebAssemblyJSRuntime
{
internal static readonly DefaultWebAssemblyJSRuntime Instance = new DefaultWebAssemblyJSRuntime();
internal static readonly DefaultWebAssemblyJSRuntime Instance = new();

public ElementReferenceContext ElementReferenceContext { get; }

[DynamicDependency(nameof(InvokeDotNet))]
[DynamicDependency(nameof(EndInvokeJS))]
[DynamicDependency(nameof(BeginInvokeDotNet))]
[DynamicDependency(nameof(NotifyByteArrayAvailable))]
private DefaultWebAssemblyJSRuntime()
{
ElementReferenceContext = new WebElementReferenceContext(this);
Expand Down Expand Up @@ -72,5 +73,21 @@ public static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetO
DotNetDispatcher.BeginInvokeDotNet(Instance, state.callInfo, state.argsJson);
});
}

/// <summary>
/// Invoked via Mono's JS interop mechanism (invoke_method)
///
/// Notifies .NET of an array that's available for transfer from JS to .NET
///
/// Ideally that byte array would be transferred directly as a parameter on this
/// call, however that's not currently possible due to: <INSERT_BUG_HERE></INSERT_BUG_HERE>
/// </summary>
/// <param name="id">Id of the byte array</param>
public static void NotifyByteArrayAvailable(int id)
{
var data = Instance.InvokeUnmarshalled<byte[]>("Blazor._internal.retrieveByteArray");

DotNetDispatcher.ReceiveByteArray(Instance, id, data);
}
}
}