[EDIT] Updated this question with actual working code.

(Note: This discussion talks about the way that the array interop works, which might be relevant.)

I'm trying to transfer an array of shorts to TypeScript from Blazor using the Blazor Byte-array interop optimisation.

I have the following C# method that I invoke from JavaScript:

[JSInvokable]
public byte[] ReadDataBytes()
{
    var shortArray = new short[1000];  // In real use, this comes from some data source.
    var byteArray = new byte[shortArray.Length * sizeof(short)];
    Buffer.BlockCopy(shortArray, 0, byteArray, 0, byteArray.Length);
    return byteArray;
}

On the TypeScript side I call that method like so:

async readData(): Promise<Int16Array>
{
    const byteArray = await this._dotNetObject.invokeMethodAsync<Uint8Array>("ReadDataBytes");

    console.log("byteOffset = ", byteArray.byteOffset);

    // Check if the byteArray is properly aligned for Int16Array
    if (byteArray.byteOffset % 2 !== 0) 
    {
        // If not aligned, create a new properly aligned buffer
        const alignedBuffer = new ArrayBuffer(byteArray.byteLength);
        const alignedView = new Uint8Array(alignedBuffer);
        alignedView.set(byteArray);
        return new Int16Array(alignedBuffer);
    }

    // Convert byte array directly to Int16Array (little-endian) if properly aligned
    return new Int16Array(byteArray.buffer, byteArray.byteOffset, byteArray.byteLength / 2);
}

_dotNetObject is a DotNet.DotNetObject:

export declare namespace DotNet {
    interface DotNetObject {
        invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T>;
        invokeMethod<T>(methodIdentifier: string, ...args: any[]): T;
        dispose(): void;
    }
}

This works, but in my tests byteArray.byteOffset starts off at 29 for a few calls, then becomes 30 for some more calls and finally becomes 31 for all remaining calls. For example, I see the following output to the browser console:

7 byteOffset =  29
7 byteOffset =  30
119 byteOffset =  31

If I remove the code that deals with a byteOffset which isn't a multiple of 2:

async readData(): Promise<Int16Array>
{
    const byteArray = await this._dotNetObject.invokeMethodAsync<Uint8Array>("ReadDataBytes");

    console.log("byteOffset = ", byteArray.byteOffset);

    // Convert byte array directly to Int16Array (little-endian)
    return new Int16Array(byteArray.buffer, byteArray.byteOffset, byteArray.byteLength / 2);
}

Then I get the error: RangeError: start offset of Int16Array should be a multiple of 2.

Is there any way to configure things so that the data is correctly aligned to a multiple of sizeof(short) in ReadDataBytes() so that I don't need to copy the data to align it in readData()?

5 Replies 5

You shouldn't need to do any alignment checks and padding at all, as with proper conversion converting short[] to a byte[] will always return an array where the length is a multiple of 2.

Have you tried logging/ checking whether the byte[] in your C# side is the correct length and the elements are correctly converted?


This is of course assuming that you're example here is a contrived one and that there is

  1. A reason to pass a short array to C#, to convert it to a byte array and immediately convert it back into a Int16Array (i.e. a shot array)
  2. Your code actually works and passes the required arguments around, and the fact that your example code here doesn't do that is because of the contrived nature of it and this isn't your actual code

I know next to nothing about Typescript, but are you not supposed to give an argument when calling invokeMethodAsync<Uint8Array>("ReadDataBytes")? Where is the data actually coming from? It also make little sense to me that byteOffset has a non zero value at all. If byteOffset represents the index where the actual data starts, what are the bytes before this? Have you tested that the values in the array are converted correctly?

@JonasH The data is all converted correctly. I had a typo in my sample code (I converted it from a more complicated example) and the ReadDataBytes() method doesn't actually have a parameter.

Like you, I don't know why byteOffset has a non-zero value; it seems to be related to how Blazor is buffering things.

@Mindswipe The alignment problem occurs in the TypeScript. The reason the short array is being converted to a byte array is to leverage the Blazor interop optimisation that I linked from my question. If you transfer a short array, Blazor 64-bit encodes the array before using SignalR to transfer it to the browser, and then converts it back in the TypeScript interop, which introduces a large overhead.

I'm not sure about typescript, sounds like your issue is there because short will always be 2 bytes in length. The method you are using will perform fast but will have a larger memory footprint. You could use a for loop(using BitConverter to get each byte) which will take a little longer(about 2x nano seconds) but memory footprint reports half.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.