c++node-gypnode-addon-apin-api

Write/Read files from C++ node addon


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


Solution

  • 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!