-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Improve the JavaScript interop code generators to support additional scenarios #78455
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
I hope @pavelsavara won't mind me pinging. |
Tagging subscribers to 'arch-wasm': @lewing Issue DetailsIs there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.I'm trying to invoke a .NET method via JS interop that passes an array of JavaScript objects back. [JSImport("myFunction", "myModule")]
public static partial void MyFunction(Action<JSObject[]> callback); The above declaration gives a compilation error from the generator specifically for the I tried variations of The related documentation page seems to imply a callback that supplies a JavaScript object array should be supported (the below images are edited for clarity): The object on the JavaScript side is not straightforward or viable to manually serialize (it contains DOM objects and circular references). I can transform the object on the JavaScript side, but doing so would still result in an array of objects, which are not marshalled as per the issue described above. Describe the solution you'd likeSeeing as how arrays of objects are already supported (as parameters and return values), this is possibly just a missed use case which should be supported. Currently the workaround is to manually call back from JavaScript to .NET without passing the callback directly to the generated interop method, which is insufficient for code organization. The problem in itself obviously exists because there is no straightforward way to interact with the DOM without custom user code. An alternative (arguably better) solution would be if there was a way to directly interact with the DOM from Additional context
|
Thanks for the feature request. Answering the questions:
|
Thanks for the response and the answers! I see very much potential in the new API, seeing as how I personally saw orders of magnitudes of performance wins in some cases. Getting to and back from the DOM is cumbersome, I hope it will become easier with future improvements. Some reactions to the answers:
|
It maps 1:1 to generic parameters on C# side.
I don't know yet. Maybe different GC semantics, maybe different type produced on JS side, maybe different implementation of the wrappers.
Technically yes, we have all the type information from the C# signature, if there is no alternative marshaling for it. |
Hello @yugabe, |
Hi @hoangdovan, I haven't taken a deeper look at it, but generating the API by using You can always do it by not directly passing a callback, but actually calling a different .NET-published
|
Hi @yugabe , maybe I will try to request for add new feature to support more for this API. Thank you for your information! |
Following on from this, what's the recommended approach to pass and use arrays in C# from JavaScript? As mentioned above, huge overhead on marshalling performance using JSON. We're also considering MemoryView, but have just started looking at that. Ideal would be the ability to use the JSObject directly. We're trying to pass and use a multi-dimensional to then create a Dictionary equivalent. |
It really depends on arrays of what. There is table with supported combinations here
There is request to add convinience wrapper for that, but it would have roughly the same perf as what you could do already with string marshaling. #77784
There is another proposal for that #78905 Some of it is already possible with the current
Passing array of keys and array of values and then recombining them on the other side worked well for me. |
Thanks @pavelsavara, we'll try that. Assume overhead of marshalling arrays of strings due to lack of support in WebAssembly? |
My comment about perf is to differentiate costs of interop calls vs usual C# to C# calls expectations. Unless you do very large colections or run interop calls in a tight loop, the perf is usually OK. Even if WASM had native string, we would need .NET string data structure in wasm memory. (In context of this issue, I speak about JSImport interop, not default Blazor interop which could also have network transport costs on top of it in hybrid mode) |
@pavelsavara Thanks for your support so far. The other thing we're considering is a MemoryView, are there any available examples of usage? I appreciate that this is work in progress. The primary NFR for us is speed. Once again, thanks for any advice offered. |
For In that case For examples, please see GetResponseBytes. It's called from async method here but the call itself is synchronous. And the implementation of http_wasm_get_response_bytes is here. And finally you could pin the memory yourself and marshal It's complex code, so I'm sure this is not great tutorials. Sorry. |
Hey, thanks for any advice and help offered, all appreciated. Understand with nascent pieces this happens, its part of new tech. |
Maybe there should be a "partially" supported indicator for Arrays of JSObjects in the documentation's supported types matrix with some footnotes on it not being supported in promise returns or action callback parameters? And/or clarify that it is only supported via representation as a single JSObject for the array, and thus the function signatures/marshaling cannot specify an array in that case. |
My workaround for an action wanting to receive an array of JSObject's. Unfortunately requires an extra interop call to unpack array references [JSImport(baseJSNamespace + ".BindListener")]
public static partial JSObject BindListener(JSObject jqObject, string events,
// action where second function param will be an ArrayObject reference
[JSMarshalAs<JSType.Function<JSType.String, JSType.Object>>] Action<string, JSObject> handler);
// Used for unpacking an ArrayObject into a JSObject[] array
[JSImport(baseJSNamespace + ".GetArrayObjectItems")]
[return: JSMarshalAs<JSType.Array<JSType.Object>>]
public static partial JSObject[] GetArrayObjectItems(JSObject jqObject); Javascript (I probably didn't need ArrayObject class, but makes the pattern clearer) JQueryProxy.BindListener = function (jsObject, events, action)
{
let handler = function (e) {
var someArray = [jQuery('div'),jQuery('#x')];
// pack array into single reference, and call action passing the array
action("blah", new ArrayObject(someArray));
}.bind(jsObject);
jsObject.on(events, handler);
return handler; // return reference to the handler so it can be passed later for .off
}
class ArrayObject {
constructor(items) {
this.items = items;
}
} The action listener that will receive the ArrayObject and unpack it into an actual array // action that wants to receive an array as a param
Action<string, JSObject> interopListener =
(eventEncoded, arrayObject) => {
// unpack the single ArrayObject JSObject reference into its individual elements
JSObject[] unpackedArray = HelpersProxy.GetArrayObjectItems(arrayObject);
// ...
};
JQueryProxy.BindListener(this.jsObject, events, interopListener); |
Supporting JSType.Function with more than 4 parameters is also needed for callbacks. Without that we have to pass a JSObject as a single param and call nested JS interop to fetch each property independently. Which will eventually be unsupported in .NET 9 WT as per #85592 (comment). |
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
I'm trying to invoke a .NET method via JS interop that passes an array of JavaScript objects back.
The above declaration gives a compilation error from the generator specifically for the
callback
symbol:SYSLIB1072: The type 'System.Action<System.Runtime.InteropServices.JavaScript.JSObject[]>' is not supported by source-generated JavaScript interop. The generated source will not handle marshalling of parameter 'callback'.
I tried variations of
[JSMarshalAs<T>]
attributes on the parameter, but the error seems to indicate this type is not supported by design (other parameters, such asAction<JSObject>
ask for the parameter to be annotated).The related documentation page seems to imply a callback that supplies a JavaScript object array should be supported (the below images are edited for clarity):
The object on the JavaScript side is not straightforward or viable to manually serialize (it contains DOM objects and circular references). I can transform the object on the JavaScript side, but doing so would still result in an array of objects, which are not marshalled as per the issue described above.
Describe the solution you'd like
Seeing as how arrays of objects are already supported (as parameters and return values), this is possibly just a missed use case which should be supported. Currently the workaround is to manually call back from JavaScript to .NET without passing the callback directly to the generated interop method, which is insufficient for code organization.
The problem in itself obviously exists because there is no straightforward way to interact with the DOM without custom user code. An alternative (arguably better) solution would be if there was a way to directly interact with the DOM from
browser-wasm
.NET apps (like Blazor). The Blazor-included JavaScript interop supports specialElementReference
objects that can hold reference to DOM objects either instantiated in Blazor or outside of it, which makes this problem a bit easier to solve with the (slower) alternative interop solution. As the current implementation can use proxy objects, it might be worth considering using proxy objects that support more scenarios (like calling methods on them directly), and JavaScript event handling.Additional context
I haven't tried if marshalling as
JSType.Function<JSType.Any>
would work if I usedAction<object>
as a parameter.It's also a bit confusing to annotate
Action
andFunc
types with theJSType.Function
parameters, because I'm not sure if the type parameters should always match (especially when a function returns JavaScriptvoid
-JSType.Void
), but this is most probably a documentation issue instead.It would also be worth clarifying why some parameters need to be explicitly annotated, like
Action
.The text was updated successfully, but these errors were encountered: