0

Having a simple C++ class that uses fstream to write/read binary files Controller.h:

#pragma once
#include <iostream>
#include <fstream>
#include <cstring>
#include <string>
#include <stdio.h>

using namespace std;

template <typename T>
class Controller
{
private:
    T buf;
    char name[40]{};

public:
    explicit Controller(char *name) { strcpy(this->name, name); }
    char *GetName() { return name; }
    bool Insert(T model);
    //    int Search(T &bus);
    //    int Delete(T model);
};

template <typename T>
bool Controller<T>::Insert(T model)
{
    fstream file;
    string db_dir = "data//";
    string file_path = db_dir + name;
    file.open(file_path.c_str(), ios::out | ios::binary);

    if (file.fail() || file.bad())
    {
        cout << "Failed" << endl;
        return false;
    }
    file.write((char *)&model, sizeof(model));
    file.close();
    cout << "Successfully wrote" << endl;
    return true;
}

The code above compiled with gcc works as expected. The Insert function creates a binary file into db//file.dat. But How to achieve the same as a Node addon using node-gyp?

Here's my binding.gyp:

{
    "targets": [{
        "target_name": "TDS",
        "cflags!": ["-fno-exceptions"],
        "cflags_cc!": ["-fno-exceptions"],
        "sources": [
            "modules/src/main.cpp"
        ],
        "include_dirs": [
            # "node_modules/node-addon-api",
            "<!(node -e \"require('nan')\")",
            "<!(node -p \"require('node-addon-api').include_dir\")"
        ],
        'libraries': [],
        'dependencies': [
            "<!(node -p \"require('node-addon-api').gyp\")"
        ],
        'defines': ['NAPI_DISABLE_CPP_EXCEPTIONS']
    }]
}

And my modules/src/main.cpp:

#include <napi.h>
#include "controller/Controller.h"
#include "auth/Login.cpp"
#include "model/Structs.h"

using namespace Napi;
using namespace std;

Value InsertProducto(const CallbackInfo &info)
{
    Env env = info.Env();

    if (!info[0].IsObject())
    {
        TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
        return env.Null();
    }

    Object obj = info[0].ToObject();
    Array props = obj.GetPropertyNames();

    // for (unsigned int j = 0; j < props.Length(); j++)
    // {
    //     // printf("key: %s: value: %s\n",
    //     //        props.Get(j).ToString().Utf8Value().c_str(),
    //     //        obj.Get(props.Get(j)).ToString().Utf8Value().c_str());
    //     string key = props.Get(j).ToString().Utf8Value();
    //     cout << obj.Get(key).ToString().Utf8Value().c_str() << endl;
    // }

    Producto producto = *new Producto();

    producto.id = obj.Get("id").ToNumber().Int32Value();
    producto.id_proveedor = obj.Get("id_proveedor").ToNumber().Int32Value();
    producto.stock = obj.Get("stock").ToNumber().Int64Value();
    producto.precio = obj.Get("precio").ToNumber().FloatValue();
    string descripcion = obj.Get("descripcion").ToString().Utf8Value();
    if (descripcion.length() > sizeof(producto.descripcion))
    {
        string mssg = "Excedeed maximum size: " +
                      sizeof(producto.descripcion);
        cout << mssg << endl;
        TypeError::New(env, mssg).ThrowAsJavaScriptException();
        return env.Null();
    }
    strcpy(producto.descripcion, descripcion.c_str());
    producto.stock_min = obj.Get("stock_min").ToNumber().Int64Value();

    cout << "Producto id: " << producto.id << endl;
    cout << "Producto id_proveedor: " << producto.id_proveedor << endl;
    cout << "Producto stock: " << producto.stock << endl;
    cout << "Producto precio: " << producto.precio << endl;
    cout << "Producto descripcion: " << producto.descripcion << endl;
    cout << "Producto stock_min: " << producto.stock_min << endl;

    Controller<Producto> controller((char *)"Cliente.dat");
    if (!controller.Insert(producto))
    {
        return String::New(env, "Failed");
    }
    return String::New(env, "Successfully inserted");
}
Object Init(Env env, Object exports)
{
    exports.Set(String::New(env, "InsertProducto"), Function::New<InsertProducto>(env));

    return exports;
}

