Skip to content

Missing module in Init #449

Closed
Closed

Description

@raffaeler

With the legacy api, I could obtain the addon module using this code:

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateObject);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

I need the module to obtain the addon filename (and folder) using this code:

std::string static GetModuleFilename(v8::Handle<v8::Object> module)
{
    Isolate* isolate = Isolate::GetCurrent();
    auto v8filename = module->Get(v8::String::NewFromUtf8(isolate, "filename"));
    auto filename = NodeTypes::FromV8String(v8filename);
    return filename;
}

How can I get the module and the filename at initialization time now?

Activity

  1. gabrielschulhof commented on Feb 23, 2019

    @gabrielschulhof
    Contributor

    @mhdawson adding stuff to env wouldn't work because env is shared among addons, unless we update the fields of env at all points before ceding control to the addon. That, in turn would add to the cost of each callback.

    With regards to the index.js, it is an additional step just to receive the module information, which is something that existed before N-API ... isn't this a clear sign of a missing/forgot feature?

    @raffaeler we intentionally didn't create a one-to-one mapping of pre-existing API to N-API. Another consequence, had we sent the Napi::Object module into the initializer, would have been that we would have expressed an opinion in favour of CommonJS modules and against ESM modules, which would have driven us to design explicit support for ESM modules later on. We did assume that most npm packages interject an index.js which processes the native bindings and provides a more JavaScript-friendly interface to its dependents. Passing through a JavaScript object and having the native addon decorate it is the lowest common denominator.

    I acknowledge that this means that, in the case of your package, you would have no choice but to change the way it initializes itself. To that end I created https://gist.github.com/gabrielschulhof/df079c9a89fe05dfa740b7171e0aa760 to illustrate the way in which I would convey the addon's absolute path into the addon, formulating the whole thing as an npm package that provides the addon to its dependents.

    Can you please go over the code and point out the ways in which it would fail under the circumstances in which your addon is being used?

  2. raffaeler commented on Feb 23, 2019

    @raffaeler
    Author

    @gabrielschulhof Thanks for the example, but I want to exclude any requirement in terms of a javascript initializer as the initial require can also be made from another native addon. I really don't understand why I can 'expand' __dirname for my module inside C++. It looke like a discordant note.

    I am not asking to restore the Module object but just an addon specific object containing the information about the module itself.
    A different approach would be a dictionary with all the modules. Not sure about the security implications of this choice.

    Going back to my previous comment, the documentation doesn't help in telling me what are the paths that:

    • are always available in any environment
    • are giving me some starting point for probing the subfolders.

    For example:

    • what's the best way to find the folder fo the apps' package.json?
    • is it true that (beyond development time) addons are always inside the node_modules folder?
  3. raffaeler commented on Feb 23, 2019

    @raffaeler
    Author

    An additional consideration. I remember to have seen different results (even in javascript) when the addon is packed from webpack.
    I strongly believe that:

    • the documentation should give more info about the possible change of the variable/objects values depending on all these situations (pure node, electron, webpack, etc.)
    • add to the runtime a number of values that are always available and trustable

    my 2 cents

  4. gabrielschulhof commented on Feb 23, 2019

    @gabrielschulhof
    Contributor

    I really don't understand why I can 'expand' __dirname for my module inside C++.

    @raffaeler Node.js wraps CommonJS modules in the following:

    (function (exports, require, module, __filename, __dirname) {
    // original contents of JS file
    });

    and then passes exports, require, module, __filename, and __dirname to the resulting function when executing the module.

    AFAIK it does no such thing for native modules, and also AFAIK there is no way to retrieve values of variables that are in scope. I guess it's setting __filename on module and passing that into the native module loader, and that was useful.

    I have to say that I'm amazed that there is code out there that loads one native addon from another native addon! Thanks for bringing up this use case! How exactly is that accomplished? Is it done by acquiring and then calling an instance of the require() function, or perhaps by using uv_dlopen() directly?

    We should definitely consider formalizing some values we make available to native modules, but we should do so while keeping in mind the diverse nature of the environments in which these modules might be loaded.

  5. gabrielschulhof commented on Feb 23, 2019

    @gabrielschulhof
    Contributor

    We also need to keep in mind that dlopen() may follow symlinks when loading a module, so the path given to dlopen() may not be the path where the module actually resides, and dlopen() doesn't report back the results of its traversal – at least not in a portable way. In this case, if we were to report that path to the module, it would be incorrect.

  6. raffaeler commented on Feb 23, 2019

    @raffaeler
    Author

    @gabrielschulhof got it, now I get why __dirname is not available, thanks for the explanation.

    How exactly is that accomplished? Is it done by acquiring and then calling an instance of the require() function, or perhaps by using uv_dlopen() directly

    I expect my addon can be loaded from other native addons. On my side I did it in other addons by getting process.mainModule.require from C++, but my addon is not currently doing it.

    Said that, my addon do load other dlls shipped as part of the addon package (no need for require in this case).
    The reason for this is that my addon is a generic host for .NET code (which dynamically generates the code needed to call and marshal from/to javascript and typescript). I dynamically load using dlopen() and once the .NET CLR is loaded it will load other assemblies that will be found in the folders I passed to it.

    We should definitely consider formalizing some values we make available to native modules, but we should do so while keeping in mind the diverse nature of the environments in which these modules might be loaded.

    That would be great. Glad to have contributed to that.

  7. gabrielschulhof commented on Feb 25, 2019

    @gabrielschulhof
    Contributor

    Hey! Can you unmute me? I joined by phone.

  8. raffaeler commented on Feb 25, 2019

    @raffaeler
    Author

    @gabrielschulhof I can't ... you only can do it via web interface. Sorry

  9. mhdawson commented on Mar 4, 2019

    @mhdawson
    Member

    @raffaeler @gabrielschulhof we've discussed in the recent N-API team meetings and @gabrielschulhof is trying out some different options and will post an update once he's done that.

  10. argv-minus-one commented on May 21, 2019

    @argv-minus-one

    @gabrielschulhof The fact that the addon initializer returns a single napi_value and takes a parameter napi_value exports is already an expression of favor of CommonJS over ES6 modules. ES6 modules export zero or more individual named values, not exactly one unnamed value like CommonJS and current N-API.

  11. gabrielschulhof commented on May 22, 2019

    @gabrielschulhof
    Contributor

    @argv-minus-one the intention with N-API was to get bindings into JS somehow and let JS impose its abstractions, like CommonJS or ESM, on top. The simplest way to achieve that was to transmit one napi_value into JS.

    The problem in this case arises from the fact that the native addon is the package entry point, rather than a script.

  12. github-actions commented on Dec 17, 2020

    @github-actions
    Contributor

    This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.

  13. raffaeler commented on Dec 17, 2020

    @raffaeler
    Author

    @gabrielschulhof @mhdawson Do you have any update on this?
    I still have the need to start from C++ and would be glad to migrate to N-API, but I am stuck on this.
    Thank you

  14. mhdawson commented on Jan 22, 2021

    @mhdawson
    Member

    We've discussed this and don't see any good options that fit with the abstractions we want to preserve in the API. Since there are alternatives which covers most of the use case, at this point unless there are more requests/cases where it blocks adoption its not in the work we are planning. Alternatives include:

    • Suggestion outlined in - Missing module in Init #449 (comment)
    • making the exports a function that can receive the module and return the bindings as a function of that -> const addon = require('./build/Release/addon.node')(module); there are then several options of how to create the bindings with knowledge of the module object.
  15. raffaeler commented on Jan 22, 2021

    @raffaeler
    Author

    Hi @mhdawson
    I can think to other use-cases that will benefit from my request.
    For example if you want to host javascript code from native code (or any other language), the starting point cannot be javascript.
    (I want to remember that in my case I am not in control of javascript code at all)

    Let's see the issue from another perspective.
    Instead of the module, could you instead expose the information that the runtime environment has? (I need the addon path but others may be useful as well)
    I could see that even many js libraries are struggling in bulding paths without having precise/reliable information.

    Thanks

  16. gabrielschulhof commented on Feb 2, 2021

    @gabrielschulhof
    Contributor

    @raffaeler I have an idea how you can get your add-on's path from the address of a symbol inside your add-on, however, it's platform-dependent. What kinds of platforms do you need to support?

  17. raffaeler commented on Feb 2, 2021

    @raffaeler
    Author

    @gabrielschulhof frankly at this stage I can't predict them. For sure Windows and Linux, but since my addon is a kind of "glue" between nodejs and .NET, it can potentially be used anywhere.

    BTW by spying file activities I see a lot of addons making deep search through various folder to find themselves. This means that many others have the same need but just didn't ask for it. Making this search leads to poor performance and can potentially break very easily.
    It is my belief that an official way to take the path would have beneficial effects on many addons.

  18. mhdawson commented on Feb 2, 2021

    @mhdawson
    Member

    @raffaeler do you have that list of other modules that you can share?

  19. raffaeler commented on Feb 3, 2021

    @raffaeler
    Author

    I didn't save a list @mhdawson. I installed (and later removed) a bunch of addons to try understanding how could they get the correct folders. Using Windows utility procmon I discovered they were just trying probing entire trees.

    BTW if I remember well, not directly related, but node-gyp itself probes debug, release and other folders to search for the native addon just because there is no info about the directory structure

  20. gabrielschulhof commented on Feb 3, 2021

    @gabrielschulhof
    Contributor

    @raffaeler it's not node-gyp that does that but bindings. However, since bindings itself is loaded from another JS module, it is able to compute the package root directory and it searches a hard-coded list of (historical) paths for modules descending from that package root directory.

    But check out nodejs/node#37195 – let's see how it goes 🤷

  21. raffaeler commented on Feb 10, 2021

    @raffaeler
    Author

    Great work @gabrielschulhof
    Starting from what version this change will be available?
    Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions