disclaimer: I am not a c++ developer at all. actually
The problem: I am currently working on a module for azerothcore which will use wasm files to extend functionality, that is, some kind of wasm plugin. Since I am not familiar with c++, i've decided to test posibilities with simple example emulating calls of module. I was able to make an example work in very hardcoded way, copying whole example from wasmtime repo. But now I want to separate variables and re-use them in different method calls. I've decided to save as a class field instance
, store
, and engine
vars. see an example
#include <fstream>
#include <iostream>
#include <sstream>
#include <wasmtime.hh>
#include <Player.h>
#include <span>
using namespace wasmtime;
Span<uint8_t> readWasmFile(const char *filename)
{
FILE *file;
file = fopen(filename, "rb");
if (!file)
{
printf("> Error loading module!\n");
fclose(file);
}
fseek(file, 0L, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0L, SEEK_SET);
printf("File was read...\n");
wasm_byte_vec_t wasm_bytes;
wasm_byte_vec_new_uninitialized(&wasm_bytes, file_size);
if (fread(wasm_bytes.data, file_size, 1, file) != 1)
{
printf("> Error loading module!\n");
}
fclose(file);
std::vector<uint8_t> vec;
Span<uint8_t> raw(reinterpret_cast<uint8_t *>(wasm_bytes.data), wasm_bytes.size);
vec.assign(raw.begin(), raw.end());
wasm_byte_vec_delete(&wasm_bytes);
return vec;
}
class Wasm_Script
{
Engine engine;
Store store;
wasmtime::Instance *instance;
public:
Wasm_Script() : store(engine)
{
auto buf = readWasmFile("wasm_modules/rust_wasm_app.wasm");
auto module = Module::compile(engine, buf).unwrap();
std::cout << "Initializing...\n";
std::cout << "Creating callbacks...\n";
Func host_func = Func::wrap(&store, []()
{ std::cout << "Calling back...\n"; });
Func send_sys_message(
&store, FuncType({ValKind::I32, ValKind::I32}, {}),
[](auto caller, auto params, auto results)
{
// caller.context().gc();
auto ptr = params[0].i32();
auto len = params[1].i32();
std::cout << "PTR = " << ptr << "\n";
Memory mem = std::get<Memory>(*caller.get_export("memory"));
wasmtime::Span<uint8_t> d = mem.data(caller);
std::string msg = "";
for (auto i = ptr; i < ptr + len; i++)
{
msg += d[i];
}
std::cout << msg << std::endl;
return std::monostate();
});
std::cout << "Instantiating module...\n";
Instance instance = Instance::create(store, module, {host_func, send_sys_message}).unwrap();
this->instance = &instance;
}
void OnLogin(Player *player)
{
std::cout << "Extracting export...\n";
auto fn = instance->get(store, "on_player_login");
auto on_player_login = std::get<Func>(*fn);
std::cout << "Calling export...\n";
auto r = on_player_login.call(store, {}).unwrap();
std::cout << "Result from `on_player_login` ";
std::cout << r[0].i32();
std::cout << "\n\n";
std::cout << "Done\n";
}
};
int main()
{
auto player = new Player();
auto script = new Wasm_Script();
script->OnLogin(player);
return 0;
}
if I run it i get a panic form rust lib :)
thread '<unnamed>' panicked at 'object used with the wrong store', crates/wasmtime/src/store/data.rs:258:5
Could you help me to understand why it does not want to work
PS: if I move everything in constructor to OnLogin method - everything works fine.
I believe your code has a use-after free in this->instance = &instance;
since after the method returns the instance
pointer is no longer valid.
I wouldn't agree.
if I add in constructor
this->instance = &instance;
std::cout << this->instance <<"\n";
and then call it in OnLogin
method
std::cout << "Extracting export...\n";
std::cout << instance <<"\n";
I have exactly the same address(pointer)
File was read...
Initializing...
Creating callbacks...
Instantiating module...
0x7ffee90abf98
Extracting export...
0x7ffee90abf98
thread '<unnamed>' panicked at 'object used with the wrong store', crates/wasmtime/src/store/data.rs:258:5
If i do the same with store - i will get the same store address. so, that is why. I am a little bit stuck with it and asked help here.
oh ok, in that case I'm probably not familiar with C++ enough to help
That debug output just shows that you have the same pointer value. But the Instance
that pointer is pointing to has gone out of scope.
@Dan Gohman , you know, I've just was thinking about that pointer is not a proof :) and you just confirmed that the address cold be the same but the value of it could be different.
Then, next question would be how i could "save" the instance
in c++ class to use it in its methods?
I am sorry, it could be a stupid/newbie question, but i cannot find the answer to it :(
PS: actually, i use a pointer for the instnace
because if I use it as wasmtime::Instance instance;
it will tell me that the Instance does not have a default constructor and you much explicitly tell it how to use it.
Would std::shared_ptr
work?
Dan Gohman ha dicho:
Would
std::shared_ptr
work?
let search how to use it :)
As I've told - i am super newbie in c++, always tried to avoid it :) but now is the time
i found another solution, but I am not sure if it is good one.
this->instance = new Instance(Instance::create(store, module, {host_func, send_sys_message}).unwrap());
so, i've used a new Instance to create a pointer to class' field.
For me it looks a little bit dirty, but it works for now.
Just in case someone will face the sam issue or similar here is full cpp
#include <fstream>
#include <iostream>
#include <sstream>
#include <wasmtime.hh>
#include <Player.h>
#include <span>
using namespace wasmtime;
Span<uint8_t> readWasmFile(const char *filename)
{
FILE *file;
file = fopen(filename, "rb");
if (!file)
{
printf("> Error loading module!\n");
fclose(file);
}
fseek(file, 0L, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0L, SEEK_SET);
printf("File was read...\n");
wasm_byte_vec_t wasm_bytes;
wasm_byte_vec_new_uninitialized(&wasm_bytes, file_size);
if (fread(wasm_bytes.data, file_size, 1, file) != 1)
{
printf("> Error loading module!\n");
}
fclose(file);
std::vector<uint8_t> vec;
Span<uint8_t> raw(reinterpret_cast<uint8_t *>(wasm_bytes.data), wasm_bytes.size);
vec.assign(raw.begin(), raw.end());
wasm_byte_vec_delete(&wasm_bytes);
return vec;
}
class Wasm_Script
{
Engine engine;
Store store;
Instance *instance;
public:
Wasm_Script() : store(engine)
{
auto buf = readWasmFile("wasm_modules/rust_wasm_app.wasm");
auto module = Module::compile(engine, buf).unwrap();
std::cout << "Initializing...\n";
std::cout << "Creating callbacks...\n";
Func host_func = Func::wrap(&store, []()
{ std::cout << "Calling back...\n"; });
Func send_sys_message(
store, FuncType({ValKind::I32, ValKind::I32}, {}),
[](auto caller, auto params, auto results)
{
auto ptr = params[0].i32();
auto len = params[1].i32();
std::cout << "PTR = " << ptr << "\n";
Memory mem = std::get<Memory>(*caller.get_export("memory"));
wasmtime::Span<uint8_t> d = mem.data(caller);
std::string msg = "";
for (auto i = ptr; i < ptr + len; i++)
{
msg += d[i];
}
std::cout << msg << std::endl;
return std::monostate();
});
std::cout << "Instantiating module...\n";
instance = new Instance(Instance::create(store, module, {host_func, send_sys_message}).unwrap());
}
void OnLogin(Player *player)
{
std::cout << "Extracting export...\n";
auto fn = instance->get(store, "on_player_login");
auto on_player_login = std::get<Func>(*fn);
std::cout << "Calling export...\n";
auto r = on_player_login.call(store, {}).unwrap();
std::cout << "Result from `on_player_login` ";
std::cout << r[0].i32();
std::cout << "\n\n";
std::cout << "Done\n";
}
};
int main()
{
auto player = new Player();
auto script = new Wasm_Script();
script->OnLogin(player);
return 0;
}
as for me it is not clean, bu tit works and result is
./main
File was read...
Initializing...
Creating callbacks...
Instantiating module...
Extracting export...
Calling export...
PTR = 1114120
This is chat message from WASM
Result from `on_player_login` 1
Thank you for the help
Ok. it looks like that issue of mine we resolved. But i faced new one.
How to pass and retrieve data to/from the store in CPP variant of wasmtime?
There is no much examples to understand it.
Let me describe it more clearly:
/// CALLBACK
Func send_sys_message(
store, FuncType({ValKind::I32, ValKind::I32}, {}),
[](auto caller, auto params, auto results)
{
auto mystore = caller.context().get_data(); // this is how I try to get AppState class instance
std::cout << &mystore << "\n\n";
// ? how I can call a method from mystore.get_session();???
auto ptr = params[0].i32();
auto len = params[1].i32();
Memory mem = std::get<Memory>(*caller.get_export("memory"));
wasmtime::Span<uint8_t> d = mem.data(caller);
std::string msg = "";
for (auto i = ptr; i < ptr + len; i++)
{
msg += d[i];
}
std::cout << msg << std::endl;
return std::monostate();
});
/// actuall call
store.context().set_data(new AppState("My User Session"));
auto fn = instance->get(store, "on_player_login");
auto on_player_login = std::get<Func>(*fn);
auto r = on_player_login.call(store, {}).unwrap();
Or you may provide better way to pass that "session" into callback on a moment of call.
Thank you.
Last updated: Dec 23 2024 at 12:05 UTC