Blazor to Javascript byte array interop - javascript

I am trying to pass an array of byte from Blazor Client to a javascript function:
private async void ShowImage()
{
SelectedImageBytes = await GetImageData();
if (SelectedImageBytes.Any())
{
ReceivedDataLength = SelectedImageBytes.Length;
//ReceivedDataLength is 131072, which is correct
JS.InvokeVoidAsync("JS.setImage", SelectedImageBytes, 256, 256);
}
StateHasChanged();
}
On Javascript side:
function setImage(data, width, height)
{
console.log("On Javascript I have received an array of " + data.length);
//data.length is 174764
console.log(data);
//...
}
console.log(data) outputs the following:
Which seems to me a base64 string representation of my binary data. According wikipedia the size is incremented approximately by 33% going from byte array to base64 string representation, and this is true for this case: 131072 * 1.33 ~ 174764
My questions then are:
How to pass and receive a byte array from Blazor (C#) to Javascript without converting it to a string
If the previous is not possible, what is the best way to convert the base64 string to byte array on Javascript side.

I gave it another go:
C#
public void CallJsUnMarshalled()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
unmarshalledRuntime.InvokeUnmarshalled<byte[], int>("JsFunctions.MyFunctionUnmarshalled", MyBytes);
}
Javascript:
function MyFunctionUnmarshalled(bytes)
{
const dataPtr = Blazor.platform.getArrayEntryPtr(bytes, 0, 4);
const length = Blazor.platform.getArrayLength(bytes);
var shorts = new Int16Array(Module.HEAPU8.buffer, dataPtr, length);
return 0;
}
InvokeUnmarshalled requires a return it appears, therefore int in the template arguments. Instead of this probably a reference to the object has to be returned to dispose it. I would appreciate if someone can comment on this (will javascript free that memory when the byte array is not used anymore?).
First tests show an improvement of a factor of 50!

When you are using the interop service, the documentation says:
InvokeAsync takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments.
So what you observe is the serialization of your byte array into a base64 string, which is the out-of-the-box behavior. So, you are right. I haven't spotted a way to influence the serialization behavior of the JSInterop service.
The Blazer framework in .NET 5 offers you a way to skip the serialization overhead: IJSUnmarshalledRuntime.
But, in my experiments, it can't handle a byte array or any arrays at all.
To answer your second question, have a look at this discussion.
Convert base64 string to ArrayBuffer

Related

How to parseInt or ParseInt embedded data with TypeScript in Qualtrics?

Even when I save an integer to embedded data earlier in the survey flow (in previous blocks on different screens), I am not able in Javascript to get the embedded data value, ensure it is parsed as a number/integer, then use it in a loop. Is this something about TypeScript? I didn't see anything about parseInt or ParseInt in the TypeScript documentation.
For example, suppose I do the following:
// Draw a random number
var x = Math.floor(Math.random() * 5);
// Save it in embedded data
Qualtrics.SurveyEngine.setEmbeddedData("foo", x);
// In a later block on a different screen, get the embedded data as an integer
var x_new = "${e://Field/foo}"; // not an int
var x_new = parseInt("${e://Field/foo}"); // doesn't work
var x_new = ParseInt("${e://Field/foo}"); // doesn't work
// Loop using x_new:
for(i = 0; i < x_new; i++) {
console.log(i)
}
Any idea why this isn't working? Perhaps I just don't know how to parseint().
In "normal" JS runtime system, we have parseInt function, the function gets a string (like number string) as a parameter. In this env, we don't support your syntax - "${e://Field/foo}", because it is not a "number string".
In Qualtrics system environment they have parseInt too, but they support their custom syntax "${e://Field/foo}" to get EmbeddedData.
Make sure that your code is running on Qualtrics system environment.
ParseInt is just turning your string into an integer.
Look at the demo below.
let myVar = "${e://Field/foo}"; // This is a string
console.log(myVar); // This prints a string
console.log(parseInt(myVar)); // This prints "NaN", i.e. Not a Number, because the string isn't a representation of a number.

How to return an array of unknown size in Enscripten?

