All Projects → Geode-solutions → Genepi

Geode-solutions / Genepi

Licence: mit
Automatic generation of N-API wrapper from a C++ library

Projects that are alternatives of or similar to Genepi

Tdl
Node.js bindings to TDLib.
Stars: ✭ 177 (+293.33%)
Mutual labels:  wrapper, bindings
Remodel
Data and class remodeling library
Stars: ✭ 63 (+40%)
Mutual labels:  wrapper, cmake
Xamarin-Android
PSPDFKit for Android wrapper for the Xamarin platform.
Stars: ✭ 18 (-60%)
Mutual labels:  wrapper, bindings
D2sqlite3
A small wrapper around SQLite for the D programming language
Stars: ✭ 67 (+48.89%)
Mutual labels:  wrapper, bindings
Rusqlite
Ergonomic bindings to SQLite for Rust
Stars: ✭ 1,008 (+2140%)
Mutual labels:  wrapper, bindings
Qt5.cr
Qt5 bindings for Crystal, based on Bindgen
Stars: ✭ 182 (+304.44%)
Mutual labels:  wrapper, bindings
Xamarin-iOS
PSPDFKit for iOS wrapper for the Xamarin platform.
Stars: ✭ 14 (-68.89%)
Mutual labels:  wrapper, bindings
Librevault
Librevault - Peer-to-peer, decentralized and open source file sync.
Stars: ✭ 1,001 (+2124.44%)
Mutual labels:  cmake
Termux Mpv
Wrapper for Mpv on Termux. Displays play controls in the notification
Stars: ✭ 43 (-4.44%)
Mutual labels:  wrapper
Ad Edwarthogenhancedscript
An Advanced & Highly Customisable Elite Dangerous Thrustmaster Warthog Script + ED Bindings Pack that utilises Modifiers, allowing for all commands to be easily accessible on the HOTAS. Includes many Quality of Life features to get the most enjoyment out of ED!
Stars: ✭ 39 (-13.33%)
Mutual labels:  bindings
Arduino Rgb Tools
A arduino library with tools for controlling an RGB-LED
Stars: ✭ 37 (-17.78%)
Mutual labels:  cmake
Iterative Closest Point
Stars: ✭ 40 (-11.11%)
Mutual labels:  cmake
Picmake
你还在学CMake的过程中毫无头绪吗?你还在为复杂程序库依赖发愁吗?你是否觉得原生CMake的编写冗余而低效?那就快来学习和使用PICMake吧!只需要一行,无论是可执行,动态库还是静态库,轻松搞定!同时高效支持多目标,复杂库的编译安装,从此告别大量冗余CMake代码,专注开发核心应用程序,编译不再愁! 例如下面是使用PICMake编译一个依赖OpenGL的可执行文件,只需要一行!
Stars: ✭ 43 (-4.44%)
Mutual labels:  cmake
Vst2
Bindings for vst2 sdk
Stars: ✭ 39 (-13.33%)
Mutual labels:  bindings
Glue
⛓️ Bindings that stick. A simple and generic API for C++ to other language bindings supporting bidirectional communication, inheritance and automatic declarations.
Stars: ✭ 44 (-2.22%)
Mutual labels:  bindings
Openconnect Gui
Mirror - Graphical OpenConnect client (beta phase)
Stars: ✭ 993 (+2106.67%)
Mutual labels:  cmake
Libcsptr
Smart pointers for the (GNU) C programming language
Stars: ✭ 1,023 (+2173.33%)
Mutual labels:  cmake
Programming learning resource
学习计算机科学的一些pdf资源
Stars: ✭ 1,019 (+2164.44%)
Mutual labels:  cmake
Skeleton
Skeleton for quick new C++ project setup
Stars: ✭ 42 (-6.67%)
Mutual labels:  cmake
Orne navigation
This repository provides mobile robot navigation system with i-Cart mini for Tsukuba Challenge under Project ORNE.
Stars: ✭ 43 (-4.44%)
Mutual labels:  cmake

Genepiby Geode-solutions

Automatic generation of N-API wrapper from a C++ library

Build Status Version

Windows support Linux support macOS support

Language License Semantic-release Slack invite


Introduction

genepi is a C++11 library providing a complete set of macros to generate a Node.js addon from your C++ code using N-API.

genepi works with cmake-js (a Node.js native addon of CMake) as build system. CMake is largely used in the C++ community, here are some arguments to why CMake is a great build system for Node.js addons: link.

genepi is MIT licensed and based on templates and macros inspired by nbind but using N-API.

Features

genepi allows you to:

  • Use your C++ API from JavaScript without any extra effort.
    • From Node.js and Electron,
    • On Linux, macOS and Windows,
    • Without changes to your C++ code. Simply add a separate short description at the end.
  • Distribute native code binary.

