Skip to content
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

Discussion: How to avoid manually setting descriptors for each attribute? #1550

Open
edoCsItahW opened this issue Aug 5, 2024 · 3 comments
Assignees

Comments

@edoCsItahW
Copy link

How to avoid manually setting descriptors for each attribute

There is a class that has many properties, but I have a way to dynamically collect these properties, so I use Maps to store them and hope they can be exposed to Nodejs. That's where the problem lies.


Assuming that according to the process, it should be like this

class A {
    private:
        attr1;
        attr2;
        ...
    
    public:
        getAttr1();
        setAttr1(attr);
        getAttr2();
        setAttr2();
        
        Init(env, exports) {
            func = DefineClass(env, "A", {
                InstanceAccessor("attr1", &A::getAttr1, &A::setAttr1),
                InstanceAccessor("attr1", &A::getAttr2, &A::setAttr2),
                ...
            })
        }
}

But I don't want to set descriptors for every attribute, so I made the following attempts

  1. Use decorators
// Definition in napi. h
using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info);
using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, const Napi::Value& value);

This doesn't work because the T::* in InstanceGetterCallback restricts my return type from being a free function

  1. Retrieve the key from the parameters received by the Setter function
set(const Napi::CallbackInfo& info, const Napi::Value& value)

But info does not contain a key, it contains the same value as value, and the key passed in by the user is unknown what has taken it away

So do you have any good methods?

@broken
Copy link

broken commented Aug 7, 2024

There's nothing I've found, but if you don't mind doing a bit of debugging & adapting, you can look at: https://github.com/broken/napi-wrapper-gen

It's a small app I wrote that uses antlr to read a c++ header file and generate the corresponding Napi header & source files. It works well for my use-case, and while I've tried to keep it generic, it is not all encompassing and there are likely some app specific things that will need to be adjusted.

Example: from Song.h, it will generate Song_wrap.h & Song_wrap.cpp.

@KevinEady
Copy link
Contributor

Hi @edoCsItahW ,

But I don't want to set descriptors for every attribute

What is the reasoning behind this, as that would determine a solution.

If it's because you don't want to replicate a lot of "boilerplate" code, then I would suggest taking a look at the solution above.

If it's because you don't want your class prototype to have a bunch of instance methods on it, you could try looking at exposing only one instance method which takes the intended method name + args as its own argument, and use a Proxy that calls that single instance method.

@edoCsItahW
Copy link
Author

Hey @broken and @KevinEady,

Thanks a lot for your help!

To start, @KevinEady, you’re right. Maybe I should provide a bit more context. So here’s the deal:

I’m trying to whip up an adapter in C++ to act as middleware between Node and another language (let’s just call it a server for brevity).
When users instantiate a class from the other language in Node and try to access its properties, the best way to handle it is to send the property name to the server, process the returned data into the appropriate type, and then send it back to the user. That’s the root of the issue.

I had already made the call to create a base class and use @broken's tools to add property descriptors—definitely better than doing it for every single class. But, due to some health stuff, I haven’t gotten around to implementing it.

@KevinEady, I’m interested in trying out your approach. I really hadn’t thought about breaking through from the Node side, and it seems promising in theory.

That said, using decorators sounds super tempting, as it could elegantly accomplish a lot with minimal code. So I hope it’s not too forward of me to ask: is there a specific reason for the existence of "T::*"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

No branches or pull requests

4 participants