v8pp
v8pp copied to clipboard
Support std::function as function parameter
Hello,
Seems this is not supported currently, but it's quite handy to have (for example callbacks). I tried implementing it, but I'm very new to both v8 and v8pp. Also not that good with template programming either. But here is something I got working, and maybe can be used to build somewhat better implementation.
template<typename T, typename ...Args>
struct V8CallFunc
{
V8CallFunc(v8::Isolate* isolate, v8::Local<v8::Function> func)
: isolate(isolate), func(isolate, func)
{
}
T operator()(Args&&... args) const
{
v8::EscapableHandleScope scope(isolate);
int const arg_count = sizeof...(Args);
// +1 to allocate array for arg_count == 0
v8::Local<v8::Value> v8_args[arg_count + 1] =
{
to_v8(isolate, std::forward<Args>(args))...
};
v8::TryCatch try_catch(isolate);
v8::Local<v8::Value> result;
v8::Local<v8::Function> funcLocal = func.Get(isolate);
bool const not_empty = funcLocal->Call(isolate->GetCurrentContext(), funcLocal, arg_count, v8_args).ToLocal(&result);
if (try_catch.HasCaught())
{
std::string const msg = v8pp::from_v8<std::string>(isolate,
try_catch.Exception()->ToString(isolate->GetCurrentContext()).ToLocalChecked());
THROW(std::runtime_error, msg);
}
assert(not_empty);
return from_v8<T>(isolate, scope.Escape(result));
}
v8::Isolate* isolate;
v8::Eternal<v8::Function> func;
};
template<typename ...Args>
struct V8CallFunc<void, Args...>
{
V8CallFunc(v8::Isolate* isolate, v8::Local<v8::Function> func)
: isolate(isolate), func(isolate, func)
{
}
void operator()(Args&&... args) const
{
v8::EscapableHandleScope scope(isolate);
int const arg_count = sizeof...(Args);
// +1 to allocate array for arg_count == 0
v8::Local<v8::Value> v8_args[arg_count + 1] =
{
to_v8(isolate, std::forward<Args>(args))...
};
v8::TryCatch try_catch(isolate);
v8::Local<v8::Function> funcLocal = func.Get(isolate);
funcLocal->Call(isolate->GetCurrentContext(), funcLocal, arg_count, v8_args);
if (try_catch.HasCaught())
{
std::string const msg = v8pp::from_v8<std::string>(isolate,
try_catch.Exception()->ToString(isolate->GetCurrentContext()).ToLocalChecked());
THROW(std::runtime_error, msg);
}
}
v8::Isolate* isolate;
v8::Eternal<v8::Function> func;
};
template <typename Ret, typename... Args> __forceinline
std::function<Ret(Args...)> createV8CallFunc(std::function<Ret(Args...)> f, v8::Isolate* isolate, v8::Local<v8::Function> func)
{
return V8CallFunc<Ret, Args...>(isolate, func);
}
template<typename T>
struct convert<std::function<T>>
{
using from_type = std::function<T>;
using to_type = v8::Local<v8::Function>;
static bool is_valid(v8::Isolate* isolate, v8::Local<v8::Value> value)
{
return !value.IsEmpty() && value->IsFunction();
}
static from_type from_v8(v8::Isolate* isolate, v8::Local<v8::Value> value)
{
if (!is_valid(isolate, value))
{
THROW(invalid_argument, isolate, value, "Function");
}
auto func = value.As<v8::Function>();
return createV8CallFunc(from_type(), isolate, func);
}
static to_type to_v8(v8::Isolate* isolate, const from_type& func)
{
using T_type = typename std::decay<from_type>::type;
T_type func_copy = func;
v8::Local<v8::Function> fn = v8::Function::New(isolate->GetCurrentContext(),
&detail::forward_function<raw_ptr_traits, T_type>,
detail::set_external_data(isolate, std::forward<T_type>(func_copy))).ToLocalChecked();
return fn;
}
};
Hi @ivalylo
there is a wrap_function() helper in v8pp/function.hpp that allows to wrap any callable C++ object (pointer to free function, lambda, maybe also std::function) to a V8 Function value. So you could try to use the wrapped function as a callback argument.
Btw, there is also a call_v8() helper in v8pp/call_v8.hpp that seems to be very similar to your V8CallFunc::operator().
It seems that proper support for std::function overload for v8pp::convert require including full content of call_from_v8.hpp and call_v8.hpp. It can be easily done from the technical point of view, but I have no idea about the architectural point of view.
I made my ugly version using forward declaration, but it doesn't work if used doesn't include call_v8.hpp or call_from_v8.hpp or both while using std::function bindings. And there are not so clear messages "undefined reference to v8::Local<v8::Value> v8pp::call_v8<double>()"
What do you think, @pmed?
Hi @YarikTH
do you mean PR #169? Thanks, it looks viable, maybe with a test case added.
I hope call_v8() can be used somehow inside convert<std::function<>> specialization. Anyways call_v8() i just a helper for v8::Function::Call(), that we can use in the specialization.
Yes. But I gave up using V8 a long time ago so I can't collaborate more.