In more detail:

  • Export C++ classes, even ones not visible from other files.
  • Export multiple C++ inheritances, even between several libraries.
  • Export C++ methods simply by mentioning their names.
  • Auto-detect argument and return types from C++ declarations.
  • Automatically convert types and data structures between languages.
  • Call C++ methods from JavaScript with type checking.
  • Pass instances of compatible classes by value between languages (through the C++ stack).

Requirements

You need Node.js (at least v10.x) and one of the following C++ compilers:

Quick start

  1. Use your already existing C++ code in JavaScript
// My C++ code in hello.cpp
#include <iostream>
#include <string>
 
struct Greeter {
    static void sayHello( const std::string& name ) 
    {
        std::cout << "Hello, " << name << std::endl;
    }
};
  1. Install genepi and add some scripts to the package.json
npm install @geode/genepi
{
  "scripts": {
    "build": "cmake-js compile",
    "build:debug": "cmake-js compile -D"
  }
}
  1. Add JavaScript binding
// Add this to the file (or in another file)
#include <genepi/genepi.h>
 
GENEPI_CLASS( Greeter )
{
    GENEPI_METHOD( sayHello );
}
GENEPI_MODULE( hello )
  1. Configure your project by creating a CMakeLists.txt
cmake_minimum_required(VERSION 3.5)

project(my_project)

find_package(genepi REQUIRED PATHS ${PROJECT_SOURCE_DIR}/node_modules/@geode/genepi/build)

add_genepi_library(my_project "hello.cpp")
  1. Compile your addon
npm run build
  1. Use it!
var myProject = require('my_project.node');
myProject.Greeter.sayHello('you');

User guide

Creating your project

Create your repository using the provided Github template: genepi-template. Here is how to use a Github template: link.

Calling from Node.js

Each genepi module (i.e. each Node.js addon generated) needs to be registered using the GENEPI_MODULE macro:

// My C++ library

GENEPI_MODULE( my_addon );

This name my_addon is only used by N-API. The name of the addon is set in the CMakeLists.txt using the add_genepi_library macro. See Quick start.

// My JavaScript file
var example = require('my-genepi-addon.node');

// Use the binding

This require will only work if the module can be found by Node.js. To ease the import, you can use the package bindings. It will try several possible paths to find the module.

// My JavaScript file
var example = require('bindings')('my-genepi-addon');

// Use the binding

Functions

Functions not belonging to any class can be exported inside a named or an anonymous namespace. The C++ function gets exported to JavaScript with the same name using GENEPI_FUNCTION, or it can be renamed by adding a second argument (without quotation marks) using NAMED_GENEPI_FUNCTION.

If the C++ function is overloaded, GENEPI_MULTIFUNCTION macro must be used instead. See overloaded functions.

Example from C++: functions.cpp

#include <iostream>
#include <string>

void sayHello( const std::string& name )
{
    std::cout << "Hello, " << name << std::endl;
}

void sayBye( const std::string& name )
{
    std::cout << "Bye, " << name << std::endl;
}

namespace foo
{
    void sayNamespacedHello( const std::string& name )
    {
        std::cout << "Hello, " << name << std::endl;
    }
}

#include <genepi/genepi.h>

namespace
{
    GENEPI_FUNCTION( sayHello );
    NAMED_GENEPI_FUNCTION( sayBye, sayGoodbye );
}

namespace foo
{
    GENEPI_FUNCTION( sayNamespacedHello );
}

GENEPI_MODULE( functions );

Example from JavaScript: functions.js

var functions = require('genepi-functions.node');

fucntions.sayHello('you'); // Output: Hello, you
fucntions.sayGoodbye('you'); // Output: Bye, you
fucntions.sayNamespacedHello('you'); // Output: Hello, you

Overloaded functions

The GENEPI_FUNCTION() macro cannot distinguish between several overloaded versions of the same function, causing an error. In this case the GENEPI_MULTIFUNCTION() macro must be used.

The second parameter of the macro is the return type. For calling from JavaScript, each overload needs to have a distinct name, given in the third parameter (without quotation marks). The remaining parameters are the parameter types of the C++ function.

Example from C++: overloaded-functions.cpp

#include <iostream>
#include <string>

void test( const std::string& number )
{
    std::cout << "Number " << number << std::endl;
}

void test( int number )
{
    std::cout << "Number " << number << std::endl;
}

void test( int number, int another_number )
{
    std::cout << "Number " << number + another_number << std::endl;
}

#include <genepi/genepi.h>

namespace
{
    GENEPI_MULTIFUNCTION( test, void, test_string, const std::string& );
    GENEPI_MULTIFUNCTION( test, void, test_int, int );
    GENEPI_MULTIFUNCTION( test, void, test_int2, int, int );
}

GENEPI_MODULE( overloaded_functions );

