-
Notifications
You must be signed in to change notification settings - Fork 122
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
Return std::shared_ptr of another wrapped class from a member function #139
Comments
it seems like there's already a PR #134 that solves the exact problem i've been struggling with I tried to incorporate that code by deriving from Am i missing something obvious here? |
Hi Dmitriy,
thanks for the sharing!
I actually don't remember why that PR is still hasn't been merged. Despite
the auto wrapping feature is there.
I need some time to recall the details. Unfortunately I can't promise
anything because I don't have much spare time for the project right now.
Best regards
Pavel
чт, 9 июл. 2020 г., 13:44 Dmitriy Luchikov <notifications@github.com>:
… it seems like there's already a PR #134
<#134> that solves the exact problem
i've been struggling with
I tried to incorporate that code by deriving fromv8pp::class_ and ptr
traits structs, that didn't go well.
Apparently v8pp::class_ wasn't designed as something to inherit from,
since it has no virtual methods, frequently returns *this by reference
and uses 'private' instead of 'protected'.
Am i missing something obvious here?
About 7 months has passed since that PR was created.
As there are no comments there, i must assume that the issue PR solves is
not an issue at all and v8pp already provides a way to elegantly handle
such cases.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#139 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAISAUX4KZKGQZPJJICFOZDR2WURHANCNFSM4OU5RTJA>
.
|
After playing around with debugger for a bit i realized that there's indeed a much easier way to automatically wrap shared_ptr instances. All one need to do is to provide a specialization for a Auto-wrapping would still be nice though. // wrapper.hpp
#define CREATE_V8PP_SHARED_CONVERTER(klass) \
namespace v8pp { \
template<> \
struct convert<std::shared_ptr<##klass>, typename std::enable_if<is_wrapped_class<##klass>::value>::type> \
{ \
using from_type = std::shared_ptr<##klass>; \
using to_type = v8::Local<v8::Object>; \
using class_type = typename std::remove_cv<##klass>::type; \
static bool is_valid(v8::Isolate*, v8::Local<v8::Value> value) \
{ \
return !value.IsEmpty() && value->IsObject(); \
} \
static from_type from_v8(v8::Isolate* isolate, v8::Local<v8::Value> value) \
{ \
if (!is_valid(isolate, value)) \
{ \
return nullptr; \
} \
return class_<class_type, shared_ptr_traits>::unwrap_object(isolate, value); \
} \
static to_type to_v8(v8::Isolate* isolate, std::shared_ptr<##klass> const& value) \
{ \
auto& class_info = detail::classes::find<shared_ptr_traits>(isolate, detail::type_id<class_type>()); \
auto wrapped_obj = class_<class_type, shared_ptr_traits>::find_object(isolate, value); \
if (wrapped_obj.IsEmpty() && class_info.auto_wrap_objects()) { \
wrapped_obj = class_<class_type, shared_ptr_traits>::reference_external(isolate, value); \
} \
return wrapped_obj; \
} \
}; \
} // bindings.cpp
...
CREATE_V8PP_SHARED_CONVERTER(A);
CREATE_V8PP_SHARED_CONVERTER(B);
CREATE_V8PP_SHARED_CONVERTER(C); |
I'm also affected by this bug. I want to create a DOM-like structure in which the root is created by C++ and then JS can append nodes to the tree. Using stack-allocated classes is not an option because that creates many copies and I lose referential stability. Using raw pointers is also not an option because they get deleted by the GC, leaving invalid pointers in the tree. Imagine something like this: class Node {};
class Group : public Node {
public:
void addNode(std::shared_ptr<Node> child) { _children.push_back(child); }
std::shared_ptr<Node> getNode(unsigned int index) { return _children[index]; }
private:
std::vector<std::shared_ptr<Node>> _children;
}; After appending nodes from JS. If I try to read a node I just added, I get the correct pointer, but if I let the GC run (or foce it with The static to_type to_v8(v8::Isolate* isolate, std::shared_ptr<T> const& value)
{
return class_<class_type, shared_ptr_traits>::find_object(isolate, value);
} And changing it to this instead (following the code by @poniraq), seems to work well, at least in my case: static to_type to_v8(v8::Isolate *isolate, std::shared_ptr<T> const &value)
{
auto &class_info = detail::classes::find<shared_ptr_traits>(
isolate, detail::type_id<class_type>());
auto wrapped_obj =
class_<class_type, shared_ptr_traits>::find_object(isolate, value);
if (wrapped_obj.IsEmpty() && class_info.auto_wrap_objects())
{
wrapped_obj = class_<class_type, shared_ptr_traits>::reference_external(
isolate, value);
}
return wrapped_obj;
} The problem with changing that directly in It would be nice if someone with a good mental map of the header dependencies could figure out a way to make this work directly in the v8pp project (i.e. how to untangle the dependency cycle). |
Actually, that approach also doesn't work well. Consider the following Javascript: const root = new Group();
root->addChild(new Group()); // Will be garbage collected because there are no more JS references.
console.log('CHILD', root.getChild(0).constructor.name); // Group, correct.
gc(); // Requires --expose-gc
console.log('CHILD', root.getChild(0).constructor.name); // Node, incorrect. And it gets worse. Once the child gets wrapped as a |
To follow up on this, I found a way to support my particular use case for v8pp. It requires having full control of the classes for which you are creating the bindings, because the idea is to have the class hierarchy collaborate with the implementation of static to_type to_v8(v8::Isolate *isolate, std::shared_ptr<Node> const &value) {
// value-> is used for run-time type identification. But we still have to pass
// the shared_ptr in order to wrap it, otherwise we risk creating two chains
// of shared_ptr pointing to the same object and having it destroyed twice.
return value->wrap(isolate, value);
} And then, every node of the hiearchy would implement their own class Group: public Node {
// ...
virtual v8::Local<v8::Object> wrap(v8::Isolate *isolate,
std::shared_ptr<Node> ptr) override {
auto wrapped_obj =
v8pp::class_<Group, v8pp::shared_ptr_traits>::find_object(
isolate, std::static_pointer_cast<Group>(ptr));
if (!wrapped_obj.IsEmpty()) {
return wrapped_obj;
}
return v8pp::class_<Group, v8pp::shared_ptr_traits>::reference_external(
isolate, std::static_pointer_cast<Group>(ptr));
}
}; Of course, most of that code can be extracted to a generic function to avoid repetition on every node of the hierarchy. What I couldn't figure out is how to extract the RTTI out of the classes I'm wrapping, so it can be reused to create bindings of third-party class hierarchies, e.g. if wrapping existing libraries. Maybe the v8pp internals could make use of |
TL;DR
electron v9.1
node.js v12.14.1
V8 v8.3
v8pp v1.7.0
Am i doing something wrong here? Is this behaviour intended? Is there a better solution?
I have a project that was using nbind for several years, but since it was abandoned, decision was made to upgrade to latest LTS node.js release & adopt another V8 binding library.
Please forgive me if code smells, I write in JS most of the time.
I've set up a playground with v8pp + electron to check things out and immediately stumbled upon this weird behaviour:
As far as i understand, this happens because
shared_ptr<A>
instance fromget_a_by_sptr
is not "registered" anywhere in v8.That's why to_v8 call results in undefined.
But i have far too little experience with C++ to understand exactly why this happens, or if there's anything i'm doing that caused such a behaviour. I managed to come up with a workaround like this:
The text was updated successfully, but these errors were encountered: