Search code examples
c++arraysnode.jsnode-gyp

C++ Emulating int main(int argc, char *argv[]) from node


Basically I am working on integrating an existing C++ file with our javascript / node using their NAPI, functionality. I have it working on a test C++ file so I know I have the setup working. However on the actual C++ file it is designed to run from the command line with argc and argv from the command line. Basically I just need to invoke the main method in C++ from inside of my other function, which means there is no command line. So I have to pass in values for argc and argv. argc is just an int, that is easy enough, but argc is a char ** type, which from my research looks like it is an array of character arrays aka strings?

This is my current code at the bottom of my c++ file

void Init(Env env, Object exports, Object module) {
    exports.Set("main", Function::New(env, main(2,{"test","test2"})));
}
NODE_API_MODULE(addon, Init)

The argc value is working fine

I am trying to create a temporary / test value for argv to pass in but I am having an issue figuring out how to make an array of type char ** with my values.


Solution

  • argv is an array of pointers to strings (actually, NUL-terminated character arrays), where element 0 is the name of the program, elements 1 ... argc-1 are the program arguments, and element argc must be NULL1.

    There's no array literal in C++, so you have to create explicitly an array variable to pass it to a function. Even worse, main is allowed to modify the passed arguments, so you cannot even build an array of string literals, as they are read only; thus, you have to explicitly allocate read/write space for each argument. A barebones solution can be to have single buffers for each argument and build the pointer array out of them:

    char argv0[] = "test_program";
    char argv1[] = "arg1";
    char argv2[] = "arg2";
    char *argv[] = {argv0, argv1, argv2, NULL};
    main(3, argv);
    

    A more flexible one (especially if you have to build your arguments dynamically) can be to use an std::vector<std::string>:

    std::vector<std::string> args = { "test_program", "arg1", "arg2" };
    // ... here you may add other arguments dynamically...
    args.push_back("arg3"); // whatever
    // build the pointers array
    std::vector<char *> argv;
    for(std::string &s: args) argv.push_back(&s[0]);
    argv.push_back(NULL);
    main(argv.size()-1, argv.data());
    

    Now, coming to your code:

    void Init(Env env, Object exports, Object module) {
        exports.Set("main", Function::New(env, main(2,{"test","test2"})));
    }
    NODE_API_MODULE(addon, Init)
    

    besides the fact that you cannot build argv to pass to main like that, you are trying to invoke main and pass its result as second argument to Function::New, while Function::New wants some callable type (e.g. a function pointer) to register as handler for the export named main! Copying from the Function::New documentation:

    /// Callable must implement operator() accepting a const CallbackInfo&
    /// and return either void or Value.
    

    So, as a simple example, you could export your main as a parameterless JS function that returns nothing (undefined, I guess?) by registering a callback like this:

    void MainCallback(const CallbackInfo& info) {
        char argv0[] = "test_program";
        char argv1[] = "arg1";
        char argv2[] = "arg2";
        char *argv[] = {argv0, argv1, argv2, NULL};
        main(3, argv);
    }
    
    void Init(Env env, Object exports, Object module) {
        exports.Set("main", Function::New(env, MainCallback));
    }
    NODE_API_MODULE(addon, Init)
    

    Finally, as others said, technically in C++ main is somewhat magic - it's undefined behavior to invoke it from inside the program; in practice, on any platform that I know of that can also run node.js, main is a perfectly regular function that happens to be invoked by the C runtime at startup, so I don't think that this will cause you any problem.


    Notes

    1. So, you could say it's a NULL-terminated array of NUL-terminated character arrays. Notice that here NULL = null pointer; NUL = string terminator, i.e. '\0'.