Calling JS object member functions from Wasm

The pong tutorial (https://labs.leaningtech.com/cheerp/tutorials/pong) shows how to handle interactions between JS and Wasm using static functions and global instances. What is the recommended approach for accessing member functions of dynamically created JS objects from Wasm? Example code:
struct [[cheerp::genericjs]] Foo {
client::HTMLElement* span;
Foo() {
span = client::document.createElement("span");
span->set_textContent("Foo");
client::document.get_body()->appendChild(span);
}
void makeBar() {
span->set_textContent("Bar");
}
}

struct [[cheerp::wasm]] Bar {
Foo* foo; // obviously does not compile
Bar() {
foo = new Foo();
foo->makeBar();
}
}

[[cheerp::wasm]]
void webMain() {
Bar* bar = new Bar();
}
struct [[cheerp::genericjs]] Foo {
client::HTMLElement* span;
Foo() {
span = client::document.createElement("span");
span->set_textContent("Foo");
client::document.get_body()->appendChild(span);
}
void makeBar() {
span->set_textContent("Bar");
}
}

struct [[cheerp::wasm]] Bar {
Foo* foo; // obviously does not compile
Bar() {
foo = new Foo();
foo->makeBar();
}
}

[[cheerp::wasm]]
void webMain() {
Bar* bar = new Bar();
}
Leaning Technologies Developer
Pong with Cheerp - Cheerp Documentation
C/C++ compiler targeting WebAssembly and JavaScript.
11 Replies
apignotti
apignotti8mo ago
WebAssembly provides very little access to JavaScript object. This applies to the standard itself, not to the Cheerp compiler. The most common solution is to use indexes into a global [[cheerp::genericjs]] array or table, then use a global or static [[cheerp::genericjs]] method to invoke the method from the array The indexes can be indirectly stored as integers inside any [[cheerp::wasm]] data structure This might sound convoluted, but it's what WebAssembly structurally can allow Some amount of interoperability can be achieved with WasmGC, and we have plans to support than in the short/medium term, but it will come with other side effects, so it's no silver bullet
Pedro
PedroOP8mo ago
Thank you! I will try it
Pedro
PedroOP8mo ago
Also, earlier I found this article by @Yuri (https://medium.com/leaningtech/adding-anyref-support-in-a-c-to-webassembly-compiler-2bba3fac707f). Enabling externref made it possible to bring Foo entirely to the Wasm world with a couple of changes to that example code. The generated JS+Wasm seems not to be working, though. Maybe I forgot something? Even then, it seems like this feature could be helpful in some use cases. Is it still being supported/developed?
Medium
Yuri
Yuri8mo ago
@Pedro could you post your code? The feature is supported, although a bit limited at the moment. We plan to expand it in the future, also in connection with WasmGC support.
apignotti
apignotti8mo ago
@Pedro Anyref allows to pass javascript objects as arguments and use them as return types. There is no support for storing them in a linear memory structure, which is what your code tries to achieve. WebAssembly linear memory is just a large array of bytes, and externref is something that does not have a user-visible representation in bytes.
Pedro
PedroOP8mo ago
Pedro
PedroOP8mo ago
main.cpp:
#include <cheerp/client.h>
#include <cheerp/clientlib.h>

struct [[cheerp::wasm]] Foo {
Foo() {
client::HTMLElement* span;
span = client::document.createElement("span");
span->set_textContent("Foo");
client::document.get_body()->appendChild(span);
}
void makeBar() {
client::HTMLElement* span;
span = (client::HTMLElement*) client::document.querySelector("span");
span->set_textContent("Bar");
}
};

struct [[cheerp::wasm]] Bar {
Foo* foo; // now it does compile
Bar() {
Foo* foo = new Foo();
foo->makeBar();
}
};

[[cheerp::wasm]]
void webMain() {
Bar* bar = new Bar();
}
#include <cheerp/client.h>
#include <cheerp/clientlib.h>

struct [[cheerp::wasm]] Foo {
Foo() {
client::HTMLElement* span;
span = client::document.createElement("span");
span->set_textContent("Foo");
client::document.get_body()->appendChild(span);
}
void makeBar() {
client::HTMLElement* span;
span = (client::HTMLElement*) client::document.querySelector("span");
span->set_textContent("Bar");
}
};

struct [[cheerp::wasm]] Bar {
Foo* foo; // now it does compile
Bar() {
Foo* foo = new Foo();
foo->makeBar();
}
};

[[cheerp::wasm]]
void webMain() {
Bar* bar = new Bar();
}
Makefile:
all: main.wasm

main.wasm: main.cpp
/opt/cheerp/bin/clang++ -target cheerp-wasm main.cpp -o main.js -cheerp-wasm-externref -cheerp-pretty-code

serve:
python3 -m http.server 8000

open:
firefox localhost:8000

clean:
rm main.js main.wasm

.PHONY: all, serve, open, clean
all: main.wasm

main.wasm: main.cpp
/opt/cheerp/bin/clang++ -target cheerp-wasm main.cpp -o main.js -cheerp-wasm-externref -cheerp-pretty-code

serve:
python3 -m http.server 8000

open:
firefox localhost:8000

clean:
rm main.js main.wasm

.PHONY: all, serve, open, clean
Oh, I see, thank you for the explanation! Yuri did mention it in his article. Also, I indeed had to make some changes to the code to make it work, more specifically, now I get a reference to the span element every time I want to access it.
Pedro
PedroOP8mo ago
It still doesn't seem to work, though, despite compiling. I reckon I may have done something wrong. The browser complains of an invalid character in the string that is passed to document.createElement in the generated JS.
No description
Yuri
Yuri8mo ago
I think that there is a compiler bug happening here. We need to investigate this further. As a general advice, to solve the issue of storing js data and use it from wasm: instead of trying to convert everything to wasm, try to have the "top-level" of your code in js, and call into a wasm function with the state it needs as parameters. You cannot store pointers to js memory in a [[cheerp::wasm]] struct, but you CAN do the opposite.
DutChen18
DutChen188mo ago
Hi @Pedro, we have fixed several bugs related to externref in the latest nightly build of cheerp. Please try again with the new build and let us know if you encounter any more issues.
Pedro
PedroOP7mo ago
Hello, thank you! Will try it
Want results from more Discord servers?
Add your server