I have a C/C++ function that returns two arrays each the size that is unknown before the call. I need to call this function from JavaScript. (For simplicity, one array is returned in the example).
extern "C" {
void produce_object_3d(float* verts, int *num_verts);
}
Note that JavaScript does not know std::vector and boost:array and other types. I currently pre-allocate some space, but it will not work. Here is the code on the JavaScript side:
var verts_address = Module._malloc(FLOAT_SIZE*3*max_verts);
var nv_address = Module._malloc(INT_SIZE*1);
//
produce_object_3d (verts_address, nv_address);
//
var nverts = Module.HEAPU32[nv_address/INT_SIZE];
var verts = Module.HEAPF32.subarray(verts_address/FLOAT_SIZE, verts_address/FLOAT_SIZE + 3*nverts);
This is not efficient. Also what if the size of the result is large and there is not enough memory pre-allocated?
For your particular case using embind is a better option.
As per the official documentation
For convenience, embind provides factory functions to register
std::vector (register_vector()) and std::map (register_map())
types:
EMSCRIPTEN_BINDINGS(stl_wrappers) {
register_vector<int>("VectorInt");
register_map<int,int>("MapIntInt");
}
The returned object has methods like .get() and .size()

To append a byte[] to a string in c#

My javascript code sends blobs of data to a handler in C#. My Javascript code is working fine, and I have already tried to receive the data from client(javascript) and pass them to the C# handler and save them in a local Folder.
Instead of saving the data in a folder, I want to now save it in a string.
My handler each time gets a piece of my information as a byte[].
my Javascript:
xhr = new XMLHttpRequest();
// this is not the complete code
// I slice my file and push them in var blobs = [];
blobs.push(file.slice(start, end));
while (blob = blobs.shift()) {
xhr.send(blob);
count++;
}
My C# handler: In here, the bool ok never gets set to true.
How can I get all my files chunk by chunk as I am sending them from javascript; and, instead of saving in a folder, saving it in a string?
public void ProcessRequest(HttpContext context)
{
try
{
byte[] buffer = new byte[context.Request.ContentLength];
context.Request.InputStream.Read(buffer, 0, context.Request.ContentLength);
string fileSize = context.Request.Headers.Get("X_FILE_SIZE");
bool ok = false;
System.Text.StringBuilder myData = new System.Text.StringBuilder();
myData.Append(buffer);
if(myData.Length == int.Parse(fileSize)){ ok=true; }
}
catch (Exception)
{
throw;
}
}
There is no overload of StringBuilder.Append that takes a byte array, so it will call the StringBuilder.Append(object) method. That will call ToString on the byte array to get a string value, which results in the string "System.Byte[]".
To get the byte array as a string, you need to know what the bytes represent. For example, if the bytes are text that is encoded as UTF-8 then you can use the Encoding.UTF8 class to decode it:
myData.Append(Encoding.UTF8.GetString(buffer));
Note that multi-byte encodings like UTF-8 may represent one character as multiple bytes, so the string length may be different from the byte array length.

Sending an array from a C pointer to a JS function without copying

I'd like to send a data pointer to a JS function at a very high rate (in order to render it on a canvas). What is the best way to do this with Emscripten, without copying the actual data ?
Is the following correct?
void send(void const * data, unsigned length) {
EM_ASM({
var data = new Uint8Array(HEAP8.buffer, $0, $1);
Module.send();
}, data, length);
}
The issue is that it requires an Uint8Array allocation at each frame, which will not make the garbage collector very happy... :(
According to the Emscripten GL implementation, it seems that the best way to achieve what I want is TypedArray#subarray. I wonder if it might affect the garbage collection, tho.
void send(void const * data, unsigned length) {
EM_ASM({
Module.send(HEAPU8.subarray($0, $0 + $1));
}, data, length);
}

Defining byte arrray in javascript

How do I pass a byte array from JavaScript to an ActiveX control.
My JavaScript will call WCF server (method) and that method will return a byte array.
After that I need to pass this byte array to the ActiveX control.
Could anybody provide me a solution for this?
Depending on what binding your WCF service uses (and as you are calling it from javascript I assume webHttpBinding) it is quite possible that the returned byte array will be returned as a base 64 encoded string. So you might need to modify the ActiveX component to accept a base 64 encoded string as parameter instead of a byte array.
From javascript you will have a form of base64_encode string. ActiveX component should have a function to convert string to byte array like this
byte[] string2byte(string s)
{
byte[] b = new byte[s.Length / 2];
for (int i = 0; i < s.Length; i += 2) { b[i / 2] = Convert.ToByte(s.Substring(i, 2), 16); }
return b;
}
I solved the problem by returning base64string rather than a byte array from the WCF Service.
So that i can simply convert the Base64 string usning Convert.FromBase64String() method to byte arrary.

Categories