Example from JavaScript: overloaded-functions.js

var overloadedFunctions = require('genepi-overloaded-functions.node');

overloadedFunctions.test_string('42'); // Output: Number 42
overloadedFunctions.test_int(42); // Output: Number 42
overloadedFunctions.test_int2(20, 22); // Output: Number 42

Classes and constructors

The GENEPI_CLASS(className) macro takes the name of your C++ class as an argument (without any quotation marks), and exports it to JavaScript using the same name. It's followed by a curly brace enclosed block of method exports, as if it was a function definition.

The class can be renamed on the JavaScript side by using the NAMED_GENEPI_CLASS macro and passing a string as a second argument. This is especially useful for binding a template class specialization with a more reasonable name: NAMED_GENEPI_CLASS(Data<int>, "IntData").

Constructors are exported with a macro call GENEPI_CONSTRUCTOR(types...); where types is a comma-separated list of arguments to the constructor, such as int, int. Calling GENEPI_CONSTRUCTOR multiple times allows overloading it, but each overload must have a different number of arguments.

Constructor arguments are the only types that genepi cannot detect automatically.

Example from C++: classes.cpp

#include <iostream>
#include <string>

class ClassExample
{
public:
    ClassExample()
    {
        std::cout << "No arguments" << std::endl;
    }

    ClassExample( int a, int b )
    {
        std::cout << "Ints: " << a << " " << b << std::endl;
    }

    ClassExample( const std::string& msg )
    {
        std::cout << "String: " << msg << std::endl;
    }
};

#include <genepi/genepi.h>

GENEPI_CLASS( ClassExample )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_CONSTRUCTOR( int, int );
    GENEPI_CONSTRUCTOR( const std::string& );
}

GENEPI_MODULE( classes );

Example from JavaScript: classes.js

var classes = require('genepi-classes.node');

var a = new classes.ClassExample();  // Output: No arguments
var b = new classes.ClassExample(42, 54); // Output: Ints: 42 54
var c = new classes.ClassExample("Don't panic"); // Output: String: Don't panic

Methods

Methods are exported inside a GENEPI_CLASS or a NAMED_GENEPI_CLASS block with a macro call GENEPI_METHOD which takes the name of the method as an argument (without any quotation marks). The C++ method gets exported to JavaScript with the same name.

If the C++ method is overloaded, GENEPI_MULTIMETHOD macro must be used instead. See overloaded methods.

If the method is static, it becomes a property of the JavaScript constructor function and can be accessed like className.methodName(). Otherwise it becomes a property of the prototype and can be accessed like obj = new className(); obj.methodName();

Example from C++: methods.cpp

#include <iostream>

class MethodExample
{
public:
    void add( int a, int b )
    {
        sum_ += a + b;
        std::cout << "Sum = " << sum_ << std::endl;
    }

    static void static_add( int a, int b )
    {
        MethodExample example;
        example.add( a, b );
    }

private:
    int sum_{ 0 };
};

#include <genepi/genepi.h>

GENEPI_CLASS( MethodExample )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_METHOD( add );
    GENEPI_METHOD( static_add );
}

GENEPI_MODULE( methods );

Example from JavaScript: methods.js


var methods = require('genepi-methods.node');

var example = new methods.MethodExample();
example.add(12, 24); // Output: Sum = 36

methods.MethodExample.static_add(12,24); // Output: Sum = 36

Overloaded methods

The GENEPI_METHOD() macro, like GENEPI_FUNCITON macro, cannot distinguish between several overloaded versions of the same method. In this case the GENEPI_MULTIMETHOD() macro must be used.

The second parameter of the macro is the return type. For calling from JavaScript, each overload needs to have a distinct name, given in the third parameter (WITH quotation marks). The remaining parameters are the parameter types of the C++ method.

Example from C++: overloaded-methods.cpp

#include <iostream>
#include <string>

class OverloadMethod
{
public:
    void test( const std::string& number )
    {
        std::cout << "Number " << number << std::endl;
    }

    void test( int number )
    {
        std::cout << "Number " << number << std::endl;
    }

    void test( int number, int another_number )
    {
        std::cout << "Number " << number + another_number << std::endl;
    }
};

#include <genepi/genepi.h>

GENEPI_CLASS( OverloadMethod )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_MULTIMETHOD( test, void, "test_string", const std::string& );
    GENEPI_MULTIMETHOD( test, void, "test_int", int );
    GENEPI_MULTIMETHOD( test, void, "test_int2", int, int );
}

GENEPI_MODULE( overloaded_methods );

Example from JavaScript: overloaded-functions.js

var overloadedMethods = require('genepi-overloaded-methods.node');

var a = new overloadedMethods.OverloadMethod();
a.test_string('42'); // Ouput: Number 42
a.test_int(42); // Ouput: Number 42
a.test_int2(20, 22); // Ouput: Number 42

