{"id":19938433,"url":"https://github.com/hunyadi/javabind","last_synced_at":"2025-05-03T14:32:08.786Z","repository":{"id":232259655,"uuid":"780136141","full_name":"hunyadi/javabind","owner":"hunyadi","description":"Effective C++ and Java interoperability via JNI","archived":false,"fork":false,"pushed_at":"2025-02-24T23:19:05.000Z","size":182,"stargazers_count":5,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-07T16:41:11.261Z","etag":null,"topics":["bindings","cpp","interoperability","java","jni-wrapper"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hunyadi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-31T19:45:58.000Z","updated_at":"2025-03-24T19:18:34.000Z","dependencies_parsed_at":"2024-04-16T21:46:11.868Z","dependency_job_id":"adf8e628-9e45-4cc1-9935-502517038877","html_url":"https://github.com/hunyadi/javabind","commit_stats":null,"previous_names":["hunyadi/javabind"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hunyadi%2Fjavabind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hunyadi%2Fjavabind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hunyadi%2Fjavabind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hunyadi%2Fjavabind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hunyadi","download_url":"https://codeload.github.com/hunyadi/javabind/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252203309,"owners_count":21710939,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bindings","cpp","interoperability","java","jni-wrapper"],"created_at":"2024-11-12T23:40:06.493Z","updated_at":"2025-05-03T14:32:08.779Z","avatar_url":"https://github.com/hunyadi.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# javabind: C++ and Java interoperability\n\njavabind is a lightweight C++17 header-only library that exposes C++ types to Java and vice versa, primarily in order to create Java bindings for existing C++ code. The objective of this library is to provide an easy-to-use interoperability interface that feels natural from both C++ and Java. javabind uses C++ compile-time introspection to generate Java Native Interface (JNI) stubs that can be accessed from Java with regular function invocation. The stubs incur minimal or no overhead compared to hand-written JNI code.\n\nThis project has been inspired by a similar binding interface between JavaScript and C++ in [emscripten](https://emscripten.org), between Python and C++ in [PyBind11](https://pybind11.readthedocs.io/en/stable/) and [Boost.Python](https://www.boost.org/doc/libs/1_81_0/libs/python/doc/html/index.html), and between Kotlin and C++ in [ktbind](https://github.com/hunyadi/ktbind). Unlike [JNA](https://github.com/java-native-access/jna), which one can utilize by means of an intermediary C interface, javabind offers a direct interface between C++ and Java.\n\n## Core features\n\nThe following C++ features can be mapped to Java:\n\n* Fundamental types and custom data structures as function arguments or return values\n* Instance methods and static methods\n* Functions with template parameters\n* Overloaded functions\n* Operators\n* Instance attributes and static attributes\n* Arbitrary exception types\n* STL containers\n* Enumeration types\n\nFurthermore, the following Java features are seamlessly exposed to C++:\n\n* Primitive types and boxed primitive types\n* Arrays of primitive types passed with zero-copy semantics\n* Collection types\n* Functional interfaces and lambda expressions\n* Arbitrary exception types\n\n## Getting started\n\njavabind is a header-only library, including the interoperability header in your C++ project allows you to create bindings to Java:\n\n```cpp\n#include \u003cjavabind/javabind.hpp\u003e\n```\n\nConsider the following C++ class as an example:\n\n```cpp\nclass Person\n{\n    std::string name;\n    Residence residence;\npublic:\n    Person() = default;\n    Person(const std::string\u0026 n) : name(n) {}\n    Person(const std::string\u0026 n, const Residence\u0026 r) : name(n), residence(r) {}\n    Residence get_residence() const { return residence; }\n    void set_residence(const Residence\u0026 r) { residence = r; }\n};\n```\n\nLet's suppose we want to expose this classes to Java. All Java bindings should be registered in the extension module block. We use `native_class` and its builder functions `constructor` and `function` to expose the member functions of the class `Person`:\n\n```cpp\nDECLARE_NATIVE_CLASS(Person, \"hu.info.hunyadi.test.Person\");\n\nJAVA_EXTENSION_MODULE() {\n    using namespace javabind;\n    native_class\u003cPerson\u003e()\n        .constructor\u003cPerson(std::string)\u003e(\"create\")\n        .constructor\u003cPerson(std::string, Residence)\u003e(\"create\")\n        .function\u003c\u0026Person::get_residence\u003e(\"getResidence\")\n        .function\u003c\u0026Person::set_residence\u003e(\"setResidence\")\n        ;\n    print_registered_bindings();\n}\n```\n\n`DECLARE_NATIVE_CLASS` assigns a fully-qualified Java class name to the C++ class, which is required to look up objects at run time.\n\nThe type parameter of the template function `constructor` is a function signature to help choose between multiple available constructors. Use the same style you would with `std::function\u003cR(Args...)\u003e`.\n\nThe non-type template parameter of `function` is a function pointer, either a member function pointer (as shown above) or a free function pointer. If multiple functions have the same name (making the pointer reference ambiguous), a static cast to the right signature might be necessary.\n\n`print_registered_bindings` is a utility function that lets you print the Java class definition that corresponds to the registered C++ class definitions. `print_registered_bindings` prints to Java `System.out` when you load the compiled shared library (`*.so` on macOS and Linux, or `*.dll` on Windows) with Java's `System.loadLibrary()`. You would normally use it in the development phase.\n\nNext, we need corresponding native bindings in Java:\n\n```java\npackage hu.info.hunyadi.test;\n\nimport hu.info.hunyadi.javabind.NativeObject;\n\npublic class Person extends NativeObject {\n    public static native Person create(String name);\n    public static native Person create(String name, Residence residence);\n    public native void close();\n    public native Residence getResidence();\n    public native void setResidence(Residence residence);\n}\n```\n\nThe example above highlights many interesting characteristics.\n\nFirst, `Person` derives from `NativeObject`. Internally, `NativeObject` stores a raw pointer as a Java `long`. This raw pointer is completely opaque to Java, and refers to an object in the C++ memory space. When methods of `Person` are called, the raw pointer is de-referenced, and the call is routed to the C++ mapping of `Person`, as registered by `native_class` in the extension module block.\n\nSecond, there are two factory methods to create instances of `Person`. Factory methods are necessary because the raw pointer has to be populated in C++, and cannot be set in Java. The `constructor` call in `native_class` routes the Java factory method to template-generated C++ code, which creates the object, calls the appropriate C++ constructor, sets the raw pointer, and returns a reference to the object. Meanwhile, `NativeObject` has a protected constructor to prevent instantiating `Person` objects directly in Java.\n\nThird, `Person` implements the `close` method inherited from the `AutoCloseable` interface (via `NativeObject`). C++ objects have constructors and destructors but Java (JVM) has garbage collection. In order to ensure that objects are properly reclaimed when they are no longer needed, `native_class` in C++ binds a template-generated de-allocator to `close`, and calling the `close` method triggers the C++ destructor. You may have `close()` called automatically at the end of a `try` block in Java.\n\nFinally, getter and setter methods involve a Java record class called `Residence`. `Residence` is defined in Java as follows:\n\n```java\npublic record Residence(String country, String city) {\n    String getCountry() {\n        return country;\n    }\n\n    String getCity() {\n        return city;\n    }\n}\n```\n\nIn order to make fields of the record class accessible from C++, we need to define a corresponding C++ class and declare bindings:\n\n```cpp\nstruct Residence\n{\n    std::string country;\n    std::string city;\n};\n\nDECLARE_RECORD_CLASS(Residence, \"hu.info.hunyadi.test.Residence\");\n\nJAVA_EXTENSION_MODULE()\n{\n    using namespace javabind;\n    // ...\n    record_class\u003cResidence\u003e()\n        .field\u003c\u0026Residence::country\u003e(\"country\")\n        .field\u003c\u0026Residence::city\u003e(\"city\")\n        ;\n}\n```\n\nThe above declaration makes `Residence` a type that we can pass and return in function calls. When `Residence` objects are received, data in Java is copied into the `struct` defined in C++. When `Residence` objects are returned, data in C++ is copied into the Java record class.\n\n## Signatures\n\nC++ function signatures that are invoked from Java can take arguments by value or by const reference. C++ functions return simple or composite types by value.\n\n## Type mapping\n\njavabind recognizes several widely-used types and marshals them automatically between C++ and Java without explicit user-defined type specification:\n\n| C++ type | Java consumed type | Java produced type |\n| -------- | ------------------ | ------------------ |\n| `void` | n/a | `void` |\n| `bool` | `boolean` | `boolean` |\n| `int8_t` | `byte` | `byte` |\n| `char16_t` | `char` | `char` |\n| `int16_t` | `short` | `short` |\n| `int32_t` | `int` | `int` |\n| `int64_t` | `long` | `long` |\n| `float` | `float` | `float` |\n| `double` | `double` | `double` |\n| `std::string` (UTF-8) | `String` | `String` |\n| `std::string_view` (UTF-8) | `String` | `String` |\n| `std::u16string_view` (UTF-16) | `String` | `String` |\n| `boxed\u003cbool\u003e` | `Boolean` | `Boolean` |\n| `boxed\u003cint8_t\u003e` | `Byte` | `Byte` |\n| `boxed\u003cchar16_t\u003e` | `Character` | `Character` |\n| `boxed\u003cint16_t\u003e` | `Short` | `Short` |\n| `boxed\u003cint32_t\u003e` | `Integer` | `Integer` |\n| `boxed\u003cint64_t\u003e` | `Long` | `Long` |\n| `boxed\u003cfloat\u003e` | `Float` | `Float` |\n| `boxed\u003cdouble\u003e` | `Double` | `Double` |\n| `std::basic_string_view\u003cT\u003e` if `T` is an arithmetic type | `T[]` | n/a |\n| `std::vector\u003cT\u003e` if `T` is an arithmetic type | `T[]` | `T[]` |\n| `std::vector\u003cT\u003e` if `T` is not an arithmetic type | `java.util.List\u003cT\u003e` | `java.util.ArrayList\u003cT\u003e` |\n| `std::set\u003cE\u003e` | `java.util.Set\u003cE\u003e` | `java.util.TreeSet\u003cE\u003e` |\n| `std::unordered_set\u003cE\u003e` | `java.util.Set\u003cE\u003e` | `java.util.HashSet\u003cE\u003e` |\n| `std::map\u003cK,V\u003e` | `java.util.Map\u003cK,V\u003e` | `java.util.TreeMap\u003cK,V\u003e` |\n| `std::unordered_map\u003cK,V\u003e` | `java.util.Map\u003cK,V\u003e` | `java.util.HashMap\u003cK,V\u003e` |\n| `std::optional\u003cT\u003e` | `T` | `T` |\n| `std::chrono::nanoseconds` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::microseconds` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::milliseconds` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::seconds` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::minutes` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::hours` | `java.time.Duration` | `java.time.Duration` |\n| `std::chrono::system_clock::time_point` | `java.time.Instant` | `java.time.Instant` |\n| `std::function\u003cR(T)\u003e` | `Function\u003cT,R\u003e` | `NativeFunction\u003cT,R\u003e` implements `Function\u003cT,R\u003e` |\n| `std::function\u003cR(int32_t)\u003e` | `IntFunction\u003cR\u003e` | `NativeIntFunction\u003cR\u003e` implements `IntFunction\u003cR\u003e` |\n| `std::function\u003cR(int64_t)\u003e` | `LongFunction\u003cR\u003e` | `NativeLongFunction\u003cR\u003e` implements `LongFunction\u003cR\u003e` |\n| `std::function\u003cR(double)\u003e` | `DoubleFunction\u003cR\u003e` | `NativeDoubleFunction\u003cR\u003e` implements `DoubleFunction\u003cR\u003e` |\n| `std::function\u003cint32_t(T)\u003e` | `ToIntFunction\u003cT\u003e` | `NativeToIntFunction\u003cT\u003e` implements `ToIntFunction\u003cT\u003e` |\n| `std::function\u003cint64_t(T)\u003e` | `ToLongFunction\u003cT\u003e` | `NativeToLongFunction\u003cT\u003e` implements `ToLongFunction\u003cT\u003e` |\n| `std::function\u003cdouble(T)\u003e` | `ToDoubleFunction\u003cT\u003e` | `NativeToDoubleFunction\u003cT\u003e` implements `ToDoubleFunction\u003cT\u003e` |\n| `std::function\u003cbool(T)\u003e` | `Predicate\u003cT\u003e` | `NativePredicate\u003cT\u003e` implements `Predicate\u003cT\u003e` |\n| `std::function\u003cbool(int32_t)\u003e` | `IntPredicate` | `NativeIntPredicate` implements `IntPredicate` |\n| `std::function\u003cbool(int64_t)\u003e` | `LongPredicate` | `NativeLongPredicate` implements `LongPredicate` |\n| `std::function\u003cbool(double)\u003e` | `DoublePredicate` | `NativeDoublePredicate` implements `DoublePredicate` |\n| `std::function\u003cvoid(T)\u003e` | `Consumer\u003cT\u003e` | `NativeConsumer\u003cT\u003e` implements `Consumer\u003cT\u003e` |\n| `std::function\u003cvoid(int32_t)\u003e` | `IntConsumer` | `NativeIntConsumer` implements `IntConsumer` |\n| `std::function\u003cvoid(int64_t)\u003e` | `LongConsumer` | `NativeLongConsumer` implements `LongConsumer` |\n| `std::function\u003cvoid(double)\u003e` | `DoubleConsumer` | `NativeDoubleConsumer` implements `DoubleConsumer` |\n\n`boxed` is a lightweight C++ wrapper defined by the library to match Java boxed types such as `java.lang.Integer`. `boxed` has no C++ run-time overhead, it is only used for disambiguation.\n\nCollection types are copied between C++ and Java.\n\nOptionals are converted to a null-value in Java when they don't have a value in C++. Null-values are converted to an empty optional in C++.\n\nC++ types `basic_string_view\u003cT\u003e` translate to JNI calls `GetPrimitiveArrayCritical` and `ReleasePrimitiveArrayCritical` to get a direct pointer to the memory managed by the Java Virtual Machine (JVM). This imposes [significant restrictions](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetPrimitiveArrayCritical_ReleasePrimitiveArrayCritical):\n\n\u003e After calling `GetPrimitiveArrayCritical`, the native code should not run for an extended period of time before it calls `ReleasePrimitiveArrayCritical`. We must treat the code inside this pair of functions as running in a \"critical region.\" Inside a critical region, native code must not call other JNI functions, or any system call that may cause the current thread to block and wait for another Java thread. (For example, the current thread must not call read on a stream being written by another Java thread.)\n\nThe C++ type `u16string_view` translates to JNI calls `GetStringCritical` and `ReleaseStringCritical`, which entail similar restrictions as `GetPrimitiveArrayCritical` and `ReleasePrimitiveArrayCritical`.\n\n## C++ unsigned integer types\n\nUnsigned integers are not supported in Java. However, it is possible to marshal a C++ unsigned integer type to a compatible Java signed integer type. Two modes are supported. Conversions for C++ unsigned types are disabled by default, and require explicit opt-in.\n\n### Signed cast\n\nSigned cast assigns the value of a C++ unsigned integer type to a Java signed integer type of the same width, copying all bits. You can opt in to signed cast by defining the preprocessor symbol `JAVABIND_INTEGER_SIGNED_CAST` (or enabling the CMake option `JAVABIND_INTEGER_SIGNED_CAST`).\n\n### Widening conversion\n\nWidening converts the value of a C++ unsigned integer type to a wider Java signed integer type such that the target type can accommodate all values the source type can assume. You can opt in to convert unsigned integers to a wider Java type by defining the preprocessor symbol `JAVABIND_INTEGER_WIDENING_CONVERSION` (or enabling the CMake option `JAVABIND_INTEGER_WIDENING_CONVERSION`).\n\nIf enabled, the following integer type conversions take place:\n\n| C++ type | Java type |\n| -------- | --------- |\n| `uint8_t` | `short` |\n| `uint16_t` | `int` |\n| `uint32_t` | `long` |\n\nNo run-time range checks are performed when passing a value from Java to C++. If the Java value is outside the range of the C++ type (e.g. negative value), the result is undefined.\n\n## Enumeration types\n\njavabind can expose C++ `enum` (and `enum class`) to Java.\n\nFirst, define a C++ `enum` that needs to be exported to Java:\n\n```cpp\nenum class MyEnum\n{\n    Foo,\n    Bar\n};\n\nDECLARE_ENUM_CLASS(MyEnum, \"hu.info.hunyadi.test.MyEnum\");\n\nJAVA_EXTENSION_MODULE()\n{\n    using namespace javabind;\n    // ...\n    enum_class\u003cMyEnum\u003e()\n        .value(MyEnum::Foo, \"Foo\")\n        .value(MyEnum::Bar, \"Bar\")\n        ;\n}\n```\n\nSecond, define a corresponding enumeration class in Java:\n\n```java\npackage hu.info.hunyadi.test;\n\npublic enum MyEnum {\n    Foo,\n    Bar\n}\n```\n\n## Exceptions\n\nExceptions thrown in C++ automatically trigger a Java exception when crossing the language boundary. The interoperability layer catches all exceptions that inherit from `std::exception`, and throws a `java.lang.Exception` before passing control back to the JVM.\n\nExceptions originating from Java are automatically wrapped in a C++ type called `JavaException`, which derives from `std::exception`. The function `what()` in `JavaException` retrieves the Java exception message. C++ code can catch `JavaException` and take appropriate action, which causes the exception to be cleared in Java.\n\n## Functional interface\n\njavabind can expose C++ function objects (`std::function\u003cR(T)\u003e`) to Java with wrappers that implement functional interfaces such as `Function\u003cT,R\u003e` or `Predicate\u003cT\u003e`. Each wrapper such as `NativeFunction\u003cT,R\u003e` or `NativePredicate\u003cT\u003e` extends the abstract base class `NativeCallback`, which is responsible for encapsulating a raw pointer. This raw pointer points at a memory location in the C++ domain, allocated with the operator `new`, and de-allocated with `delete` once the Java wrapper is garbage collected. Invocation is done in a way similar to regular native class methods but the call is bound not to an object instance (as with `NativeObject`) but to a function object.\n\nBecause function objects as C++ return values are depending on class definitions in Java, auxiliary classes such as `NativeFunction\u003cT,R\u003e` or `NativePredicate\u003cT\u003e` must be available on the class path at binding registration time to be accessible for `FindClass`. All of these are defined in the namespace `hu.info.hunyadi.javabind`.\n\nAuxiliary classes use `java.lang.ref.Cleaner` to ensure associated native resources are reclaimed when the Java object becomes phantom reachable.\n\n## Binding registration\n\nThe macro `JAVA_EXTENSION_MODULE` expands into a pair of function definitions:\n\n```c\nJNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { ... }\nJNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { ... }\n```\n\nThese definitions, in turn, iterate over the function and field bindings registered with `native_class`, `static_class` and `record_class`.\n\nEach function binding generates a function pointer at compile time, which are passed to the JNI function `RegisterNatives`. Each of these function pointers points at a static member function of a template class, where the template parameters capture the type information extracted from the function signature. When the function is invoked through the pointer, the function makes the appropriate type conversions to cast Java types into C++ types and back. For example, the C++ function signature\n\n```cpp\nbool func(const std::string\u0026 str, const std::vector\u003cint32_t\u003e\u0026 vec, double d);\n```\n\ncauses the function adapter template to be instantiated with parameter types `std::string`, `std::vector\u003cint32_t\u003e` and `double` and return type `bool`. When Java calls the pointer through JNI, the adapter transforms the types `std::string` and `std::vector\u003cint32_t\u003e`. (`double` and `bool` need no transformation.) For each transformed type, a temporary object is created, all of which are then used in invoking the original function `func`.\n\nInternally, field bindings utilize JNI accessor functions like `GetObjectField` and `SetObjectField` to extract and populate Java objects. Like with function bindings, javabind uses C++ type information to make the appropriate JNI function call. For instance, setting a field with type `double` entails a call to `GetDoubleField` (from Java to C++) or `SetDoubleField` (from C++ to Java). If the type is a composite type, such as a `std::vector\u003cT\u003e`, then a Java object is constructed recursively, and then set with `SetObjectField`. For example,\n\n* Setting a field of type `std::vector\u003cboxed\u003cint32_t\u003e\u003e` first creates a `java.util.ArrayList` with JNI's `NewObject`, then sets elements with the `add` method (invoked using JNI's `CallBooleanMethod`), performing boxing for the primitive type `int` with `valueOf`, and finally uses `SetObjectField` with the newly created `java.util.ArrayList` instance.\n* Setting an `std::vector\u003cstd::string\u003e` field involves creating a `java.util.ArrayList` with JNI's `NewObject`, and a call to JNI's `NewStringUTF` for each string element. The strings are then added to the `java.util.ArrayList` instance with `add`, and finally to the field with `SetObjectField`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhunyadi%2Fjavabind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhunyadi%2Fjavabind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhunyadi%2Fjavabind/lists"}