// Register and initialize native add-on
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init);

I'm able to use this function in JavaScript this way:

var TDS = require('../build/Release/TDS.node');

const producto = {
    "id": "1",
    "id_proveedor": "1",
    "stock": "5",
    "precio": "50.0",
    "descripcion": "Un producto",
    "stock_min": "500"
}

console.log(TDS);
console.log(TDS.InsertProducto(producto));

module.exports = TDS;

And everything works except for the controller.Insert call that uses the same Controller.h from above. I'm always seeing the "Failed" message.

How can write/read any binary file using C++ node addons?

Here's the source code for anyone looking to give it a try: https://github.com/JesusJimenezG/transactional-system-managemente

Jesus Jimenez
  • 351
  • 1
  • 3
  • 13
  • Just because code is loaded in the context of a Node.js process doesn't mean basic functionality stops working. Why does the open fail? Is there no `data` directory in the current working directory? Have you tried running your code under `strace` to see how the `open` fails? – Botje Nov 23 '22 at 08:54
  • Try printing the returned error code from the `open` – mmomtchev Nov 23 '22 at 09:10
  • I've got `'ERR_DLOPEN_FAILED'` failed when debugged. Didn't know how to debug node files so you point me into doing it, thanks! @Botje @mmomtchev – Jesus Jimenez Nov 23 '22 at 16:11
  • Additionally, there's a `data` directory in the current working directory `modules/src/data/`, but it is not added in the `binding-gyp` `sources`, should I add it? @Botje – Jesus Jimenez Nov 23 '22 at 16:18
  • It seems like the error code above was due to running `gdb node` on Windows folder from Ubuntu wsl. Running `gdb node ` from Ubuntu's folder the output is different, with various `[Thread 0x7ffff5a47640 (LWP 2330) exited]` lines. – Jesus Jimenez Nov 23 '22 at 17:24
  • `file.open(file_path.c_str(), ios::out | ios::binary);` this line does not produce `ERR_DLOPEN_FAILED`. `ERR_DLOPEN_FAILED` means that your DLL did not load at all. – mmomtchev Nov 24 '22 at 13:08
  • @mmomtchev I initialized the project on Windows, then ran `gdb node` to debug the error on Ubuntu, that's why I got `ERR_DLOPEN_FAILED`. After initializing the project on Ubuntu and running the `gdb node` command on Ubuntu, got a `[Thread 0x7ffff5a47640 (LWP 2330) exited]` error, but not an actual error output at the `file.open(file_path.c_str(), ios::out | ios::binary)` line. – Jesus Jimenez Nov 24 '22 at 17:11
  • You won't get it unless you explicitly print it in your code: `cerr << "Error: " << strerror(errno); ` – mmomtchev Nov 24 '22 at 18:28
  • @mmomtchev thanks!, so the output now is different, if the `data` folder is empty, the `cerr << "Error: " << strerror(errno);` is "No such file or directory". If I create the `.dat` file manually, the output is `[52992:1124/161321.352:ERROR:crashpad_client_win.cc(814)] not connected` – Jesus Jimenez Nov 24 '22 at 20:16

1 Answers1

0

The problem was solved by changing the way of accessing the folder on which to write the file:

Instead of:

    string db_dir = "data/";
    string file_path = db_dir + name;

I'm using filesystem::current_path() now:

    filesystem::path cwd = std::filesystem::current_path() / name;
    file.open(cwd.c_str(), ios::out | ios::binary);

This way works!

Jesus Jimenez
  • 351
  • 1
  • 3
  • 13