Inheritance

When a C++ class inherits another, the GENEPI_INHERIT macro can be used to allow calling parent class methods on the child class, or passing child class instances to C++ methods expecting parent class instances.

Internally JavaScript only has prototype-based single inheritance while C++ supports multiple inheritance. To simulate it, genepi will copy the contents of the parents to the prototype. This has otherwise the same effect, except the JavaScript instanceof operator will return false for the parent classes.

Example from C++: inherit.cpp

#include <iostream>

class FirstParent
{
public:
    FirstParent()
    {
        std::cout << "FirstParent" << std::endl;
    }

    void from_first_parent()
    {
        std::cout << "from first parent" << std::endl;
    }
};

class SecondParent
{
public:
    SecondParent()
    {
        std::cout << "SecondParent" << std::endl;
    }

    void from_second_parent()
    {
        std::cout << "from second parent" << std::endl;
    }
};

class Child: public FirstParent, public SecondParent
{
public:
    Child()
    {
        std::cout << "Child" << std::endl;
    }
};

#include <genepi/genepi.h>

GENEPI_CLASS( FirstParent )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_METHOD( from_first_parent );
}

GENEPI_CLASS( SecondParent )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_METHOD( from_second_parent );
}

GENEPI_CLASS( Child )
{
    GENEPI_CONSTRUCTOR();
    GENEPI_INHERIT( FirstParent );
    GENEPI_INHERIT( SecondParent );
}

GENEPI_MODULE( inherit );

Example from JavaScript: overloaded-methods.js

var inherit = require('genepi-inherit.node');

var a = new inherit.Child(); // Ouput: FirstParent / SecondParent / Child
a.from_first_parent(); // Output: from first parent
a.from_second_parent(); // Output: from second parent

Passing data structures

genepi supports automatically converting between JavaScript arrays and C++ std::vector or std::array types. Just use them as arguments or return values in C++ methods.

Note that data structures don't use the same memory layout in both languages, so the data always gets copied which takes more time for more data. For example the strings in an array of strings also get copied, one character at a time.

Using objects

C++ objects can be passed to and from JavaScript using different parameter and return types in C++ code:

  • by reference using pointers or references
  • by value

Constness of objects is not ensured by genepi. So you could call a non const method on a const object.

Note: using pointers and references is particularly dangerous because the pointer may become invalid without JavaScript noticing it.

Example from C++: objects.cpp

#include <iostream>

class Coord
{
public:
    Coord( int x, int y ) : x_( x ), y_( y ) {}

    int getX()
    {
        return x_;
    }
    int getY()
    {
        return y_;
    }

private:
    int x_, y_;
};

class ObjectExample
{
public:
    static void showByValue( Coord coord )
    {
        std::cout << "C++ value " << coord.getX() << ", " << coord.getY()
                  << std::endl;
    }

    static void showByRef( Coord* coord )
    {
        std::cout << "C++ ref " << coord->getX() << ", " << coord->getY()
                  << std::endl;
    }

    static Coord getValue()
    {
        return Coord{ 12, 34 };
    }

    static Coord* getRef()
    {
        static Coord coord{ 56, 78 };
        return &coord;
    }
};

#include <genepi/genepi.h>

GENEPI_CLASS( Coord )
{
    GENEPI_CONSTRUCTOR( int, int );
    GENEPI_METHOD( getX );
    GENEPI_METHOD( getY );
}

GENEPI_CLASS( ObjectExample )
{
    GENEPI_METHOD( showByValue );
    GENEPI_METHOD( showByRef );
    GENEPI_METHOD( getValue );
    GENEPI_METHOD( getRef );
}

GENEPI_MODULE( objects );

Example from JavaScript: objects.js

var objects = require('genepi-objects.node');

var value1 = new objects.Coord(123, 456);
var value2 = objects.ObjectExample.getValue();
objects.ObjectExample.showByValue(value1); // Output: C++ value 123, 456
objects.ObjectExample.showByValue(value2); // Output: C++ value 12, 34

var ref = objects.ObjectExample.getRef();
objects.ObjectExample.showByRef(ref); // Output: C++ ref 56, 78

Type conversion

Parameters and return values of function calls between languages are automatically converted between equivalent types:

JavaScript C++
number (un)signed char, short, int, long
number float, double
boolean bool
string const (unsigned) char *
string std::string
Array std::vector<type>
Array std::array<type, size>
genepi-wrapped pointer Pointer or reference to an instance of any bound class
See Using objects

Alternatives

Questions

For questions and support please use the official slack and go to the channel #genepi. The issue list of this repo is exclusively for bug reports and feature requests.

Changelog

Detailed changes for each release are documented in the release notes.

License

MIT

Copyright (c) 2019-present, Geode-solutions

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].