From 4b48a201fd8289f8d5cbaf142d44da051f1167f7 Mon Sep 17 00:00:00 2001 From: Boris Rasin Date: Fri, 22 Mar 2024 01:02:24 +0200 Subject: [PATCH] Initial commit (split from scapix repository) --- .gitignore | 1 + CMakeLists.txt | 72 +++ LICENSE.txt | 89 +++ README.md | 42 ++ source/com/scapix/Bridge.java | 20 + source/com/scapix/Function.java | 18 + source/com/scapix/NativeException.java | 21 + source/scapix/jni/array.h | 506 +++++++++++++++++ source/scapix/jni/byte_buffer.h | 43 ++ source/scapix/jni/cast.h | 50 ++ source/scapix/jni/class.h | 116 ++++ source/scapix/jni/class_loader.h | 62 +++ source/scapix/jni/com/scapix/bridge.h | 216 ++++++++ source/scapix/jni/com/scapix/function.h | 111 ++++ .../scapix/jni/com/scapix/native_exception.h | 114 ++++ source/scapix/jni/convert.h | 508 ++++++++++++++++++ source/scapix/jni/detail/api.h | 17 + source/scapix/jni/detail/api/array.h | 34 ++ source/scapix/jni/detail/api/call.h | 63 +++ source/scapix/jni/detail/api/ref.h | 54 ++ source/scapix/jni/detail/api/string.h | 45 ++ source/scapix/jni/detail/api/type.h | 192 +++++++ source/scapix/jni/detail/exception.cpp | 48 ++ source/scapix/jni/detail/exception.h | 113 ++++ source/scapix/jni/detail/init.h | 34 ++ source/scapix/jni/detail/util.h | 21 + source/scapix/jni/element.h | 54 ++ source/scapix/jni/env.h | 97 ++++ source/scapix/jni/function.h | 39 ++ source/scapix/jni/fwd/array.h | 29 + source/scapix/jni/fwd/byte_buffer.h | 28 + source/scapix/jni/fwd/class.h | 28 + source/scapix/jni/fwd/element.h | 20 + source/scapix/jni/fwd/native_method.h | 23 + source/scapix/jni/fwd/object.h | 28 + source/scapix/jni/fwd/object_base.h | 28 + source/scapix/jni/fwd/ref.h | 22 + source/scapix/jni/fwd/signature.h | 20 + source/scapix/jni/fwd/string.h | 28 + source/scapix/jni/fwd/throwable.h | 28 + source/scapix/jni/local_frame.h | 75 +++ source/scapix/jni/lock.h | 28 + source/scapix/jni/module.h | 49 ++ source/scapix/jni/monitor.h | 85 +++ source/scapix/jni/native_method.h | 201 +++++++ source/scapix/jni/object.h | 126 +++++ source/scapix/jni/object_base.h | 65 +++ source/scapix/jni/object_impl.h | 278 ++++++++++ source/scapix/jni/object_traits.h | 33 ++ source/scapix/jni/ref.h | 433 +++++++++++++++ source/scapix/jni/signature.h | 72 +++ source/scapix/jni/string.h | 184 +++++++ source/scapix/jni/struct.h | 37 ++ source/scapix/jni/throwable.h | 31 ++ source/scapix/jni/type_traits.h | 170 ++++++ source/scapix/jni/vm.h | 39 ++ source/scapix/jni/vm_exception.h | 79 +++ 57 files changed, 5067 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 source/com/scapix/Bridge.java create mode 100644 source/com/scapix/Function.java create mode 100644 source/com/scapix/NativeException.java create mode 100644 source/scapix/jni/array.h create mode 100644 source/scapix/jni/byte_buffer.h create mode 100644 source/scapix/jni/cast.h create mode 100644 source/scapix/jni/class.h create mode 100644 source/scapix/jni/class_loader.h create mode 100644 source/scapix/jni/com/scapix/bridge.h create mode 100644 source/scapix/jni/com/scapix/function.h create mode 100644 source/scapix/jni/com/scapix/native_exception.h create mode 100644 source/scapix/jni/convert.h create mode 100644 source/scapix/jni/detail/api.h create mode 100644 source/scapix/jni/detail/api/array.h create mode 100644 source/scapix/jni/detail/api/call.h create mode 100644 source/scapix/jni/detail/api/ref.h create mode 100644 source/scapix/jni/detail/api/string.h create mode 100644 source/scapix/jni/detail/api/type.h create mode 100644 source/scapix/jni/detail/exception.cpp create mode 100644 source/scapix/jni/detail/exception.h create mode 100644 source/scapix/jni/detail/init.h create mode 100644 source/scapix/jni/detail/util.h create mode 100644 source/scapix/jni/element.h create mode 100644 source/scapix/jni/env.h create mode 100644 source/scapix/jni/function.h create mode 100644 source/scapix/jni/fwd/array.h create mode 100644 source/scapix/jni/fwd/byte_buffer.h create mode 100644 source/scapix/jni/fwd/class.h create mode 100644 source/scapix/jni/fwd/element.h create mode 100644 source/scapix/jni/fwd/native_method.h create mode 100644 source/scapix/jni/fwd/object.h create mode 100644 source/scapix/jni/fwd/object_base.h create mode 100644 source/scapix/jni/fwd/ref.h create mode 100644 source/scapix/jni/fwd/signature.h create mode 100644 source/scapix/jni/fwd/string.h create mode 100644 source/scapix/jni/fwd/throwable.h create mode 100644 source/scapix/jni/local_frame.h create mode 100644 source/scapix/jni/lock.h create mode 100644 source/scapix/jni/module.h create mode 100644 source/scapix/jni/monitor.h create mode 100644 source/scapix/jni/native_method.h create mode 100644 source/scapix/jni/object.h create mode 100644 source/scapix/jni/object_base.h create mode 100644 source/scapix/jni/object_impl.h create mode 100644 source/scapix/jni/object_traits.h create mode 100644 source/scapix/jni/ref.h create mode 100644 source/scapix/jni/signature.h create mode 100644 source/scapix/jni/string.h create mode 100644 source/scapix/jni/struct.h create mode 100644 source/scapix/jni/throwable.h create mode 100644 source/scapix/jni/type_traits.h create mode 100644 source/scapix/jni/vm.h create mode 100644 source/scapix/jni/vm_exception.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..82ba7c7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.14...3.26) + +project(scapix-jni CXX) + +option(SCAPIX_JNI_CACHE_CLASS_LOADER "" ON) +option(SCAPIX_JNI_AUTO_ATTACH_THREAD "" ON) + +message(STATUS "SCAPIX_JNI_CACHE_CLASS_LOADER: ${SCAPIX_JNI_CACHE_CLASS_LOADER}") +message(STATUS "SCAPIX_JNI_AUTO_ATTACH_THREAD: ${SCAPIX_JNI_AUTO_ATTACH_THREAD}") + +file(GLOB_RECURSE sources CONFIGURE_DEPENDS source/*) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/source" PREFIX "source" FILES ${sources}) + +add_library(scapix_jni ${sources} ${java_api_sources}) +add_library(scapix::jni ALIAS scapix_jni) + +# temporary: should be ScapixCore (scapix::core) +set(SCAPIX_BRIDGE "java" CACHE STRING "") +find_package(Scapix REQUIRED) +target_link_libraries(scapix_jni PUBLIC scapix) + +file(GLOB_RECURSE java_api_sources CONFIGURE_DEPENDS ${_SCAPIX_PATH}/java_api/${SCAPIX_JAVA_API}/scapix/java_api/*) +source_group(TREE ${_SCAPIX_PATH}/java_api/${SCAPIX_JAVA_API}/scapix/java_api PREFIX "java_api" FILES ${java_api_sources}) +target_include_directories(scapix_jni PUBLIC ${_SCAPIX_PATH}/java_api/${SCAPIX_JAVA_API}) + +target_include_directories(scapix_jni PUBLIC source) +target_compile_features(scapix_jni PUBLIC cxx_std_20) + +if(SCAPIX_JNI_CACHE_CLASS_LOADER) + target_compile_definitions(scapix_jni PUBLIC SCAPIX_JNI_CACHE_CLASS_LOADER) +endif() + +if(SCAPIX_JNI_AUTO_ATTACH_THREAD) + target_compile_definitions(scapix_jni PUBLIC SCAPIX_JNI_AUTO_ATTACH_THREAD) +endif() + +file(GLOB_RECURSE java_sources CONFIGURE_DEPENDS source/*.java) +target_sources(scapix_jni INTERFACE ${java_sources}) + +if(NOT ANDROID) + # Save CMAKE_FIND_FRAMEWORK + if(DEFINED CMAKE_FIND_FRAMEWORK) + set(SCAPIX_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) + else() + unset(SCAPIX_CMAKE_FIND_FRAMEWORK) + endif() + + set(CMAKE_FIND_FRAMEWORK LAST) + find_package(JNI REQUIRED) + + # Restore CMAKE_FIND_FRAMEWORK + if(DEFINED SCAPIX_CMAKE_FIND_FRAMEWORK) + set(CMAKE_FIND_FRAMEWORK ${SCAPIX_CMAKE_FIND_FRAMEWORK}) + unset(SCAPIX_CMAKE_FIND_FRAMEWORK) + else() + unset(CMAKE_FIND_FRAMEWORK) + endif() + + target_include_directories(scapix_jni PUBLIC ${JNI_INCLUDE_DIRS}) + target_link_libraries(scapix_jni PUBLIC ${JNI_LIBRARIES}) + + find_package(Java REQUIRED COMPONENTS Development) + include(UseJava) + +# file(GLOB_RECURSE java_sources CONFIGURE_DEPENDS source/*.java) + add_jar(scapix_jni_jar SOURCES ${java_sources}) + + add_dependencies(scapix_jni scapix_jni_jar) + + get_target_property(scapix_jni_jar_file scapix_jni_jar JAR_FILE) + target_compile_definitions(scapix_jni PUBLIC SCAPIX_JNI_JAR_FILE="${scapix_jni_jar_file}") +endif() diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8dd69b8 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,89 @@ +Terms and conditions + +This is the Scapix Language Bridge License Agreement + +1. Introduction + +1.1 The Scapix Language Bridge is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Scapix in relation to your use of the Scapix Language Bridge. + +1.2 "Scapix" means Boris Rasin (boris@scapix.com), author of Scapix Language Bridge, residing in Holon, Israel. + +2. Accepting this License Agreement + +2.1 In order to use the Scapix Language Bridge, you must first agree to the License Agreement. You may not use the Scapix Language Bridge if you do not accept the License Agreement. + +2.2 You may not use the Scapix Language Bridge and may not accept the License Agreement if you are a person barred from receiving the Scapix Language Bridge under the laws of the United States or other countries, including the country in which you are resident or from which you use the Scapix Language Bridge. + +2.3 If you are agreeing to be bound by the License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Scapix Language Bridge on behalf of your employer or other entity. + +3. Scapix Language Bridge License from Scapix + +3.1 Subject to the terms of the License Agreement, Scapix grants you a limited, worldwide, royalty-free, non-assignable, non-exclusive, and non-sublicensable license to use the Scapix Language Bridge solely to develop applications. + +3.2 You agree that Scapix or third parties own all legal right, title and interest in and to the Scapix Language Bridge, including any Intellectual Property Rights that subsist in the Scapix Language Bridge. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Scapix reserves all rights not expressly granted to you. + +3.3 You may not use the Scapix Language Bridge for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Scapix Language Bridge or any part of the Scapix Language Bridge. + +3.4 Use, reproduction and distribution of components of the Scapix Language Bridge licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. + +3.5 You agree that the form and nature of the Scapix Language Bridge that Scapix provides may change without prior notice to you and that future versions of the Scapix Language Bridge may be incompatible with applications developed on previous versions of the Scapix Language Bridge. You agree that Scapix may stop (permanently or temporarily) providing the Scapix Language Bridge (or any features within the Scapix Language Bridge) to you or to users generally at Scapix's sole discretion, without prior notice to you. + +3.6 Nothing in the License Agreement gives you a right to use any of Scapix's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.7 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Scapix Language Bridge. + +4. Use of the Scapix Language Bridge by You + +4.1 Scapix agrees that it obtains no right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the Scapix Language Bridge, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the Scapix Language Bridge and write applications only for purposes that are permitted by (a) the License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that you are solely responsible for (and that Scapix has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Scapix or any third party may suffer) of any such breach. + +5. Terminating this License Agreement + +5.1 The License Agreement will continue to apply until terminated by either you or Scapix as set out below. + +5.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the Scapix Language Bridge. + +5.3 Scapix may at any time, terminate the License Agreement with you if: (A) you have breached any provision of the License Agreement; or (B) Scapix is required to do so by law; or (C) the partner with whom Scapix offered certain parts of Scapix Language Bridge (such as APIs) to you has terminated its relationship with Scapix or ceased to offer certain parts of the Scapix Language Bridge to you; or (D) Scapix decides to no longer provide the Scapix Language Bridge or certain parts of the Scapix Language Bridge to users in the country in which you are resident or from which you use the service, or the provision of the Scapix Language Bridge or certain Scapix Language Bridge services to you by Scapix is, in Scapix's sole discretion, no longer commercially viable. + +5.4 When the License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Scapix have benefited from, been subject to (or which have accrued over time whilst the License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 10.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + +6. DISCLAIMER OF WARRANTIES + +6.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SCAPIX LANGUAGE BRIDGE IS AT YOUR SOLE RISK AND THAT THE SCAPIX LANGUAGE BRIDGE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM Scapix. + +6.2 YOUR USE OF THE SCAPIX LANGUAGE BRIDGE AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SCAPIX LANGUAGE BRIDGE IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +6.3 SCAPIX FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + +7. LIMITATION OF LIABILITY + +7.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT SCAPIX, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT SCAPIX OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +8. Indemnification + +8.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Scapix, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the Scapix Language Bridge, (b) any application you develop on the Scapix Language Bridge that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with the License Agreement. + +9. Changes to the License Agreement + +9.1 Scapix may make changes to the License Agreement as it distributes new versions of the Scapix Language Bridge. When these changes are made, Scapix will make a new version of the License Agreement available on the website where the Scapix Language Bridge is made available. + +10. General Legal Terms + +10.1 The License Agreement constitutes the whole legal agreement between you and Scapix and governs your use of the Scapix Language Bridge (excluding any services which Scapix may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Scapix in relation to the Scapix Language Bridge. + +10.2 You agree that if Scapix does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Scapix has the benefit of under any applicable law), this will not be taken to be a formal waiver of Scapix's rights and that those rights or remedies will still be available to Scapix. + +10.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. + +10.4 You acknowledge and agree that each member of the group of companies of which Scapix is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. + +10.5 EXPORT RESTRICTIONS. THE SCAPIX LANGUAGE BRIDGE IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SCAPIX LANGUAGE BRIDGE. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +10.6 The rights granted in the License Agreement may not be assigned or transferred by either you or Scapix without the prior written approval of the other party. Neither you nor Scapix shall be permitted to delegate their responsibilities or obligations under the License Agreement without the prior written approval of the other party. + +10.7 The License Agreement, and your relationship with Scapix under the License Agreement, shall be governed by the laws of the State of Israel. You and Scapix agree to submit to the exclusive jurisdiction of the courts located within the Tel-Aviv District to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Scapix shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + +March 1, 2019 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2506f73 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Scapix JNI + +🇺🇦 If you like this project, please consider donating to one of the funds helping victims of russian aggression against Ukraine: [ukraine.ua](https://war.ukraine.ua/donate/) + +Modern C++20 wrapper for JNI (Java Native Interface): +- type-safe APIs +- automatic resource management +- ZERO runtime overhead compared to manually written JNI code +- automatic C++/Java type conversion for many standard types (std::string, std::vector, etc.) +- automatic C++/Java exception tunneling +- comes with pre-generated C++ headers for all JDK and Android Java APIs +- automatically generate C++ headers for any Java code, including your own + +```cpp +// generated headers for all JDK/Android classes +#include +#include +#include + +using namespace scapix::java_api; + +void test() +{ + // Automatic convertion between common C++ and Java types (std::string, std::vector, std::map, etc) + + std::string version = java::lang::System::getProperty("java.version"); + std::vector languages = java::util::Locale::getISOLanguages(); + std::vector> zone_strings = java::text::DateFormatSymbols::getInstance()->getZoneStrings(); + std::map properties = java::lang::System::getProperties(); +} +``` + +[Documentation](https://www.scapix.com/java_link)\ +[Example](https://github.com/scapix-com/example2) + +## License + +Please carefully read [license agreement](LICENSE.txt). + +**In short:** +If you comply with [license agreement](LICENSE.txt), you may use [Scapix JNI](https://www.scapix.com) free of charge to build commercial and/or open source applications. +You may NOT modify and/or redistribute the [Scapix JNI](https://www.scapix.com) product itself. diff --git a/source/com/scapix/Bridge.java b/source/com/scapix/Bridge.java new file mode 100644 index 0000000..a00a1f3 --- /dev/null +++ b/source/com/scapix/Bridge.java @@ -0,0 +1,20 @@ +/* + com/scapix/Bridge.java + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +package com.scapix; + +public class Bridge +{ + private long ptr; + + @SuppressWarnings({"deprecation","removal"}) + @Override + protected native void finalize(); + + protected static final class Nop {} + protected static final Nop nop = null; + protected Bridge(Nop nop) {} +} diff --git a/source/com/scapix/Function.java b/source/com/scapix/Function.java new file mode 100644 index 0000000..299fe48 --- /dev/null +++ b/source/com/scapix/Function.java @@ -0,0 +1,18 @@ +/* + com/scapix/Function.java + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +package com.scapix; + +public class Function +{ + private long ptr; + + @SuppressWarnings({"deprecation","removal"}) + @Override + protected native void finalize(); + + protected Function(long p) { ptr = p; } +} diff --git a/source/com/scapix/NativeException.java b/source/com/scapix/NativeException.java new file mode 100644 index 0000000..c785bc0 --- /dev/null +++ b/source/com/scapix/NativeException.java @@ -0,0 +1,21 @@ +/* + com/scapix/NativeException.java + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +package com.scapix; + +public final class NativeException extends java.lang.RuntimeException +{ + private long ptr; + + private NativeException(long p) { ptr = p; } + + @SuppressWarnings({"deprecation","removal"}) + @Override + protected native void finalize(); + + @Override + public native String getMessage(); +} diff --git a/source/scapix/jni/array.h b/source/scapix/jni/array.h new file mode 100644 index 0000000..82f5036 --- /dev/null +++ b/source/scapix/jni/array.h @@ -0,0 +1,506 @@ +/* + scapix/jni/array.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_ARRAY_H +#define SCAPIX_JNI_ARRAY_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +class array_base : public object> +{ + using base = object>; + +public: + + jsize size() const { return detail::env()->GetArrayLength(this->handle()); } + +protected: + + array_base(typename base::handle_type h) : base(h) {} + +}; + +// object array + +template +class array : public array_base +{ + using base = array_base; + +public: + + class reference; + class const_reference; + class iterator; + class const_iterator; + + using value_type = ref; + using element_type = typename ref::element_type; + using size_type = jsize; + using difference_type = jsize; + using pointer = void; + using const_pointer = void; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + class reference + { + public: + + // We rely on C++17 guaranteed copy elision. + // The same could be done in C++11 with Copy-List-Initialization (return {handle, size}). + + reference() = delete; + reference(const reference&) = delete; + reference(reference&&) = delete; + + void operator & () = delete; + + reference& operator = (const reference& other) { *this = other.get(); return *this; } + reference& operator = (reference&& other) { *this = other.get(); return *this; } + + template + reference& operator = (const ref& value) { arr.set_element(pos, value); return *this; } + + operator ref() const { return get(); } + ref get() const { return arr.get_element(pos); } + ref operator -> () const { return get(); } + + private: + + friend array; + friend class iterator; + + reference(typename array::handle_type arr, jsize pos) : arr(arr), pos(pos) {} + + array arr; + jsize pos; + + }; + + class iterator + { + public: + + using difference_type = jsize; + using value_type = ref; + using pointer = void; + using reference = array::reference; + using iterator_category = std::random_access_iterator_tag; + + iterator() : pos(0) {} + iterator(const iterator&) = default; + iterator& operator =(const iterator&) = default; + + iterator& operator ++() { ++pos; return *this; } + iterator& operator --() { --pos; return *this; } + + iterator operator ++(int) { iterator temp(*this); ++pos; return temp; } + iterator operator --(int) { iterator temp(*this); --pos; return temp; } + + iterator& operator +=(difference_type d) { pos += d; return *this; } + iterator& operator -=(difference_type d) { pos -= d; return *this; } + + friend iterator operator + (const iterator& i, difference_type n) { iterator temp(i); i += n; return temp; } + friend iterator operator + (difference_type n, const iterator& i) { iterator temp(i); i += n; return temp; } + friend iterator operator - (const iterator& i, difference_type n) { iterator temp(i); i -= n; return temp; } + friend iterator operator - (difference_type n, const iterator& i) { iterator temp(i); i -= n; return temp; } + + reference operator [](difference_type n) const { return *(*this + n); } + + reference operator *() const { return reference(arr.handle(), pos); } + reference operator ->() const { return reference(arr.handle(), pos); } + + friend bool operator == (const iterator& a, const iterator& b) { return a.pos == b.pos; } + friend bool operator != (const iterator& a, const iterator& b) { return a.pos != b.pos; } + friend bool operator > (const iterator& a, const iterator& b) { return a.pos > b.pos; } + friend bool operator >= (const iterator& a, const iterator& b) { return a.pos >= b.pos; } + friend bool operator < (const iterator& a, const iterator& b) { return a.pos < b.pos; } + friend bool operator <= (const iterator& a, const iterator& b) { return a.pos <= b.pos; } + + friend void swap(iterator& a, iterator& b) + { + using std::swap; + swap(a.arr, b.arr); + swap(a.pos, b.pos); + } + + private: + + friend array; + + iterator(typename array::handle_type arr, jsize pos) : arr(arr), pos(pos) {} + + array arr; + jsize pos; + + }; + + class const_reference + { + public: + + // We rely on C++17 guaranteed copy elision. + // The same could be done in C++11 with Copy-List-Initialization (return {handle, size}). + + const_reference() = delete; + const_reference(const const_reference&) = delete; + const_reference(const_reference&&) = delete; + + void operator & () = delete; + + operator ref() const { return get(); } + ref get() const { return arr.get_element(pos); } + ref operator -> () const { return get(); } + + private: + + friend array; + friend class const_iterator; + + const_reference(typename array::handle_type arr, jsize pos) : arr(arr), pos(pos) {} + + array arr; + jsize pos; + + }; + + class const_iterator + { + public: + + using difference_type = jsize; + using value_type = ref; + using pointer = void; + using reference = array::const_reference; + using iterator_category = std::random_access_iterator_tag; + + const_iterator() : pos(0) {} + const_iterator(const const_iterator&) = default; + const_iterator& operator =(const const_iterator&) = default; + + const_iterator& operator ++() { ++pos; return *this; } + const_iterator& operator --() { --pos; return *this; } + + const_iterator operator ++(int) { const_iterator temp(*this); ++pos; return temp; } + const_iterator operator --(int) { const_iterator temp(*this); --pos; return temp; } + + const_iterator& operator +=(difference_type d) { pos += d; return *this; } + const_iterator& operator -=(difference_type d) { pos -= d; return *this; } + + friend const_iterator operator + (const const_iterator& i, difference_type n) { const_iterator temp(i); i += n; return temp; } + friend const_iterator operator + (difference_type n, const const_iterator& i) { const_iterator temp(i); i += n; return temp; } + friend const_iterator operator - (const const_iterator& i, difference_type n) { const_iterator temp(i); i -= n; return temp; } + friend const_iterator operator - (difference_type n, const const_iterator& i) { const_iterator temp(i); i -= n; return temp; } + + reference operator [](difference_type n) const { return *(*this + n); } + + reference operator *() const { return reference(arr.handle(), pos); } + reference operator ->() const { return reference(arr.handle(), pos); } + + friend bool operator == (const const_iterator& a, const const_iterator& b) { return a.pos == b.pos; } + friend bool operator != (const const_iterator& a, const const_iterator& b) { return a.pos != b.pos; } + friend bool operator > (const const_iterator& a, const const_iterator& b) { return a.pos > b.pos; } + friend bool operator >= (const const_iterator& a, const const_iterator& b) { return a.pos >= b.pos; } + friend bool operator < (const const_iterator& a, const const_iterator& b) { return a.pos < b.pos; } + friend bool operator <= (const const_iterator& a, const const_iterator& b) { return a.pos <= b.pos; } + + friend void swap(const_iterator& a, const_iterator& b) + { + using std::swap; + swap(a.arr, b.arr); + swap(a.pos, b.pos); + } + + private: + + friend array; + + const_iterator(typename array::handle_type arr, jsize pos) : arr(arr), pos(pos) {} + + array arr; + jsize pos; + + }; + + bool empty() const { return this->size() == 0; } + + iterator begin() { return iterator(this->handle(), 0); } + const_iterator begin() const { return const_iterator(this->handle(), 0); } + const_iterator cbegin() const { return begin(); } + + iterator end() { return iterator(this->handle(), this->size()); } + const_iterator end() const { return const_iterator(this->handle(), this->size()); } + const_iterator cend() const { return end(); } + + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const { return rbegin(); } + + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const { return rend(); } + + reference operator[](size_type pos) { return reference(this->handle(), pos); } + const_reference operator[](size_type pos) const { return const_reference(this->handle(), pos); } + + reference at(size_type pos) + { + if (pos >= this->size()) + throw std::out_of_range("array_elements"); + + return (*this)[pos]; + } + + const_reference at(size_type pos) const + { + if (pos >= this->size()) + throw std::out_of_range("array_elements"); + + return (*this)[pos]; + } + + static ref new_object(jsize len, ref init = {}) + { + return detail::check_result(detail::env()->NewObjectArray(len, object_impl_t::class_object().handle(), init.handle())); + } + + // for consistency with primitive array + + array& elements() { return *this; } + const array& elements() const { return *this; } + +protected: + + array(typename base::handle_type h) : base(h) {} + +private: + + ref get_element(jsize index) const + { + jobject element = detail::env()->GetObjectArrayElement(this->handle(), index); + detail::check_exception(); + return local_ref(element); + } + + void set_element(jsize index, ref value) + { + detail::env()->SetObjectArrayElement(this->handle(), index, value.handle()); + detail::check_exception(); + } + +}; + +// primitive array + +template +class array_elements +{ +public: + + typedef T value_type; + typedef jsize size_type; + typedef jsize difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T* iterator; + typedef const T* const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + // We rely on C++17 guaranteed copy elision. + // The same could be done in C++11 with Copy-List-Initialization (return {handle, size}). + + array_elements(const array_elements&) = delete; + array_elements& operator=(const array_elements&) = delete; + + array_elements(array_elements&&) = delete; + array_elements& operator=(array_elements&&) = delete; + + ~array_elements() + { + if (data_) + release(static_cast(Mode)); + } + + pointer data() { return data_; } + const_pointer data() const { return data_; } + + size_type size() const { return size_; } + bool empty() const { return size() == 0; } + + iterator begin() { return data(); } + const_iterator begin() const { return data(); } + const_iterator cbegin() const { return data(); } + + iterator end() { return data() + size(); } + const_iterator end() const { return data() + size(); } + const_iterator cend() const { return data() + size(); } + + reverse_iterator rbegin() { return data() + size(); } + const_reverse_iterator rbegin() const { return data() + size(); } + const_reverse_iterator crbegin() const { return data() + size(); } + + reverse_iterator rend() { return data(); } + const_reverse_iterator rend() const { return data(); } + const_reverse_iterator crend() const { return data(); } + + reference operator[](size_type pos) { return data()[pos]; } + const_reference operator[](size_type pos) const { return data()[pos]; } + + reference at(size_type pos) + { + if (pos >= size()) + throw std::out_of_range("array_elements"); + + return (*this)[pos]; + } + + const_reference at(size_type pos) const + { + if (pos >= size()) + throw std::out_of_range("array_elements"); + + return (*this)[pos]; + } + + jboolean is_copy() const + { + return is_copy_; + } + + void commit() + { + release(JNI_COMMIT); + } + + // After a call to release() or abort() object is in a valid but unspecified state: + // only functions without preconditions are safe to call (like an assignment operator or destructor). + + void release() + { + if (data_) + { + release(0 /* JNI_COPY */); + data_ = 0; + } + } + + void abort() const + { + if (data_) + { + release(JNI_ABORT); + data_ = 0; + } + } + +private: + + friend class array; + + using handle_type = typename array::handle_type; + + array_elements(handle_type array, jsize size) : + array_(array), + data_(detail::check_result(detail::api::array::get_array_elements(array_, &is_copy_))), + size_(size) + { + } + + void release(jint mode) + { + detail::api::array::release_array_elements(array_, data_, mode); + } + + handle_type array_; + jboolean is_copy_; + pointer data_; + size_type size_; + +}; + +template +class array : public array_base +{ + using base = array_base; + +public: + + static ref new_object(jsize len) + { + return detail::check_result(detail::api::type::new_array(len)); + } + + template + auto elements() + { + return array_elements(this->handle(), this->size()); + } + + template + auto elements(jsize size) + { + return array_elements(this->handle(), size); + } + + template + const auto elements() const + { + return array_elements(this->handle(), this->size()); + } + + template + const auto elements(jsize size) const + { + return array_elements(this->handle(), size); + } + + template + const auto const_elements() const + { + return array_elements(this->handle(), this->size()); + } + + template + const auto const_elements(jsize size) const + { + return array_elements(this->handle(), size); + } + + void get_region(jsize start, jsize len, T* buf) const + { + detail::api::type::get_array_region(this->handle(), start, len, buf); + detail::check_exception(); + } + + void set_region(jsize start, jsize len, const T* buf) + { + detail::api::type::set_array_region(this->handle(), start, len, buf); + detail::check_exception(); + } + +protected: + + array(typename base::handle_type h) : base(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_ARRAY_H diff --git a/source/scapix/jni/byte_buffer.h b/source/scapix/jni/byte_buffer.h new file mode 100644 index 0000000..177d0d4 --- /dev/null +++ b/source/scapix/jni/byte_buffer.h @@ -0,0 +1,43 @@ +/* + scapix/jni/byte_buffer.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_BYTE_BUFFER_H +#define SCAPIX_JNI_BYTE_BUFFER_H + +#include + +namespace scapix::jni { + +class byte_buffer : public object<"java/nio/ByteBuffer"> +{ +public: + + static local_ref new_direct(void* address, jlong capacity) + { + jobject buf = detail::env()->NewDirectByteBuffer(address, capacity); + detail::check_exception(); + return local_ref(buf); + } + + void* direct_address() const + { + return detail::env()->GetDirectBufferAddress(handle()); + } + + jlong direct_capacity() const + { + return detail::env()->GetDirectBufferCapacity(handle()); + } + +protected: + + byte_buffer(handle_type h) : object(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_BYTE_BUFFER_H diff --git a/source/scapix/jni/cast.h b/source/scapix/jni/cast.h new file mode 100644 index 0000000..8125601 --- /dev/null +++ b/source/scapix/jni/cast.h @@ -0,0 +1,50 @@ +/* + scapix/jni/cast.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_CAST_H +#define SCAPIX_JNI_CAST_H + +#include +#include + +namespace scapix::jni { + +template +inline ref static_pointer_cast(const ref& r) +{ + return ref(r.handle()); +} + +template +inline ref static_pointer_cast(ref&& r) +{ + if constexpr (Scope == scope::generic) + return ref(r.release(), r.get_scope()); + else + return ref(r.release()); +} + +template +inline ref dynamic_pointer_cast(const ref& r) +{ + if (ref<>(r)->template is_instance_of()) + return static_pointer_cast(r); + + return nullptr; +} + +template +inline ref dynamic_pointer_cast(ref&& r) +{ + if (ref<>(r)->template is_instance_of()) + return static_pointer_cast(std::move(r)); + + return nullptr; +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_CAST_H diff --git a/source/scapix/jni/class.h b/source/scapix/jni/class.h new file mode 100644 index 0000000..29e66fd --- /dev/null +++ b/source/scapix/jni/class.h @@ -0,0 +1,116 @@ +/* + scapix/jni/class.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +// outside of include guard +#include + +#ifndef SCAPIX_JNI_CLASS_H +#define SCAPIX_JNI_CLASS_H + +#include +#include +#include + +namespace scapix::jni { + +class class_ : public object<"java/lang/Class"> +{ +public: + + static local_ref define_class(const char* name, ref> loader, const jbyte* buf, jsize size) + { + return detail::check_result(detail::env()->DefineClass(name, loader.handle(), buf, size)); + } + + static local_ref find_class(const char* name) + { + return detail::check_result(detail::env()->FindClass(name)); + } + + local_ref get_super_class() const + { + return detail::check_result(detail::env()->GetSuperclass(handle())); + } + + jboolean is_assignable_from(ref source) const noexcept + { + return detail::env()->IsAssignableFrom(source.handle(), handle()); + } + + jfieldID get_field_id(const char* name, const char* sig) const + { + return detail::check_result(detail::env()->GetFieldID(handle(), name, sig)); + } + + jfieldID get_static_field_id(const char* name, const char* sig) const + { + return detail::check_result(detail::env()->GetStaticFieldID(handle(), name, sig)); + } + + jmethodID get_method_id(const char* name, const char* sig) const + { + return detail::check_result(detail::env()->GetMethodID(handle(), name, sig)); + } + + jmethodID get_static_method_id(const char* name, const char* sig) const + { + return detail::check_result(detail::env()->GetStaticMethodID(handle(), name, sig)); + } + + void register_natives(const JNINativeMethod* methods, jint count) const + { + detail::check_result(detail::env()->RegisterNatives(handle(), methods, count)); + } + + template + void register_natives(const JNINativeMethod(&methods)[N]) const + { + register_natives(methods, N); + } + + jint unregister_natives() const + { + return detail::env()->UnregisterNatives(handle()); + } + + jint throw_new(const char* message) const + { + return detail::env()->ThrowNew(handle(), message); + } + + static jmethodID from_reflected_method(ref> method) + { + return detail::env()->FromReflectedMethod(method.handle()); + } + + static jfieldID from_reflected_field(ref> field) + { + return detail::env()->FromReflectedField(field.handle()); + } + + ref> to_reflected_method(jmethodID id, bool is_static) + { + jobject method = detail::env()->ToReflectedMethod(handle(), id, is_static); + detail::check_exception(); + return local_ref>(method); + } + + ref> to_reflected_field(jfieldID id, bool is_static) + { + jobject field = detail::env()->ToReflectedField(handle(), id, is_static); + detail::check_exception(); + return local_ref>(field); + } + +protected: + + class_(handle_type h) : object(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_CLASS_H diff --git a/source/scapix/jni/class_loader.h b/source/scapix/jni/class_loader.h new file mode 100644 index 0000000..40a6779 --- /dev/null +++ b/source/scapix/jni/class_loader.h @@ -0,0 +1,62 @@ +/* + scapix/jni/class_loader.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +// outside of include guard +#include + +#ifndef SCAPIX_JNI_CLASS_LOADER_H +#define SCAPIX_JNI_CLASS_LOADER_H + +#include +#include + +namespace scapix::jni { + +/* + +In some JVM scenarios (notably on Android), JNI FindClass() fails to load application classes when called from C++ created threads. +More information here: [Why didn’t FindClass find my class?](https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class). +This implements the option "Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly." + +*/ + +template<> +inline ref object_impl<"java/lang/Thread">::class_object() +{ + static const static_global_ref cls(class_::find_class(class_name)); + return cls; +} + +template<> +inline ref object_impl<"java/lang/ClassLoader">::class_object() +{ + static const static_global_ref cls(class_::find_class(class_name)); + return cls; +} + +class class_loader +{ +public: + + static void init() + { + loader = object<"java/lang/Thread">::call_static_method<"currentThread", ref>()>()->call_method<"getContextClassLoader", ref>()>(); + } + + static local_ref find_class(const char* name) + { + return loader->call_method<"loadClass", ref(ref)>(new_object(name)); + } + +private: + + inline static static_global_ref> loader; + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_CLASS_LOADER_H diff --git a/source/scapix/jni/com/scapix/bridge.h b/source/scapix/jni/com/scapix/bridge.h new file mode 100644 index 0000000..696816b --- /dev/null +++ b/source/scapix/jni/com/scapix/bridge.h @@ -0,0 +1,216 @@ +/* + scapix/jni/com/scapix/bridge.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_COM_SCAPIX_BRIDGE_H +#define SCAPIX_JNI_COM_SCAPIX_BRIDGE_H + +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { +namespace com::scapix { + +namespace cpp { class object_base; } + +class bridge_; + +class bridge : public object<"com/scapix/Bridge"> +{ +public: + + using nop = object<"com/scapix/Bridge$Nop">; + + void set_ptr(cpp::object_base* p) { set_field<"ptr", jlong>(reinterpret_cast(p)); } + cpp::object_base* get_ptr() const { return reinterpret_cast(get_field<"ptr", jlong>()); } + +protected: + + bridge(handle_type h) : object(h) {} + +}; + +template +class bridge_object : public object +{ + using base = object; + +public: + + static local_ref new_object() + { + return jni::new_object)>(nullptr); + } + + T* get_ptr() const { return static_cast(base::get_ptr()); } + +protected: + + bridge_object(typename base::handle_type h) : base(h) {} + +}; + +namespace cpp { + +class object_base +{ +protected: + + object_base() = default; + object_base(const object_base&) {} + object_base(object_base&&) = default; + object_base& operator =(const object_base&) { return *this; } + object_base& operator =(object_base&&) = default; + +private: + + template + friend class init; + + template + friend struct jni::convert_shared; + + friend class com::scapix::bridge_; + + void attach(ref obj, std::shared_ptr shared_this) + { + assert(!wrapper); + assert(!self); + + wrapper = std::move(obj); + self = std::move(shared_this); + + wrapper->set_ptr(this); + } + + // to do: with indirect inheritance support, + // wrappers should depend on actual object type. + + template + local_ref> get_ref(std::shared_ptr shared_this) + { + local_ref> local(static_pointer_cast>(wrapper)); + + if (!local) + { + local = new_object>(); + attach(local, std::move(shared_this)); + } + + return local; + } + + const std::shared_ptr& scapix_shared() + { + assert(self); + return self; + } + + void finalize() + { + wrapper.reset(); + self.reset(); // might destroy this object + } + + weak_ref wrapper; + std::shared_ptr self; + +}; + +// to do: inheritance should be private + +template +class object : public object_base +{ +protected: + + object() = default; + object(const object&) = default; + object(object&&) = default; + object& operator =(const object&) = default; + object& operator =(object&&) = default; + +}; + +template +class init +{ +public: + + using type = T; + + init(ref&& wrapper) : wrapper(std::move(wrapper)) {} + + template + void create(Args... args) + { + std::shared_ptr obj = std::make_shared(std::forward(args)...); + object_base* ptr = obj.get(); + ptr->attach(std::move(wrapper), std::move(obj)); + } + +private: + + ref wrapper; + +}; + +} // namespace cpp + +class bridge_ +{ +public: + + using native_methods = jni::native_methods + < + bridge::class_name, + native_method<"finalize", void(), void(cpp::object_base::*)(), &cpp::object_base::finalize> + >; + +}; + +} // namespace com::scapix + +template T, fixed_string ClassName> +T convert_this(ref> x) +{ + return T(std::move(ref>(x))); +} + +template T, fixed_string ClassName> +T& convert_this(ref> x) +{ + return *ref>(x)->get_ptr(); +} + +template +struct convert_shared, T, std::enable_if_t>> +{ + static std::shared_ptr cpp(ref::class_name, T>> v) + { + if (!v) + return nullptr; + + return std::static_pointer_cast(v->get_ptr()->scapix_shared()); + } + + static ref::class_name, T>> jni(std::shared_ptr v) + { + if (!v) + return nullptr; + + auto p = v.get(); + return p->template get_ref::class_name>(std::move(v)); + } +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_COM_SCAPIX_BRIDGE_H diff --git a/source/scapix/jni/com/scapix/function.h b/source/scapix/jni/com/scapix/function.h new file mode 100644 index 0000000..f280b49 --- /dev/null +++ b/source/scapix/jni/com/scapix/function.h @@ -0,0 +1,111 @@ +/* + scapix/jni/com/scapix/function.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_COM_SCAPIX_FUNCTION_H +#define SCAPIX_JNI_COM_SCAPIX_FUNCTION_H + +#include +#include +#include +#include +#include + +namespace scapix::jni { +namespace com::scapix { +namespace cpp { + +class function +{ +public: + + virtual ~function() = default; + + void finalize() + { + delete this; + } + +}; + +template +class function_impl; + +template +class function_impl : public function +{ +public: + + function_impl(std::function&& f) : func(std::move(f)) {} + + R call(Args... args) + { + return func(std::forward(args)...); + } + +private: + + std::function func; + +}; + +} // namespace cpp + +class function : public object<"com/scapix/Function"> +{ +public: + + using native_methods = jni::native_methods + < + class_name, + native_method<"finalize", void(), void(cpp::function::*)(), &cpp::function::finalize> + >; + + cpp::function* get_ptr() const { return reinterpret_cast(get_field<"ptr", jlong>()); } + +protected: + + function(handle_type h) : object(h) {} + +}; + +template +class function_impl; + +template +class function_impl : public object> +{ + using base = object>; + +public: + + template + static local_ref new_object(std::function&& func) + { + cpp::function* ptr = new cpp::function_impl(std::move(func)); + return jni::new_object(reinterpret_cast(ptr)); + } + +protected: + + function_impl(typename base::handle_type h) : base(h) {} + +}; + +} // namespace com::scapix + +template T, typename J> +T& convert_this(ref x) +{ + return *static_cast(static_pointer_cast(x)->get_ptr()); +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_COM_SCAPIX_FUNCTION_H diff --git a/source/scapix/jni/com/scapix/native_exception.h b/source/scapix/jni/com/scapix/native_exception.h new file mode 100644 index 0000000..3d39593 --- /dev/null +++ b/source/scapix/jni/com/scapix/native_exception.h @@ -0,0 +1,114 @@ +/* + scapix/jni/com/scapix/native_exception.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_COM_SCAPIX_NATIVE_EXCEPTION_H +#define SCAPIX_JNI_COM_SCAPIX_NATIVE_EXCEPTION_H + +#include +#include +#include +#include +#include +#include + +#if __has_include() +#include +#endif + +namespace scapix::jni { +namespace com::scapix { +namespace cpp { + +class native_exception final +{ +public: + + [[noreturn]] void rethrow() const + { + std::rethrow_exception(ptr); + } + + void finalize() + { + delete this; + } + + ref message() const + { + try + { + std::rethrow_exception(ptr); + } + catch (const std::exception& e) + { + return new_object(e.what()); + } + catch (...) + { + #if __has_include() + int status; + auto name = abi::__cxa_current_exception_type()->name(); + auto demangled_name = abi::__cxa_demangle(name, nullptr, nullptr, &status); + auto ret = new_object(demangled_name); + free(demangled_name); + return ret; + #else + return new_object("unknown"); + #endif + } + } + +private: + + std::exception_ptr ptr = std::current_exception(); + +}; + +} // namespace cpp + +class native_exception : public object<"com/scapix/NativeException", throwable> +{ +public: + + using native_methods = jni::native_methods + < + class_name, + native_method<"finalize", void(), void(cpp::native_exception::*)(), &cpp::native_exception::finalize>, + native_method<"getMessage", ref(), ref(cpp::native_exception::*)() const, &cpp::native_exception::message> + >; + + cpp::native_exception* cpp() const + { + return reinterpret_cast(get_field<"ptr", jlong>()); + } + + static local_ref new_object() + { + return jni::new_object(reinterpret_cast(new cpp::native_exception)); + } + + [[noreturn]] void rethrow() const + { + cpp()->rethrow(); + } + +protected: + + native_exception(handle_type h) : object(h) {} + +}; + +} // namespace com::scapix + +template T> +com::scapix::cpp::native_exception& convert_this(ref v) +{ + return *v->cpp(); +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_COM_SCAPIX_NATIVE_EXCEPTION_H diff --git a/source/scapix/jni/convert.h b/source/scapix/jni/convert.h new file mode 100644 index 0000000..b091a47 --- /dev/null +++ b/source/scapix/jni/convert.h @@ -0,0 +1,508 @@ +/* + scapix/jni/convert.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_CONVERT_H +#define SCAPIX_JNI_CONVERT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +struct convert_shared; + +template +using has_convert_shared_t = decltype(convert_shared::jni(std::declval>())); + +template +struct convert>> +{ + static_assert((std::is_integral_v && std::is_integral_v && sizeof(Jni) == sizeof(Cpp)) || std::is_same_v); + + static Cpp cpp(Jni v) + { + return v; + } + + static Jni jni(Cpp v) + { + return v; + } +}; + +template +struct convert>> +{ + static_assert(std::is_integral_v && sizeof(Jni) == sizeof(Cpp)); + + static Cpp cpp(Jni value) + { + return static_cast(value); + } + + static Jni jni(Cpp value) + { + return static_cast(value); + } +}; + +template +struct convert, Cpp, std::enable_if_t>> +{ + using underlying = std::underlying_type_t; + + static Cpp cpp(ref value) + { + return static_cast(convert_cpp(value)); + } + + static ref jni(Cpp value) + { + return convert_jni>(static_cast(value)); + } +}; + +template +struct convert> +{ + static std::shared_ptr cpp(Jni value) + { + if constexpr (std::experimental::is_detected_v) + return convert_shared::cpp(value); + else + return std::make_shared(convert_cpp(value)); + } + + static Jni jni(std::shared_ptr value) + { + if constexpr (std::experimental::is_detected_v) + return convert_shared::jni(value); + else + return convert_jni(*value); + } +}; + +struct convert_string +{ + using charset = object<"java/nio/charset/Charset">; + using standard_charsets = object<"java/nio/charset/StandardCharsets">; + + static ref utf8_charset() + { + static const static_global_ref ch = standard_charsets::get_static_field<"UTF_8", ref>(); + return ch; + } + + // C->Java conversion is fixed on Android 6 (https://android-review.googlesource.com/#/c/130121/) + + static std::string cpp(ref obj) + { +// std::string str(obj->length(), char()); +// obj->get_region(0, static_cast(str.size()), str.data()); +// return str; + + auto bytes = obj->call_method<"getBytes", ref(ref)>(utf8_charset()); + std::string str(bytes->size(), char()); + bytes->get_region(0, static_cast(str.size()), (jbyte*)str.data()); + + return str; + } + + static ref jni(std::string_view str) + { +// return new_object(str.data()); + + auto bytes = new_object>(static_cast(str.size())); + bytes->set_region(0, static_cast(str.size()), (const jbyte*)str.data()); + + return new_object, ref)>(bytes, utf8_charset()); + } +}; + +template +struct convert, Cpp, std::enable_if_t && std::is_convertible_v && !is_ref_v>> : convert_string +{ +}; + +template +struct convert_primitive_object +{ + static CppPrimitive cpp(ref obj) + { + return obj->template call_method(); + } + + static ref jni(CppPrimitive value) + { + return Object::template call_static_method<"valueOf", ref(JniPrimitive)>(value); + } +}; + +using java_lang_boolean = object<"java/lang/Boolean">; +template struct convert, bool, std::enable_if_t>> : +convert_primitive_object {}; + +using java_lang_byte = object<"java/lang/Byte">; + +template +struct convert, Cpp, std::enable_if_t && std::is_integral_v && sizeof(Cpp) == sizeof(std::int8_t)>> : +convert_primitive_object {}; + +using java_lang_short = object<"java/lang/Short">; + +template +struct convert, Cpp, std::enable_if_t && std::is_integral_v && sizeof(Cpp) == sizeof(std::int16_t)>> : +convert_primitive_object {}; + +using java_lang_integer = object<"java/lang/Integer">; + +template +struct convert, Cpp, std::enable_if_t && std::is_integral_v && sizeof(Cpp) == sizeof(std::int32_t)>> : +convert_primitive_object {}; + +using java_lang_long = object<"java/lang/Long">; + +template +struct convert, Cpp, std::enable_if_t && std::is_integral_v && sizeof(Cpp) == sizeof(std::int64_t)>> : +convert_primitive_object {}; + +using java_lang_float = object<"java/lang/Float">; +template struct convert, float, std::enable_if_t>> : +convert_primitive_object {}; + +using java_lang_double = object<"java/lang/Double">; +template struct convert, double, std::enable_if_t>> : +convert_primitive_object {}; + +template +struct convert>, std::vector> +{ + static_assert(sizeof(J) == sizeof(T)); + + static std::vector cpp(ref> a) + { + const auto size = a->size(); + std::vector v(size); + a->get_region(0, size, reinterpret_cast(v.data())); + return v; + } + + static ref> jni(const std::vector& v) + { + const auto size = static_cast(v.size()); + auto a = new_object>(size); + a->set_region(0, size, reinterpret_cast(v.data())); + return a; + } +}; + +template +struct convert>, std::vector> +{ + static std::vector cpp(ref> a) + { + auto e = a->const_elements(); + return std::vector(e.begin(), e.end()); + } + + static ref> jni(const std::vector& v) + { + const auto size = static_cast(v.size()); + auto a = new_object>(size); + auto e = a->elements(size); + std::copy(v.begin(), v.end(), e.begin()); + return a; + } +}; + +template +struct convert>, std::vector> +{ + static std::vector cpp(ref> a) + { + const auto size = a->size(); + std::vector v(size); + + for (jsize i = 0; i < size; ++i) + v[i] = convert_cpp(a[i].get()); + + return v; + } + + static ref> jni(const std::vector& v) + { + const auto size = static_cast(v.size()); + auto a = new_object>(size); + + for (jsize i = 0; i < size; ++i) + a[i] = convert_jni>(v[i]); + + return a; + } +}; + +using java_util_treemap = object<"java/util/TreeMap">; + +template +struct convert>, std::map> +{ + static std::map cpp(ref tm) + { + std::map m; + + auto set = tm->call_method<"entrySet", ref>()>(); + auto i = set->call_method<"iterator", ref>()>(); + + while (i->call_method<"hasNext", jboolean()>()) + { + auto entry = i->call_method<"next", ref>>()>(); + + m.emplace_hint + ( + m.end(), + convert_cpp(entry->call_method<"getKey", ref>()>()), + convert_cpp(entry->call_method<"getValue", ref>()>()) + ); + } + + return m; + } + + static ref jni(const std::map& m) + { + auto tm = new_object(); + + for (auto& i : m) + tm->call_method<"put", ref>(ref>, ref>)>(convert_jni>(i.first), convert_jni>(i.second)); + + return tm; + } +}; + +// currently used only manually, not in generated bindings + +using java_util_map = object<"java/util/Map">; + +template +struct convert>, std::map> +{ + static std::map cpp(ref tm) + { + std::map m; + + auto set = tm->call_method<"entrySet", ref>()>(); + auto i = set->call_method<"iterator", ref>()>(); + + while (i->call_method<"hasNext", jboolean()>()) + { + auto entry = i->call_method<"next", ref>>()>(); + + m.emplace_hint + ( + m.end(), + convert_cpp(entry->call_method<"getKey", ref>()>()), + convert_cpp(entry->call_method<"getValue", ref>()>()) + ); + } + + return m; + } + + static ref jni(const std::map& m) + { + auto tm = new_object(); + + for (auto& i : m) + tm->call_method<"put", ref>(ref>, ref>)>(convert_jni>(i.first), convert_jni>(i.second)); + + return static_pointer_cast(std::move(tm)); + } +}; + +using java_util_treeset = object<"java/util/TreeSet">; + +template +struct convert>, std::set> +{ + static std::set cpp(ref set) + { + std::set m; + + auto i = set->call_method<"iterator", ref>()>(); + + while (i->call_method<"hasNext", jboolean()>()) + { + m.emplace_hint + ( + m.end(), + convert_cpp(i->call_method<"next", ref>()>()) + ); + } + + return m; + } + + static ref jni(const std::set& s) + { + auto set = new_object(); + + for (auto& i : s) + set->call_method<"add", jboolean(ref>)>(convert_jni>(i)); + + return set; + } +}; + +using java_util_hashmap = object<"java/util/HashMap">; + +template +struct convert>, std::unordered_map> +{ + static std::unordered_map cpp(ref tm) + { + std::unordered_map m; + + auto set = tm->call_method<"entrySet", ref>()>(); + auto i = set->call_method<"iterator", ref>()>(); + + while (i->call_method<"hasNext", jboolean()>()) + { + auto entry = i->call_method<"next", ref>>()>(); + + m.emplace_hint + ( + m.end(), + convert_cpp(entry->call_method<"getKey", ref>()>()), + convert_cpp(entry->call_method<"getValue", ref>()>()) + ); + } + + return m; + } + + static ref jni(const std::unordered_map& m) + { + auto tm = new_object(); + + for (auto& i : m) + tm->call_method<"put", ref>(ref>, ref>)>(convert_jni>(i.first), convert_jni>(i.second)); + + return tm; + } +}; + +using java_util_hashset = object<"java/util/HashSet">; + +template +struct convert>, std::unordered_set> +{ + static std::unordered_set cpp(ref set) + { + std::unordered_set m; + + auto i = set->call_method<"iterator", ref>()>(); + + while (i->call_method<"hasNext", jboolean()>()) + { + m.emplace_hint + ( + m.end(), + convert_cpp(i->call_method<"next", ref>()>()) + ); + } + + return m; + } + + static ref jni(const std::unordered_set& s) + { + auto set = new_object(); + + for (auto& i : s) + set->call_method<"add", jboolean(ref>)>(convert_jni>(i)); + + return set; + } +}; + +template +struct convert>, std::function> +{ + static std::function cpp(ref> i) + { + if (!i) + return nullptr; + + return std::function([i = global_ref>(i)](Args&&... args) + { + if constexpr (std::is_void_v) + return i->call(convert_jni(std::forward(args))...); + else + return convert_cpp(i->call(convert_jni(std::forward(args))...)); + }); + } + + static ref> jni(std::function f) + { + if (!f) + return nullptr; + + return new_object>(std::move(f)); + } +}; + +template +struct convert>> +{ + using struct_object = object::class_name>; + using fields = typename struct_::fields; + + static ref jni(const Struct& value) + { + auto obj = new_object(); + + meta::for_each([&](auto f) + { + using field = decltype(f); + obj->template set_field(convert_jni(value.*field::ptr)); + }); + + return obj; + } + + static Struct cpp(ref value) + { + Struct obj; + + meta::for_each([&](auto f) + { + using field = decltype(f); + obj.*field::ptr = convert_cpp(value->template get_field()); + }); + + return obj; + } +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_CONVERT_H diff --git a/source/scapix/jni/detail/api.h b/source/scapix/jni/detail/api.h new file mode 100644 index 0000000..fae5471 --- /dev/null +++ b/source/scapix/jni/detail/api.h @@ -0,0 +1,17 @@ +/* + scapix/jni/detail/api.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_H +#define SCAPIX_JNI_DETAIL_API_H + +#include +#include +#include +#include +#include +#include + +#endif // SCAPIX_JNI_DETAIL_API_H diff --git a/source/scapix/jni/detail/api/array.h b/source/scapix/jni/detail/api/array.h new file mode 100644 index 0000000..2ff98d6 --- /dev/null +++ b/source/scapix/jni/detail/api/array.h @@ -0,0 +1,34 @@ +/* + scapix/jni/detail/api/array.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_ARRAY_H +#define SCAPIX_JNI_DETAIL_API_ARRAY_H + +#include +#include + +namespace scapix::jni::detail::api { + +template +struct array; + +template +struct array +{ + static T* get_array_elements(handle_type_t obj, jboolean* is_copy) { return type::get_array_elements(obj, is_copy); } + static void release_array_elements(handle_type_t obj, T* elems, jint mode) { type::release_array_elements(obj, elems, mode); } +}; + +template +struct array +{ + static T* get_array_elements(handle_type_t obj, jboolean* is_copy) { return static_cast(env()->GetPrimitiveArrayCritical(obj, is_copy)); } + static void release_array_elements(handle_type_t obj, T* elems, jint mode) { env()->ReleasePrimitiveArrayCritical(obj, elems, mode); } +}; + +} // namespace scapix::jni::detail::api + +#endif // SCAPIX_JNI_DETAIL_API_ARRAY_H diff --git a/source/scapix/jni/detail/api/call.h b/source/scapix/jni/detail/api/call.h new file mode 100644 index 0000000..f7dcde8 --- /dev/null +++ b/source/scapix/jni/detail/api/call.h @@ -0,0 +1,63 @@ +/* + scapix/jni/detail/api/call.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_CALL_H +#define SCAPIX_JNI_DETAIL_API_CALL_H + +#include +#include +#include + +namespace scapix::jni::detail::api { + +template +struct call; + +template +struct call +{ + static R method(jobject obj, jmethodID id, Args... args) + { + check_exception_nested_on_destroy check; + return type::call_method(obj, id, arg(args)...); + } + + static R nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) + { + check_exception_nested_on_destroy check; + return type::call_nonvirtual_method(obj, cls, id, arg(args)...); + } + + static R static_method(jclass cls, jmethodID id, Args... args) + { + check_exception_nested_on_destroy check; + return type::call_static_method(cls, id, arg(args)...); + } + + template + static local_ref new_object(jclass cls, jmethodID id, Args... args) + { + return check_result_nested(env()->NewObject(cls, id, arg(args)...)); + } + +private: + + template + static jobject arg(const jni::ref& v) { return v.handle(); } + + static jboolean arg(jboolean v) { return v; } + static jbyte arg(jbyte v) { return v; } + static jchar arg(jchar v) { return v; } + static jshort arg(jshort v) { return v; } + static jint arg(jint v) { return v; } + static jlong arg(jlong v) { return v; } + static jfloat arg(jfloat v) { return v; } + static jdouble arg(jdouble v) { return v; } +}; + +} // namespace scapix::jni::detail::api + +#endif // SCAPIX_JNI_DETAIL_API_CALL_H diff --git a/source/scapix/jni/detail/api/ref.h b/source/scapix/jni/detail/api/ref.h new file mode 100644 index 0000000..0848401 --- /dev/null +++ b/source/scapix/jni/detail/api/ref.h @@ -0,0 +1,54 @@ +/* + scapix/jni/detail/api/ref.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_REF_H +#define SCAPIX_JNI_DETAIL_API_REF_H + +#include + +namespace scapix::jni::detail::api { + +enum class scope +{ + generic, + local, + global, + weak +}; + +template +struct ref; + +template <> +struct ref +{ + static jobject new_ref(jobject h) noexcept { return detail::env()->NewLocalRef(h); } + static void delete_ref(jobject h) noexcept { detail::env()->DeleteLocalRef(h); } +}; + +template <> +struct ref +{ + static jobject new_ref(jobject h) noexcept { return detail::env()->NewGlobalRef(h); } + static void delete_ref(jobject h) noexcept { detail::env()->DeleteGlobalRef(h); } +}; + +// Bug in JDK 16-21: according to JNI specification, +// NewWeakGlobalRef should return null when passed null argument. +// JDK 16-21 throws `java.lang.OutOfMemoryError: C heap space`. +// https://bugs.openjdk.org/browse/JDK-8313874 +// Fixed in JDK 22, JDK 21.0.1, JDK 17.0.9 + +template <> +struct ref +{ + static jobject new_ref(jobject h) noexcept { if (!h) return nullptr; return detail::env()->NewWeakGlobalRef(h); } + static void delete_ref(jobject h) noexcept { detail::env()->DeleteWeakGlobalRef(h); } +}; + +} // namespace scapix::jni::detail::api + +#endif // SCAPIX_JNI_DETAIL_API_REF_H diff --git a/source/scapix/jni/detail/api/string.h b/source/scapix/jni/detail/api/string.h new file mode 100644 index 0000000..199f8b4 --- /dev/null +++ b/source/scapix/jni/detail/api/string.h @@ -0,0 +1,45 @@ +/* + scapix/jni/detail/api/string.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_STRING_H +#define SCAPIX_JNI_DETAIL_API_STRING_H + +#include +#include + +namespace scapix::jni::detail::api { + +template +struct string; + +template <> +struct string +{ + static jsize length(jstring str) noexcept { return env()->GetStringLength(str); } + static const jchar* get_chars(jstring str, jboolean* is_copy) noexcept { return env()->GetStringChars(str, is_copy); } + static void release_chars(jstring str, const jchar* chars) noexcept { env()->ReleaseStringChars(str, chars); } + static void get_region(jstring str, jsize start, jsize len, jchar* buf) noexcept { env()->GetStringRegion(str, start, len, buf); } +}; + +template <> +struct string : string +{ + static const jchar* get_chars(jstring str, jboolean* is_copy) noexcept { return env()->GetStringCritical(str, is_copy); } + static void release_chars(jstring str, const jchar* chars) noexcept { env()->ReleaseStringCritical(str, chars); } +}; + +template <> +struct string +{ + static jsize length(jstring str) noexcept { return env()->GetStringUTFLength(str); } + static const char* get_chars(jstring str, jboolean* is_copy) noexcept { return env()->GetStringUTFChars(str, is_copy); } + static void release_chars(jstring str, const char* chars) noexcept { env()->ReleaseStringUTFChars(str, chars); } + static void get_region(jstring str, jsize start, jsize len, char* buf) noexcept { env()->GetStringUTFRegion(str, start, len, buf); } +}; + +} // namespace scapix::jni::detail::api + +#endif // SCAPIX_JNI_DETAIL_API_STRING_H diff --git a/source/scapix/jni/detail/api/type.h b/source/scapix/jni/detail/api/type.h new file mode 100644 index 0000000..cfd9f39 --- /dev/null +++ b/source/scapix/jni/detail/api/type.h @@ -0,0 +1,192 @@ +/* + scapix/jni/detail/api/type.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_API_TYPE_H +#define SCAPIX_JNI_DETAIL_API_TYPE_H + +#include + +namespace scapix::jni::detail::api { + +template +struct type; + +template +struct type> +{ + static local_ref get_field(jobject obj, jfieldID id) noexcept { return local_ref(env()->GetObjectField(obj, id)); } + static void set_field(jobject obj, jfieldID id, jni::ref value) noexcept { env()->SetObjectField(obj, id, value.handle()); } + static local_ref get_static_field(jclass cls, jfieldID id) noexcept { return local_ref(env()->GetStaticObjectField(cls, id)); } + static void set_static_field(jclass cls, jfieldID id, jni::ref value) noexcept { env()->SetStaticObjectField(cls, id, value.handle()); } + + template static local_ref call_method(jobject obj, jmethodID id, Args... args) noexcept { return local_ref(env()->CallObjectMethod(obj, id, args...)); } + template static local_ref call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return local_ref(env()->CallNonvirtualObjectMethod(obj, cls, id, args...)); } + template static local_ref call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return local_ref(env()->CallStaticObjectMethod(cls, id, args...)); } +}; + +template <> +struct type +{ + template static void call_method(jobject obj, jmethodID id, Args... args) noexcept { env()->CallVoidMethod(obj, id, args...); } + template static void call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { env()->CallNonvirtualVoidMethod(obj, cls, id, args...); } + template static void call_static_method(jclass cls, jmethodID id, Args... args) noexcept { env()->CallStaticVoidMethod(cls, id, args...); } +}; + +template <> +struct type +{ + static jboolean get_field(jobject obj, jfieldID id) noexcept { return env()->GetBooleanField(obj, id); } + static void set_field(jobject obj, jfieldID id, jboolean value) noexcept { env()->SetBooleanField(obj, id, value); } + static jboolean get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticBooleanField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jboolean value) noexcept { env()->SetStaticBooleanField(cls, id, value); } + + template static jboolean call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallBooleanMethod(obj, id, args...); } + template static jboolean call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualBooleanMethod(obj, cls, id, args...); } + template static jboolean call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticBooleanMethod(cls, id, args...); } + + static jbooleanArray new_array(jsize len) noexcept { return env()->NewBooleanArray(len); } + static jboolean* get_array_elements(jbooleanArray array, jboolean* isCopy) noexcept { return env()->GetBooleanArrayElements(array, isCopy); } + static void release_array_elements(jbooleanArray array, jboolean* elems, jint mode) noexcept { env()->ReleaseBooleanArrayElements(array, elems, mode); } + static void get_array_region(jbooleanArray array, jsize start, jsize len, jboolean* buf) noexcept { env()->GetBooleanArrayRegion(array, start, len, buf); } + static void set_array_region(jbooleanArray array, jsize start, jsize len, const jboolean *buf) noexcept { env()->SetBooleanArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jbyte get_field(jobject obj, jfieldID id) noexcept { return env()->GetByteField(obj, id); } + static void set_field(jobject obj, jfieldID id, jbyte value) noexcept { env()->SetByteField(obj, id, value); } + static jbyte get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticByteField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jbyte value) noexcept { env()->SetStaticByteField(cls, id, value); } + + template static jbyte call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallByteMethod(obj, id, args...); } + template static jbyte call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualByteMethod(obj, cls, id, args...); } + template static jbyte call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticByteMethod(cls, id, args...); } + + static jbyteArray new_array(jsize len) noexcept { return env()->NewByteArray(len); } + static jbyte* get_array_elements(jbyteArray array, jboolean* isCopy) noexcept { return env()->GetByteArrayElements(array, isCopy); } + static void release_array_elements(jbyteArray array, jbyte* elems, jint mode) noexcept { env()->ReleaseByteArrayElements(array, elems, mode); } + static void get_array_region(jbyteArray array, jsize start, jsize len, jbyte* buf) noexcept { env()->GetByteArrayRegion(array, start, len, buf); } + static void set_array_region(jbyteArray array, jsize start, jsize len, const jbyte *buf) noexcept { env()->SetByteArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jchar get_field(jobject obj, jfieldID id) noexcept { return env()->GetCharField(obj, id); } + static void set_field(jobject obj, jfieldID id, jchar value) noexcept { env()->SetCharField(obj, id, value); } + static jchar get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticCharField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jchar value) noexcept { env()->SetStaticCharField(cls, id, value); } + + template static jchar call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallCharMethod(obj, id, args...); } + template static jchar call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualCharMethod(obj, cls, id, args...); } + template static jchar call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticCharMethod(cls, id, args...); } + + static jcharArray new_array(jsize len) noexcept { return env()->NewCharArray(len); } + static jchar* get_array_elements(jcharArray array, jboolean* isCopy) noexcept { return env()->GetCharArrayElements(array, isCopy); } + static void release_array_elements(jcharArray array, jchar* elems, jint mode) noexcept { env()->ReleaseCharArrayElements(array, elems, mode); } + static void get_array_region(jcharArray array, jsize start, jsize len, jchar* buf) noexcept { env()->GetCharArrayRegion(array, start, len, buf); } + static void set_array_region(jcharArray array, jsize start, jsize len, const jchar *buf) noexcept { env()->SetCharArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jshort get_field(jobject obj, jfieldID id) noexcept { return env()->GetShortField(obj, id); } + static void set_field(jobject obj, jfieldID id, jshort value) noexcept { env()->SetShortField(obj, id, value); } + static jshort get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticShortField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jshort value) noexcept { env()->SetStaticShortField(cls, id, value); } + + template static jshort call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallShortMethod(obj, id, args...); } + template static jshort call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualShortMethod(obj, cls, id, args...); } + template static jshort call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticShortMethod(cls, id, args...); } + + static jshortArray new_array(jsize len) noexcept { return env()->NewShortArray(len); } + static jshort* get_array_elements(jshortArray array, jboolean* isCopy) noexcept { return env()->GetShortArrayElements(array, isCopy); } + static void release_array_elements(jshortArray array, jshort* elems, jint mode) noexcept { env()->ReleaseShortArrayElements(array, elems, mode); } + static void get_array_region(jshortArray array, jsize start, jsize len, jshort* buf) noexcept { env()->GetShortArrayRegion(array, start, len, buf); } + static void set_array_region(jshortArray array, jsize start, jsize len, const jshort *buf) noexcept { env()->SetShortArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jint get_field(jobject obj, jfieldID id) noexcept { return env()->GetIntField(obj, id); } + static void set_field(jobject obj, jfieldID id, jint value) noexcept { env()->SetIntField(obj, id, value); } + static jint get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticIntField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jint value) noexcept { env()->SetStaticIntField(cls, id, value); } + + template static jint call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallIntMethod(obj, id, args...); } + template static jint call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualIntMethod(obj, cls, id, args...); } + template static jint call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticIntMethod(cls, id, args...); } + + static jintArray new_array(jsize len) noexcept { return env()->NewIntArray(len); } + static jint* get_array_elements(jintArray array, jboolean* isCopy) noexcept { return env()->GetIntArrayElements(array, isCopy); } + static void release_array_elements(jintArray array, jint* elems, jint mode) noexcept { env()->ReleaseIntArrayElements(array, elems, mode); } + static void get_array_region(jintArray array, jsize start, jsize len, jint* buf) noexcept { env()->GetIntArrayRegion(array, start, len, buf); } + static void set_array_region(jintArray array, jsize start, jsize len, const jint *buf) noexcept { env()->SetIntArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jlong get_field(jobject obj, jfieldID id) noexcept { return env()->GetLongField(obj, id); } + static void set_field(jobject obj, jfieldID id, jlong value) noexcept { env()->SetLongField(obj, id, value); } + static jlong get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticLongField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jlong value) noexcept { env()->SetStaticLongField(cls, id, value); } + + template static jlong call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallLongMethod(obj, id, args...); } + template static jlong call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualLongMethod(obj, cls, id, args...); } + template static jlong call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticLongMethod(cls, id, args...); } + + static jlongArray new_array(jsize len) noexcept { return env()->NewLongArray(len); } + static jlong* get_array_elements(jlongArray array, jboolean* isCopy) noexcept { return env()->GetLongArrayElements(array, isCopy); } + static void release_array_elements(jlongArray array, jlong* elems, jint mode) noexcept { env()->ReleaseLongArrayElements(array, elems, mode); } + static void get_array_region(jlongArray array, jsize start, jsize len, jlong* buf) noexcept { env()->GetLongArrayRegion(array, start, len, buf); } + static void set_array_region(jlongArray array, jsize start, jsize len, const jlong *buf) noexcept { env()->SetLongArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jfloat get_field(jobject obj, jfieldID id) noexcept { return env()->GetFloatField(obj, id); } + static void set_field(jobject obj, jfieldID id, jfloat value) noexcept { env()->SetFloatField(obj, id, value); } + static jfloat get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticFloatField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jfloat value) noexcept { env()->SetStaticFloatField(cls, id, value); } + + template static jfloat call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallFloatMethod(obj, id, args...); } + template static jfloat call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualFloatMethod(obj, cls, id, args...); } + template static jfloat call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticFloatMethod(cls, id, args...); } + + static jfloatArray new_array(jsize len) noexcept { return env()->NewFloatArray(len); } + static jfloat* get_array_elements(jfloatArray array, jboolean* isCopy) noexcept { return env()->GetFloatArrayElements(array, isCopy); } + static void release_array_elements(jfloatArray array, jfloat* elems, jint mode) noexcept { env()->ReleaseFloatArrayElements(array, elems, mode); } + static void get_array_region(jfloatArray array, jsize start, jsize len, jfloat* buf) noexcept { env()->GetFloatArrayRegion(array, start, len, buf); } + static void set_array_region(jfloatArray array, jsize start, jsize len, const jfloat *buf) noexcept { env()->SetFloatArrayRegion(array, start, len, buf); } +}; + +template <> +struct type +{ + static jdouble get_field(jobject obj, jfieldID id) noexcept { return env()->GetDoubleField(obj, id); } + static void set_field(jobject obj, jfieldID id, jdouble value) noexcept { env()->SetDoubleField(obj, id, value); } + static jdouble get_static_field(jclass cls, jfieldID id) noexcept { return env()->GetStaticDoubleField(cls, id); } + static void set_static_field(jclass cls, jfieldID id, jdouble value) noexcept { env()->SetStaticDoubleField(cls, id, value); } + + template static jdouble call_method(jobject obj, jmethodID id, Args... args) noexcept { return env()->CallDoubleMethod(obj, id, args...); } + template static jdouble call_nonvirtual_method(jobject obj, jclass cls, jmethodID id, Args... args) noexcept { return env()->CallNonvirtualDoubleMethod(obj, cls, id, args...); } + template static jdouble call_static_method(jclass cls, jmethodID id, Args... args) noexcept { return env()->CallStaticDoubleMethod(cls, id, args...); } + + static jdoubleArray new_array(jsize len) noexcept { return env()->NewDoubleArray(len); } + static jdouble* get_array_elements(jdoubleArray array, jboolean* isCopy) noexcept { return env()->GetDoubleArrayElements(array, isCopy); } + static void release_array_elements(jdoubleArray array, jdouble* elems, jint mode) noexcept { env()->ReleaseDoubleArrayElements(array, elems, mode); } + static void get_array_region(jdoubleArray array, jsize start, jsize len, jdouble* buf) noexcept { env()->GetDoubleArrayRegion(array, start, len, buf); } + static void set_array_region(jdoubleArray array, jsize start, jsize len, const jdouble *buf) noexcept { env()->SetDoubleArrayRegion(array, start, len, buf); } +}; + +} // namespace scapix::jni::detail::api + +#endif // SCAPIX_JNI_DETAIL_API_TYPE_H diff --git a/source/scapix/jni/detail/exception.cpp b/source/scapix/jni/detail/exception.cpp new file mode 100644 index 0000000..193d9c4 --- /dev/null +++ b/source/scapix/jni/detail/exception.cpp @@ -0,0 +1,48 @@ +/* + scapix/jni/detail/exception.cpp + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#include +#include +#include +#include +#include + +namespace scapix::jni::detail { + +[[noreturn]] void throw_exception(jthrowable e) +{ +// env()->ExceptionDescribe(); + env()->ExceptionClear(); + throw vm_exception(local_ref(e)); +} + +/* + +Only check for possible (nested) C++ exception (native_exception) +after calling these four JNI function families, +which can throw any exception thrown by executed Java code: + +1. call_method +2. call_nonvirtual_method +3. call_static_method +4. new_object + +*/ + +[[noreturn]] void throw_exception_nested(jthrowable e) +{ +// env()->ExceptionDescribe(); + env()->ExceptionClear(); + + local_ref exception(e); + + if (auto native = exception->instance_of()) + native->rethrow(); + + throw vm_exception(std::move(exception)); +} + +} // namespace scapix::jni::detail diff --git a/source/scapix/jni/detail/exception.h b/source/scapix/jni/detail/exception.h new file mode 100644 index 0000000..45a0bdd --- /dev/null +++ b/source/scapix/jni/detail/exception.h @@ -0,0 +1,113 @@ +/* + scapix/jni/detail/exception.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_EXCEPTION_H +#define SCAPIX_JNI_DETAIL_EXCEPTION_H + +#include +#include + +namespace scapix::jni::detail { + +[[noreturn]] void throw_exception(jthrowable e = env()->ExceptionOccurred()); +[[noreturn]] void throw_exception_nested(jthrowable e = env()->ExceptionOccurred()); + +inline void check_exception() +{ + jthrowable e = env()->ExceptionOccurred(); + + if (e != 0) [[unlikely]] + throw_exception(e); +} + +inline void check_exception_nested() +{ + jthrowable e = env()->ExceptionOccurred(); + + if (e != 0) [[unlikely]] + throw_exception_nested(e); +} + +inline jfieldID check_result(jfieldID id) +{ + if (!id) [[unlikely]] + throw_exception(); + + return id; +} + +inline jmethodID check_result(jmethodID id) +{ + if (!id) [[unlikely]] + throw_exception(); + + return id; +} + +// RegisterNatives() + +inline jint check_result(jint i) +{ + if (i) [[unlikely]] + throw_exception(); + + return i; +} + +// GetArrayElements(), GetPrimitiveArrayCritical() + +template +inline T* check_result(T* p) +{ + if (!p) [[unlikely]] + throw_exception(); + + return p; +} + +template +inline local_ref check_result(jobject obj) +{ + if (!obj) [[unlikely]] + throw_exception(); + + return local_ref(obj); +} + +template +inline local_ref check_result_nested(jobject obj) +{ + if (!obj) [[unlikely]] + throw_exception_nested(); + + return local_ref(obj); +} + +struct check_exception_on_destroy +{ + check_exception_on_destroy() = default; + ~check_exception_on_destroy() noexcept(false) { check_exception(); } + + check_exception_on_destroy(const check_exception_on_destroy&) = delete; + check_exception_on_destroy(check_exception_on_destroy&&) = delete; + check_exception_on_destroy& operator = (const check_exception_on_destroy&) = delete; + check_exception_on_destroy& operator = (check_exception_on_destroy&&) = delete; +}; + +struct check_exception_nested_on_destroy +{ + check_exception_nested_on_destroy() = default; + ~check_exception_nested_on_destroy() noexcept(false) { check_exception_nested(); } + + check_exception_nested_on_destroy(const check_exception_nested_on_destroy&) = delete; + check_exception_nested_on_destroy(check_exception_nested_on_destroy&&) = delete; + check_exception_nested_on_destroy& operator = (const check_exception_nested_on_destroy&) = delete; + check_exception_nested_on_destroy& operator = (check_exception_nested_on_destroy&&) = delete; +}; + +} // namespace scapix::jni::detail + +#endif // SCAPIX_JNI_DETAIL_EXCEPTION_H diff --git a/source/scapix/jni/detail/init.h b/source/scapix/jni/detail/init.h new file mode 100644 index 0000000..200d1a9 --- /dev/null +++ b/source/scapix/jni/detail/init.h @@ -0,0 +1,34 @@ +/* + scapix/jni/detail/init.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_INIT_H +#define SCAPIX_JNI_DETAIL_INIT_H + +#include +#include +#include +#include + +#ifdef SCAPIX_JNI_CACHE_CLASS_LOADER +#include +#endif + +namespace scapix::jni::detail { + +inline void init() +{ + #ifdef SCAPIX_JNI_CACHE_CLASS_LOADER + class_loader::init(); + #endif + + com::scapix::native_exception::native_methods::register_(); + com::scapix::bridge_::native_methods::register_(); + com::scapix::function::native_methods::register_(); +} + +} // namespace scapix::jni::detail + +#endif // SCAPIX_JNI_DETAIL_INIT_H diff --git a/source/scapix/jni/detail/util.h b/source/scapix/jni/detail/util.h new file mode 100644 index 0000000..0820afc --- /dev/null +++ b/source/scapix/jni/detail/util.h @@ -0,0 +1,21 @@ +/* + scapix/jni/detail/util.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_DETAIL_UTIL_H +#define SCAPIX_JNI_DETAIL_UTIL_H + +namespace scapix::jni::detail { + +template +class befriend : public T +{ + using T::T; + friend Friend; +}; + +} // namespace scapix::jni::detail + +#endif // SCAPIX_JNI_DETAIL_UTIL_H diff --git a/source/scapix/jni/element.h b/source/scapix/jni/element.h new file mode 100644 index 0000000..9113236 --- /dev/null +++ b/source/scapix/jni/element.h @@ -0,0 +1,54 @@ +/* + scapix/jni/element.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_ELEMENT_H +#define SCAPIX_JNI_ELEMENT_H + +#include +#include +#include + +namespace scapix::jni { + +template +struct generic_type; + +template > +struct generic; + +//template +//struct extends; + +//template +//struct super; + +template +struct element_type +{ + using type = T; +}; + +template +struct element_type +{ + using type = array; +}; + +template +struct element_type> +{ + using type = element_type_t; +}; + +template +struct element_type> +{ + using type = element_type_t; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_ELEMENT_H diff --git a/source/scapix/jni/env.h b/source/scapix/jni/env.h new file mode 100644 index 0000000..e3617e2 --- /dev/null +++ b/source/scapix/jni/env.h @@ -0,0 +1,97 @@ +/* + scapix/jni/env.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_ENV_H +#define SCAPIX_JNI_ENV_H + +#include +#include + +namespace scapix::jni { +namespace detail { + +/* + +jni.h (JDK) uses void** type: + + jint JNI_CreateJavaVM(JavaVM**, void**, void*); + jint AttachCurrentThread(JavaVM*, void**, void*); + jint AttachCurrentThreadAsDaemon(JavaVM*, void**, void*); + +jni.h (Android NDK) uses JNIEnv** type: + + jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); + jint AttachCurrentThread(JavaVM*, JNIEnv**, void*); + jint AttachCurrentThreadAsDaemon(JavaVM*, JNIEnv**, void*); + +*/ + +using jnienv_type = function_traits>::argument_type<1>; + +inline JavaVM* jvm_ptr; + +inline JavaVM* jvm() noexcept +{ + return jvm_ptr; +} + +struct thread_env +{ + ~thread_env() + { + if (ptr && jvm()) + jvm()->DetachCurrentThread(); + } + + jnienv_type addr() { return reinterpret_cast(&ptr); } + void** addr_void() { return reinterpret_cast(&ptr); } + + JNIEnv* ptr = nullptr; +}; + +inline thread_local thread_env env_; + +} // namespace detail + +inline jint get_env(jint version = JNI_VERSION_1_6) noexcept +{ + return detail::jvm()->GetEnv(detail::env_.addr_void(), version); +} + +inline jint attach_current_thread(JavaVMAttachArgs* args = nullptr) noexcept +{ + return detail::jvm()->AttachCurrentThread(detail::env_.addr(), args); +} + +inline jint attach_current_thread_as_daemon(JavaVMAttachArgs* args = nullptr) noexcept +{ + return detail::jvm()->AttachCurrentThreadAsDaemon(detail::env_.addr(), args); +} + +inline jint detach_current_thread() noexcept +{ + detail::env_.ptr = nullptr; + return detail::jvm()->DetachCurrentThread(); +} + +namespace detail { + +inline JNIEnv* env() noexcept +{ +#ifdef SCAPIX_JNI_AUTO_ATTACH_THREAD + + if (!env_.ptr) [[unlikely]] + attach_current_thread(); + +#endif + + return env_.ptr; +} + +} // namespace detail +} // namespace scapix::jni + +#endif // SCAPIX_JNI_ENV_H diff --git a/source/scapix/jni/function.h b/source/scapix/jni/function.h new file mode 100644 index 0000000..37a7d68 --- /dev/null +++ b/source/scapix/jni/function.h @@ -0,0 +1,39 @@ +/* + scapix/jni/function.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FUNCTION_H +#define SCAPIX_JNI_FUNCTION_H + +#include + +namespace scapix::jni { + +// represents java @FunctionalInterface + +template +class function; + +template +class function : public object +{ + using base = object; + +public: + + R call(Args... args) const + { + return base::template call_method(args...); + } + +protected: + + function(typename base::handle_type h) : base(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FUNCTION_H diff --git a/source/scapix/jni/fwd/array.h b/source/scapix/jni/fwd/array.h new file mode 100644 index 0000000..f9634e1 --- /dev/null +++ b/source/scapix/jni/fwd/array.h @@ -0,0 +1,29 @@ +/* + scapix/jni/fwd/array.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_ARRAY_H +#define SCAPIX_JNI_FWD_ARRAY_H + +#include +#include +#include +#include + +namespace scapix::jni { + +template +class array; + +template +struct object_traits> +{ + static constexpr auto class_name = signature_v>; + using base_classes = std::tuple>; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_ARRAY_H diff --git a/source/scapix/jni/fwd/byte_buffer.h b/source/scapix/jni/fwd/byte_buffer.h new file mode 100644 index 0000000..3f1227f --- /dev/null +++ b/source/scapix/jni/fwd/byte_buffer.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/byte_buffer.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_BYTE_BUFFER_H +#define SCAPIX_JNI_FWD_BYTE_BUFFER_H + +#include +#include +#include +#include + +namespace scapix::jni { + +class byte_buffer; + +template<> +struct object_traits +{ + static constexpr auto class_name = "java/nio/ByteBuffer"; + using base_classes = std::tuple>; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_BYTE_BUFFER_H diff --git a/source/scapix/jni/fwd/class.h b/source/scapix/jni/fwd/class.h new file mode 100644 index 0000000..660c5a9 --- /dev/null +++ b/source/scapix/jni/fwd/class.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/class.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_CLASS_H +#define SCAPIX_JNI_FWD_CLASS_H + +#include +#include +#include +#include + +namespace scapix::jni { + +class class_; + +template<> +struct object_traits +{ + static constexpr fixed_string class_name = "java/lang/Class"; + using base_classes = std::tuple>; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_CLASS_H diff --git a/source/scapix/jni/fwd/element.h b/source/scapix/jni/fwd/element.h new file mode 100644 index 0000000..aeaf666 --- /dev/null +++ b/source/scapix/jni/fwd/element.h @@ -0,0 +1,20 @@ +/* + scapix/jni/fwd/element.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_ELEMENT_H +#define SCAPIX_JNI_FWD_ELEMENT_H + +namespace scapix::jni { + +template +struct element_type; + +template +using element_type_t = typename element_type::type; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_ELEMENT_H diff --git a/source/scapix/jni/fwd/native_method.h b/source/scapix/jni/fwd/native_method.h new file mode 100644 index 0000000..8cc65b0 --- /dev/null +++ b/source/scapix/jni/fwd/native_method.h @@ -0,0 +1,23 @@ +/* + scapix/jni/fwd/native_method.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_NATIVE_METHOD_H +#define SCAPIX_JNI_FWD_NATIVE_METHOD_H + +#include +#include + +namespace scapix::jni { + +template +class native_methods; + +template Method> +struct native_method; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_NATIVE_METHOD_H diff --git a/source/scapix/jni/fwd/object.h b/source/scapix/jni/fwd/object.h new file mode 100644 index 0000000..56236e5 --- /dev/null +++ b/source/scapix/jni/fwd/object.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/object.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_OBJECT_H +#define SCAPIX_JNI_FWD_OBJECT_H + +#include +#include +#include + +namespace scapix::jni { + +template +class object; + +template +struct object_traits> +{ + static constexpr auto class_name = ClassName; + using base_classes = std::tuple; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_OBJECT_H diff --git a/source/scapix/jni/fwd/object_base.h b/source/scapix/jni/fwd/object_base.h new file mode 100644 index 0000000..33c1a20 --- /dev/null +++ b/source/scapix/jni/fwd/object_base.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/object_base.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_OBJECT_BASE_H +#define SCAPIX_JNI_FWD_OBJECT_BASE_H + +#include +#include +#include + +namespace scapix::jni { + +template +class object_base; + +template +struct object_traits> +{ + static constexpr auto class_name = ClassName; + using base_classes = std::tuple; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_OBJECT_BASE_H diff --git a/source/scapix/jni/fwd/ref.h b/source/scapix/jni/fwd/ref.h new file mode 100644 index 0000000..d91898c --- /dev/null +++ b/source/scapix/jni/fwd/ref.h @@ -0,0 +1,22 @@ +/* + scapix/jni/fwd/ref.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_REF_H +#define SCAPIX_JNI_FWD_REF_H + +#include +#include + +namespace scapix::jni { + +using scope = detail::api::scope; + +template , scope Scope = scope::generic> +class ref; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_REF_H diff --git a/source/scapix/jni/fwd/signature.h b/source/scapix/jni/fwd/signature.h new file mode 100644 index 0000000..af99dc2 --- /dev/null +++ b/source/scapix/jni/fwd/signature.h @@ -0,0 +1,20 @@ +/* + scapix/jni/fwd/signature.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_SIGNATURE_H +#define SCAPIX_JNI_FWD_SIGNATURE_H + +namespace scapix::jni { + +template +struct signature; + +template +constexpr auto signature_v = signature::value; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_SIGNATURE_H diff --git a/source/scapix/jni/fwd/string.h b/source/scapix/jni/fwd/string.h new file mode 100644 index 0000000..5c37e68 --- /dev/null +++ b/source/scapix/jni/fwd/string.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/string.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_STRING_H +#define SCAPIX_JNI_FWD_STRING_H + +#include +#include +#include +#include + +namespace scapix::jni { + +class string; + +template<> +struct object_traits +{ + static constexpr fixed_string class_name = "java/lang/String"; + using base_classes = std::tuple>; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_STRING_H diff --git a/source/scapix/jni/fwd/throwable.h b/source/scapix/jni/fwd/throwable.h new file mode 100644 index 0000000..61a886e --- /dev/null +++ b/source/scapix/jni/fwd/throwable.h @@ -0,0 +1,28 @@ +/* + scapix/jni/fwd/throwable.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_FWD_THROWABLE_H +#define SCAPIX_JNI_FWD_THROWABLE_H + +#include +#include +#include +#include + +namespace scapix::jni { + +class throwable; + +template<> +struct object_traits +{ + static constexpr fixed_string class_name = "java/lang/Throwable"; + using base_classes = std::tuple>; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_FWD_THROWABLE_H diff --git a/source/scapix/jni/local_frame.h b/source/scapix/jni/local_frame.h new file mode 100644 index 0000000..2796b96 --- /dev/null +++ b/source/scapix/jni/local_frame.h @@ -0,0 +1,75 @@ +/* + scapix/jni/local_frame.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_LOCAL_FRAME_H +#define SCAPIX_JNI_LOCAL_FRAME_H + +#include +#include + +namespace scapix::jni { + +class local_frame +{ +public: + + local_frame(jint capacity) + { + frame = detail::env()->PushLocalFrame(capacity); + } + + ~local_frame() + { + if (frame == 0) + detail::env()->PopLocalFrame(0); + } + + jobject pop(jobject result = 0) + { + if (frame == 0) + { + frame = -1; + return detail::env()->PopLocalFrame(result); + } + + return 0; + } + + local_frame(const local_frame&) = delete; + local_frame& operator = (const local_frame&) = delete; + + local_frame(local_frame&& source) : + frame(source.frame) + { + source.frame = -1; + } + + local_frame& operator = (local_frame&& source) + { + local_frame(std::move(source)).swap(*this); + return *this; + } + + void swap(local_frame& source) + { + using std::swap; + swap(frame, source.frame); + } + +private: + + jint frame; + +}; + +inline void swap(local_frame& a, local_frame& b) +{ + a.swap(b); +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_LOCAL_FRAME_H diff --git a/source/scapix/jni/lock.h b/source/scapix/jni/lock.h new file mode 100644 index 0000000..11890ba --- /dev/null +++ b/source/scapix/jni/lock.h @@ -0,0 +1,28 @@ +/* + scapix/jni/lock.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_LOCK_H +#define SCAPIX_JNI_LOCK_H + +#include + +namespace scapix::jni { + +enum class lock +{ + noncritical, + critical, +}; + +enum class release_mode +{ + copy = 0, + abort = JNI_ABORT, +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_LOCK_H diff --git a/source/scapix/jni/module.h b/source/scapix/jni/module.h new file mode 100644 index 0000000..cd63809 --- /dev/null +++ b/source/scapix/jni/module.h @@ -0,0 +1,49 @@ +/* + scapix/jni/module.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_MODULE_H +#define SCAPIX_JNI_MODULE_H + +#include +#include +#include + +namespace scapix::jni { + +inline jint on_load(JavaVM* vm, void* reserved, void(*init)() = []{}) +{ + try + { + detail::jvm_ptr = vm; + get_env(); + + detail::init(); + init(); + + return JNI_VERSION_1_6; + } + catch (const vm_exception& e) + { + e.get()->throw_(); + + // Android doesn't check exception after calling JNI_OnLoad() + #ifdef ANDROID + detail::env()->ExceptionDescribe(); + detail::env()->ExceptionClear(); + #endif + } + + return JNI_ERR; +} + +inline void on_unload(JavaVM *vm, void *reserved) noexcept +{ + detail::jvm_ptr = nullptr; +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_MODULE_H diff --git a/source/scapix/jni/monitor.h b/source/scapix/jni/monitor.h new file mode 100644 index 0000000..216cc4c --- /dev/null +++ b/source/scapix/jni/monitor.h @@ -0,0 +1,85 @@ +/* + scapix/jni/monitor.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_MONITOR_H +#define SCAPIX_JNI_MONITOR_H + +#include +#include +#include + +namespace scapix::jni { + +template +class monitor +{ +public: + + // Generally, caller is responsible to insure the reference (not only the object) is alive while monitor is active. + // But if an owning reference is moved here, monitor will take ownership. + + monitor(ref obj) + : object(std::move(obj)) + { + [[maybe_unused]] auto result = detail::env()->MonitorEnter(object.handle()); + assert(result == 0); + } + + monitor(const monitor&) = delete; + monitor& operator = (const monitor&) = delete; + + monitor(monitor&& source) : + object(source.release()) + { + } + + monitor& operator = (monitor&& source) + { + monitor(std::move(source)).swap(*this); + return *this; + } + + ~monitor() + { + if (object) + { + [[maybe_unused]] auto result = detail::env()->MonitorExit(object.handle()); + assert(result == 0); + } + } + + explicit operator bool() const { return bool(object); } + ref get() const { return object; } + + void reset() + { + monitor().swap(*this); + } + + void swap(monitor& source) + { + using std::swap; + swap(object, source.object); + } + +private: + + monitor() {} + ref release() { return std::move(object); } + + ref object; + +}; + +template +inline void swap(monitor& a, monitor& b) +{ + a.swap(b); +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_MONITOR_H diff --git a/source/scapix/jni/native_method.h b/source/scapix/jni/native_method.h new file mode 100644 index 0000000..56448dc --- /dev/null +++ b/source/scapix/jni/native_method.h @@ -0,0 +1,201 @@ +/* + scapix/jni/native_method.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_NATIVE_METHOD_H +#define SCAPIX_JNI_NATIVE_METHOD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +struct param; + +template +struct param +{ + using Cpv = std::remove_reference_t; + + static Jni jni(Cpp v) { return convert_jni(v); } + static Cpv cpp(Jni v) { return convert_cpp(v); } +}; + +template +struct param, Cpp> +{ + static jobject jni(Cpp&& v) + { + ref res(std::move(v)); + + assert(res.get_scope() == scope::local || res.get_scope() == scope::generic); + +// if (res.get_scope() == scope::global || res.get_scope() == scope::weak) +// return local_ref<>(res).release(); + + return res.release(); + } + + static decltype(auto) cpp(jobject v) + { + return ref(v); + } +}; + +template +struct param_t; + +template + requires (primitive || std::is_void_v) +struct param_t +{ + using type = Jni; +}; + +template +struct param_t> +{ + using type = jobject; +}; + +template +using param_type = typename param_t::type; + +template +struct jni_native_method +{ + const char* name; + const char* signature; + Func fnPtr; + +private: + + static void compile_check() + { + static_assert(sizeof(JNINativeMethod) == sizeof(jni_native_method), "jni_native_method should be ABI compatible with JNINativeMethod"); + } + +}; + +// for apple clang 15.0.0.15000100 +template +jni_native_method(const char*, const char*, Func) -> jni_native_method; + +template +class native_methods +{ +public: + + static void register_() + { + class_::find_class(ClassName)->register_natives(reinterpret_cast(&methods), sizeof...(Methods)); + } + +private: + + native_methods() = delete; + + static constexpr tuple methods = { Methods::template get()... }; + +}; + +// std::decay is a workaround for GCC bug (fixed in GCC 12): +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61355 + +template Method> +struct native_method +{ + template + struct impl; + + template + struct impl + { + static param_type func(JNIEnv* env, jobject thiz, param_type... args) + { + detail::env_.ptr = env; + + try + { + decltype(auto) obj = convert_this(ref>(thiz)); + + if constexpr (std::is_void_v) + { + return (obj.*Method)(param::cpp(args)...); + } + else + { + return param::jni((obj.*Method)(param::cpp(args)...)); + } + } + catch (const vm_exception& e) + { + e.get()->throw_(); + } + catch (...) + { + new_object()->throw_(); + } + + if constexpr (!std::is_void_v) + return {}; + } + }; + + template + struct impl + { + static param_type func(JNIEnv* env, jclass clazz, param_type... args) + { + detail::env_.ptr = env; + + try + { + if constexpr (std::is_void_v) + { + return Method(param::cpp(args)...); + } + else + { + return param::jni(Method(param::cpp(args)...)); + } + } + catch (const vm_exception& e) + { + e.get()->throw_(); + } + catch (...) + { + new_object()->throw_(); + } + + if constexpr (!std::is_void_v) + return {}; + } + }; + + template + static constexpr auto get() + { + return jni_native_method + { + Name, + signature_v, + impl>::func + }; + } +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_NATIVE_METHOD_H diff --git a/source/scapix/jni/object.h b/source/scapix/jni/object.h new file mode 100644 index 0000000..4ee1c48 --- /dev/null +++ b/source/scapix/jni/object.h @@ -0,0 +1,126 @@ +/* + scapix/jni/object.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +// outside of include guard +#include + +#ifndef SCAPIX_JNI_OBJECT_H +#define SCAPIX_JNI_OBJECT_H + +#include +#include + +namespace scapix::jni { + +#include + +template +class object : private object_impl, public Bases... +{ + using impl = object_impl; + +public: + + static constexpr auto class_name = ClassName; + using base_classes = std::tuple; + using handle_type = handle_type_t; + + using impl::call_method; + using impl::call_nonvirtual_method; + using impl::call_static_method; + using impl::get_field; + using impl::set_field; + using impl::get_static_field; + using impl::set_static_field; + using impl::to_reflected_method; + using impl::to_reflected_static_method; + using impl::to_reflected_field; + using impl::to_reflected_static_field; + using impl::class_object; + +protected: + + object(handle_type h) : impl(h), Bases(h)... {} + handle_type handle() const { return static_cast(impl::handle()); } + +}; + +template +class object : public object> +{ +public: + + using object>::object; + +}; + +template +struct always_false : std::false_type {}; + +template +class object<"java/lang/Object", Base1, Bases...> +{ + static_assert(always_false::value, "java/lang/Object should not specify base classes"); +}; + +template <> +class object<"java/lang/Object"> : private object_impl<"java/lang/Object"> +{ +public: + + static constexpr fixed_string class_name = "java/lang/Object"; + using base_classes = std::tuple<>; + using handle_type = handle_type_t; + + using object_impl::call_method; + using object_impl::call_nonvirtual_method; + using object_impl::call_static_method; + using object_impl::get_field; + using object_impl::set_field; + using object_impl::get_static_field; + using object_impl::set_static_field; + using object_impl::to_reflected_method; + using object_impl::to_reflected_static_method; + using object_impl::to_reflected_field; + using object_impl::to_reflected_static_field; + using object_impl::class_object; + + template + bool is_instance_of() const noexcept + { + return is_instance_of(object_impl_t::class_object()); + } + + bool is_instance_of(ref cls) const noexcept + { + return detail::env()->IsInstanceOf(handle(), cls.handle()); + } + + template + ref instance_of() const noexcept + { + if (is_instance_of()) + return ref(handle()); + + return nullptr; + } + + local_ref get_object_class() const noexcept + { + return local_ref(detail::env()->GetObjectClass(handle())); + } + +protected: + + object(handle_type h) : object_impl(h) {} + +}; + +#include + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_OBJECT_H diff --git a/source/scapix/jni/object_base.h b/source/scapix/jni/object_base.h new file mode 100644 index 0000000..af8bea7 --- /dev/null +++ b/source/scapix/jni/object_base.h @@ -0,0 +1,65 @@ +/* + scapix/jni/object_base.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +// outside of include guard +#include + +#ifndef SCAPIX_JNI_OBJECT_BASE_H +#define SCAPIX_JNI_OBJECT_BASE_H + +#include +#include + +namespace scapix::jni { + +#include + +template +class object_base : private object_impl, public Bases... +{ + using impl = object_impl; + +protected: + + static constexpr auto class_name = ClassName; + using base_classes = std::tuple; + using handle_type = handle_type_t; + + using base_ = object_base; + + object_base(handle_type h) : impl(h), Bases(h)... {} + + template + static local_ref new_object(Args&&... args) + { + return jni::new_object, void(std::remove_reference_t...)>(std::forward(args)...); + } + + template + auto call_method(Args&&... args) const + { + return impl::template call_method...)>(std::forward(args)...); + } + + template + static auto call_static_method(Args&&... args) + { + return impl::template call_static_method...)>(std::forward(args)...); + } + + using impl::get_field; + using impl::set_field; + using impl::get_static_field; + using impl::set_static_field; + using impl::class_object; + +}; + +#include + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_OBJECT_BASE_H diff --git a/source/scapix/jni/object_impl.h b/source/scapix/jni/object_impl.h new file mode 100644 index 0000000..66b8ddd --- /dev/null +++ b/source/scapix/jni/object_impl.h @@ -0,0 +1,278 @@ +/* + scapix/jni/object_impl.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_OBJECT_IMPL_H +#define SCAPIX_JNI_OBJECT_IMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +class object_impl +{ + static constexpr auto class_name = ClassName; + using handle_type = jobject; + +public: + + // call_method + + template + auto call_method(Args&&... args) const + { + return call_method(method_id(), std::forward(args)...); + } + + template + auto call_method(jmethodID id, Args&&... args) const + { + return detail::api::call::method(handle(), id, std::forward(args)...); + } + + // call_nonvirtual_method + + template + auto call_nonvirtual_method(Args&&... args) const + { + return call_nonvirtual_method(method_id(), std::forward(args)...); + } + + template + auto call_nonvirtual_method(jmethodID id, Args&&... args) const + { + return detail::api::call::nonvirtual_method(handle(), class_object().handle(), id, std::forward(args)...); + } + + // call_static_method + + template + static auto call_static_method(Args&&... args) + { + return call_static_method(static_method_id(), std::forward(args)...); + } + + template + static auto call_static_method(jmethodID id, Args&&... args) + { + return detail::api::call::static_method(class_object().handle(), id, std::forward(args)...); + } + + // get_field + + template + Type get_field() const + { + return get_field(field_id()); + } + + template + Type get_field(jfieldID id) const + { + return detail::api::type::get_field(handle(), id); + } + + // set_field + + template + void set_field(std::type_identity_t value) const + { + set_field(field_id(), value); + } + + template + void set_field(jfieldID id, std::type_identity_t value) const + { + detail::api::type::set_field(handle(), id, value); + } + + // get_static_field + + template + static Type get_static_field() + { + return get_static_field(static_field_id()); + } + + template + static Type get_static_field(jfieldID id) + { + return detail::api::type::get_static_field(class_object().handle(), id); + } + + // set_static_field + + template + static void set_static_field(std::type_identity_t value) + { + set_static_field(static_field_id(), value); + } + + template + static void set_static_field(jfieldID id, std::type_identity_t value) + { + detail::api::type::set_static_field(class_object().handle(), id, value); + } + + // reflect + + template + static ref> to_reflected_method(); + + template + static ref> to_reflected_static_method(); + + template + static ref> to_reflected_field(); + + template + static ref> to_reflected_static_field(); + + // class_object + + static ref class_object(); + +protected: + + object_impl(handle_type h) : handle_(h) {} + handle_type handle() const { return handle_; } + +private: + + template + friend ref new_object(Args&&... args); // requires requires { element_type_t::class_name; }; + + template + static jmethodID method_id(); + + template + static jmethodID static_method_id(); + + template + static jfieldID field_id(); + + template + static jfieldID static_field_id(); + + handle_type handle_; + +}; + +template +using object_impl_t = object_impl>>; + +template +ref new_object(jmethodID id, Args&&... args) requires requires { element_type_t::class_name; } +{ + return detail::api::call::template new_object>(object_impl_t::class_object().handle(), id, std::forward(args)...); +} + +// https://github.com/llvm/llvm-project/issues/63536 +template +ref new_object(Args&&... args) // requires requires { element_type_t::class_name; } +{ + return new_object(object_impl_t::template method_id<"", Type>(), std::forward(args)...); +} + +template +ref new_object(Args&&... args) requires requires { element_type_t::new_object(std::forward(args)...); } +{ + return element_type_t::new_object(std::forward(args)...); +} + +} // namespace scapix::jni + +#ifdef SCAPIX_JNI_CACHE_CLASS_LOADER +#include +#else +#include +#endif + +namespace scapix::jni { + +template +template +inline ref> object_impl::to_reflected_method() +{ + return class_object()->to_reflected_method(method_id(), false); +} + +template +template +inline ref> object_impl::to_reflected_static_method() +{ + return class_object()->to_reflected_method(static_method_id(), true); +} + +template +template +inline ref> object_impl::to_reflected_field() +{ + return class_object()->to_reflected_field(field_id(), false); +} + +template +template +inline ref> object_impl::to_reflected_static_field() +{ + return class_object()->to_reflected_field(static_field_id(), true); +} + +template +inline ref object_impl::class_object() +{ + +#ifdef SCAPIX_JNI_CACHE_CLASS_LOADER + static const static_global_ref cls(class_loader::find_class(ClassName.replace('/', '.'))); +#else + static const static_global_ref cls(class_::find_class(ClassName)); +#endif + + return cls; +} + +template +template +inline jmethodID object_impl::method_id() +{ + static const jmethodID id(class_object()->get_method_id(Name, signature_v)); + return id; +} + +template +template +inline jmethodID object_impl::static_method_id() +{ + static const jmethodID id(class_object()->get_static_method_id(Name, signature_v)); + return id; +} + +template +template +inline jfieldID object_impl::field_id() +{ + static const jfieldID id(class_object()->get_field_id(Name, signature_v)); + return id; +} + +template +template +inline jfieldID object_impl::static_field_id() +{ + static const jfieldID id(class_object()->get_static_field_id(Name, signature_v)); + return id; +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_OBJECT_IMPL_H diff --git a/source/scapix/jni/object_traits.h b/source/scapix/jni/object_traits.h new file mode 100644 index 0000000..9c43c31 --- /dev/null +++ b/source/scapix/jni/object_traits.h @@ -0,0 +1,33 @@ +/* + scapix/jni/object_traits.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_OBJECT_TRAITS_H +#define SCAPIX_JNI_OBJECT_TRAITS_H + +#include + +namespace scapix::jni { + +// object_traits +// Specialized to provide traits for incomplete object type. +// Otherwise, uses traits from (complete) object type. + +template +struct object_traits +{ + static constexpr auto class_name = detail::befriend::class_name; + using base_classes = typename detail::befriend::base_classes; +}; + +template +constexpr auto class_name_v = object_traits::class_name; + +template +using base_classes_t = typename object_traits::base_classes; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_OBJECT_TRAITS_H diff --git a/source/scapix/jni/ref.h b/source/scapix/jni/ref.h new file mode 100644 index 0000000..4c9539c --- /dev/null +++ b/source/scapix/jni/ref.h @@ -0,0 +1,433 @@ +/* + scapix/jni/ref.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_REF_H +#define SCAPIX_JNI_REF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +class redirector +{ +public: + + redirector(const redirector&) = delete; + redirector& operator = (const redirector&) = delete; + + redirector(redirector&&) = delete; + redirector& operator = (redirector&&) = delete; + + explicit redirector(T&& v) : value(std::move(v)) {} + + T* operator -> () && { return &value; } + const T* operator -> () const && { return &value; } + + T& operator * () && { return value; } + const T& operator * () const && { return value; } + +// operator T* () && { return &value; } +// operator const T* () const && { return &value; } + +private: + + T value; + +}; + +// is_ref + +template +struct is_ref : std::false_type {}; + +template +struct is_ref> : std::true_type {}; + +template +constexpr bool is_ref_v = is_ref::value; + +// canonical_ref + +template +struct canonical_ref +{ + using type = T; +}; + +template +struct canonical_ref, Scope>> +{ + using type = ref; +}; + +template +using canonical_ref_t = typename canonical_ref::type; + +// convert + +template +struct convert; + +template +decltype(auto) convert_jni(Cpp&& cpp) +{ + return convert>, std::remove_cvref_t>::jni(std::forward(cpp)); +} + +template +decltype(auto) convert_cpp(Jni&& jni) +{ + return convert>, std::remove_cvref_t>::cpp(std::forward(jni)); +} + +template +using has_convert_jni_t = decltype(std::declval() = convert>, std::remove_cvref_t>::jni(std::declval())); + +template +using has_convert_cpp_t = decltype(std::declval() = convert>, std::remove_cvref_t>::cpp(std::declval())); + +/* + +Implementation for owning reference types: local_ref, global_ref, weak_ref. + +*/ + +template +class ref +{ +public: + + using element_type = element_type_t; + using handle_type = handle_type_t; + static constexpr auto class_name = class_name_v; + + static_assert(!is_ref_v && !is_ref_v); + + constexpr scope get_scope() { return Scope; } + + ref(std::nullptr_t = nullptr) : object(nullptr) {} + explicit ref(jobject h) : object(h) {} + + ref(const ref& r) : object(new_ref(r)) {} + + template Y, scope S> + ref(const ref& r) : object(new_ref(r)) {} + + ref(ref&& r) noexcept : object(r.release()) {} + + template Y, scope S> + ref(ref&& r) : object(nullptr) + { + if (get_scope() == r.get_scope()) + { + object = r.release(); + } + else + { + object = new_ref(r); + } + } + + ~ref() + { + if (handle()) + detail::api::ref::delete_ref(handle()); + } + + ref& operator = (const ref& r) + { + ref(r).swap(*this); + return *this; + } + + template Y, scope S> + ref& operator = (const ref& r) + { + ref(r).swap(*this); + return *this; + } + + ref& operator = (ref&& r) + { + ref(std::move(r)).swap(*this); + return *this; + } + + template Y, scope S> + ref& operator = (ref&& r) + { + ref(std::move(r)).swap(*this); + return *this; + } + + redirector operator -> () { return redirector(make_element()); } + const redirector operator -> () const { return redirector(make_element()); } + element_type operator * () { return make_element(); } + const element_type operator * () const { return make_element(); } + + auto operator [] (jsize i) requires object_array { return get()[i]; } + + explicit operator bool() const { return handle() != nullptr; } + element_type get() const { return make_element(); } + + auto handle() const + { + return static_cast(object); + } + + void reset(jobject h = nullptr) + { + ref(h).swap(*this); + } + + auto release() + { + auto temp(static_cast(object)); + object = nullptr; + return temp; + } + + void swap (ref& r) + { + using std::swap; + swap (object, r.object); + } + +private: + + element_type make_element() const + { + return detail::befriend(handle()); + } + + template + static jobject new_ref(const Ref& r) + { + return detail::api::ref::new_ref(r.handle()); + } + + jobject object; + +}; + +/* + +Implementation for (conceptually) non-owning reference type: ref. + +In reality takes ownership when moved to, and does not take ownership when copied to. +This allows both zero overhead parameters and safe return of temporaries. + +When owning semantics is required, use local_ref, global_ref or weak_ref explicitly. + +*/ + +template +class ref +{ +public: + + using element_type = element_type_t; + using handle_type = handle_type_t; + static constexpr auto class_name = class_name_v; + + static_assert(!is_ref_v && !is_ref_v); + + scope get_scope() { return scp; } + + ref(std::nullptr_t = nullptr) : object(nullptr), scp(scope::generic) {} + + explicit(!std::is_same_v>) ref(jobject h) : object(h), scp(scope::generic) {} + + ref(const ref& r) : object(r.handle()), scp(scope::generic) {} + + template Y, scope S> + ref(const ref& r) : object(r.handle()), scp(scope::generic) {} + + ref(ref&& r) noexcept : object(r.release()), scp(r.get_scope()) {} + + template Y, scope S> + ref(ref&& r) : object(r.release()), scp(r.get_scope()) {} + + template >> + ref(X&& x) + : ref(convert_jni(std::forward(x))) + { + } + + template && !std::is_same_v>> + operator X() const + { + return convert_cpp(*this); + } + + ~ref() + { + if (handle()) + { + if (scp == scope::local) + { + detail::api::ref::delete_ref(handle()); + } + else if (scp == scope::global) + { + detail::api::ref::delete_ref(handle()); + } + else if (scp == scope::weak) + { + detail::api::ref::delete_ref(handle()); + } + } + } + + ref& operator = (const ref& r) + { + ref(r).swap(*this); + return *this; + } + + template Y, scope S> + ref& operator = (const ref& r) + { + ref(r).swap(*this); + return *this; + } + + ref& operator = (ref&& r) + { + ref(std::move(r)).swap(*this); + return *this; + } + + template Y, scope S> + ref& operator = (ref&& r) + { + ref(std::move(r)).swap(*this); + return *this; + } + + redirector operator -> () { return redirector(make_element()); } + const redirector operator -> () const { return redirector(make_element()); } + element_type operator * () { return make_element(); } + const element_type operator * () const { return make_element(); } + + auto operator [] (jsize i) requires object_array { return get()[i]; } + + explicit operator bool() const { return handle() != nullptr; } + element_type get() const { return make_element(); } + + auto handle() const + { + return static_cast(object); + } + + void reset(jobject h = nullptr) + { + ref(h).swap(*this); + } + + auto release() + { + auto temp(static_cast(object)); + object = nullptr; + return temp; + } + + void swap (ref& r) + { + using std::swap; + swap (object, r.object); + swap (scp, r.scp); + } + +private: + + template + friend ref static_pointer_cast(ref&& r); + + ref(jobject h, scope s) noexcept : object(h), scp(s) {} + +private: + + element_type make_element() const + { + return detail::befriend(handle()); + } + + jobject object; + scope scp; + +}; + +// C++20 Class Template Argument Deduction for Alias Templates +// +// MSVC - VS 2019 16.7 +// GCC - 10 +// Clang - not supported as of clang 17 + +#if 0 // __cpp_deduction_guides >= 201907L +template +ref(ref) -> ref; +#endif + +template > +using local_ref = ref; + +template > +using global_ref = ref; + +template > +using weak_ref = ref; + +template +inline void swap(ref& a, ref& b) +{ + a.swap(b); +} + +template +inline bool operator ==(const ref& a, const ref& b) +{ + return detail::env()->IsSameObject(a.handle(), b.handle()); +} + +template +inline bool operator !=(const ref& a, const ref& b) +{ + return !(a == b); +} + +// global_ref used as static object +// Disable reset() in destructor, as it happens too late for JNI calls. +// Could instead register ref in a global list to call reset() during OnUnload(). + +template > +class static_global_ref : public global_ref +{ +public: + + using global_ref::global_ref; + + ~static_global_ref() + { + global_ref::release(); + } +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_REF_H diff --git a/source/scapix/jni/signature.h b/source/scapix/jni/signature.h new file mode 100644 index 0000000..4475dc9 --- /dev/null +++ b/source/scapix/jni/signature.h @@ -0,0 +1,72 @@ +/* + scapix/jni/signature.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_SIGNATURE_H +#define SCAPIX_JNI_SIGNATURE_H + +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +struct signature +{ + static constexpr auto value = "L" + class_name_v + ";"; +}; + +template<> struct signature { static constexpr fixed_string value = "V"; }; +template<> struct signature { static constexpr fixed_string value = "Z"; }; +template<> struct signature { static constexpr fixed_string value = "B"; }; +template<> struct signature { static constexpr fixed_string value = "C"; }; +template<> struct signature { static constexpr fixed_string value = "S"; }; +template<> struct signature { static constexpr fixed_string value = "I"; }; +template<> struct signature { static constexpr fixed_string value = "J"; }; +template<> struct signature { static constexpr fixed_string value = "F"; }; +template<> struct signature { static constexpr fixed_string value = "D"; }; + +template +struct signature> +{ + static constexpr auto value = signature_v; +}; + +template +struct signature> +{ + static constexpr auto value = "[" + signature_v; +}; + +template +struct signature +{ + static constexpr auto value = signature_v>; +}; + +template +struct signature> +{ + static constexpr auto value = signature_v; +}; + +template +struct signature> +{ + static constexpr auto value = signature_v; +}; + +template +struct signature +{ + static constexpr auto value = ("(" + ... + signature_v) + (")" + signature_v); +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_SIGNATURE_H diff --git a/source/scapix/jni/string.h b/source/scapix/jni/string.h new file mode 100644 index 0000000..d528740 --- /dev/null +++ b/source/scapix/jni/string.h @@ -0,0 +1,184 @@ +/* + scapix/jni/string.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +// outside of include guard +#include + +#ifndef SCAPIX_JNI_STRING_H +#define SCAPIX_JNI_STRING_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +template +class string_chars +{ +public: + + typedef Char value_type; + typedef std::make_unsigned::type size_type; + typedef jsize difference_type; +// typedef value_type* pointer; + typedef const value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef value_type* iterator; + typedef const value_type* const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + string_chars(const string_chars&) = delete; + string_chars& operator=(const string_chars&) = delete; + + string_chars(string_chars&& c) : string_(c.string_), is_copy_(c.is_copy_), data_(c.data_), size_(c.size_) { c.data_ = nullptr; } + string_chars& operator=(string_chars&& c) = delete; // { c.swap(*this); return *this; } + + ~string_chars() { release(); } + + pointer data() { return data_; } + const_pointer data() const { return data_; } + + size_type size() const { return size_; } + bool empty() const { return size() == 0; } + + iterator begin() { return data(); } + const_iterator begin() const { return data(); } + const_iterator cbegin() const { return data(); } + + iterator end() { return data() + size(); } + const_iterator end() const { return data() + size(); } + const_iterator cend() const { return data() + size(); } + + reverse_iterator rbegin() { return data() + size(); } + const_reverse_iterator rbegin() const { return data() + size(); } + const_reverse_iterator crbegin() const { return data() + size(); } + + reverse_iterator rend() { return data(); } + const_reverse_iterator rend() const { return data(); } + const_reverse_iterator crend() const { return data(); } + + reference operator[](size_type pos) { return data()[pos]; } + const_reference operator[](size_type pos) const { return data()[pos]; } + + reference at(size_type pos) + { + if (pos >= size()) + throw std::out_of_range("string_chars"); + + return (*this)[pos]; + } + + const_reference at(size_type pos) const + { + if (pos >= size()) + throw std::out_of_range("string_chars"); + + return (*this)[pos]; + } + + jboolean is_copy() const + { + return is_copy_; + } + + void release() + { + if (data_) + { + detail::api::string::release_chars(string_, data_); + data_ = nullptr; + } + } + + std::basic_string_view view() const + { + return std::basic_string_view(data(), size()); + } + +private: + + friend class string; + + string_chars(jstring string, jsize size) : + string_(string), + data_(detail::api::string::get_chars(string_, &is_copy_)), + size_(size) + { + } + +private: + + jstring string_; + jboolean is_copy_; + pointer data_; + size_type size_; + +}; + +class string : public object<"java/lang/String"> +{ +public: + + static local_ref new_object(const jchar* buf, jsize len) + { + jstring str = detail::env()->NewString(buf, len); + detail::check_exception(); + return local_ref(str); + } + + static local_ref new_object(const char* buf) + { + jstring str = detail::env()->NewStringUTF(buf); + detail::check_exception(); + return local_ref(str); + } + + template + jsize length() const + { + return detail::api::string::length(handle()); + } + + template + string_chars chars() const + { + static_assert(!std::is_same_v || Lock != lock::critical, "Unsupported parameter combination: "); + return string_chars(handle(), length()); + } + + template + string_chars chars(jsize size) const + { + static_assert(!std::is_same_v || Lock != lock::critical, "Unsupported parameter combination: "); + assert(size <= length()); + return string_chars(handle(), size); + } + + template + void get_region(jsize start, jsize len, Char* buf) const + { + detail::api::string::get_region(handle(), start, len, buf); + detail::check_exception(); + } + +protected: + + string(handle_type h) : object(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_STRING_H diff --git a/source/scapix/jni/struct.h b/source/scapix/jni/struct.h new file mode 100644 index 0000000..b09ef56 --- /dev/null +++ b/source/scapix/jni/struct.h @@ -0,0 +1,37 @@ +/* + scapix/jni/struct.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_STRUCT_H +#define SCAPIX_JNI_STRUCT_H + +#include +#include + +namespace scapix::jni { + +template +struct struct_; + +template +struct is_struct : std::false_type {}; + +template +struct is_struct::fields>> : std::true_type {}; + +template +constexpr bool is_struct_v = is_struct::value; + +template +struct field +{ + using type = Type; + static constexpr auto name = Name; + static constexpr auto ptr = Ptr; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_STRUCT_H diff --git a/source/scapix/jni/throwable.h b/source/scapix/jni/throwable.h new file mode 100644 index 0000000..a003a70 --- /dev/null +++ b/source/scapix/jni/throwable.h @@ -0,0 +1,31 @@ +/* + scapix/jni/throwable.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_THROWABLE_H +#define SCAPIX_JNI_THROWABLE_H + +#include + +namespace scapix::jni { + +class throwable : public object<"java/lang/Throwable"> +{ +public: + + jint throw_() const + { + return detail::env()->Throw(handle()); + } + +protected: + + throwable(handle_type h) : object(h) {} + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_THROWABLE_H diff --git a/source/scapix/jni/type_traits.h b/source/scapix/jni/type_traits.h new file mode 100644 index 0000000..98668a9 --- /dev/null +++ b/source/scapix/jni/type_traits.h @@ -0,0 +1,170 @@ +/* + scapix/jni/type_traits.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_TYPE_TRAITS_H +#define SCAPIX_JNI_TYPE_TRAITS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +// reference + +template +concept reference = requires +{ + requires std::is_class_v>; + class_name_v>; + typename base_classes_t>; +}; + +// primitive + +template +concept integral = + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as; + +template +concept floating_point = + std::same_as || + std::same_as; + +template +concept numeric = integral || floating_point; + +template +concept primitive = numeric || std::same_as; + +// array_element + +template +concept array_element = reference || primitive; + +// is_array + +template +struct is_array : std::integral_constant[0] == '['> {}; + +template +constexpr bool is_array_v = is_array::value; + +template +struct is_object_array : std::integral_constant[0] == '[' && (class_name_v[1] == 'L' || class_name_v[1] == '[')> {}; + +template +constexpr bool is_object_array_v = is_object_array::value; + +template +concept object_array = is_object_array_v; + +// handle_type + +namespace detail { + +template +struct handle_type_info +{ + static constexpr auto class_name = ClassName; + using handle_type = HandleType; +}; + +using handle_types = std::tuple +< + handle_type_info<"java/lang/Object", jobject>, // must be first + handle_type_info<"java/lang/Class", jclass>, + handle_type_info<"java/lang/String", jstring>, + handle_type_info<"java/lang/Throwable", jthrowable>, + handle_type_info<"[Z", jbooleanArray>, + handle_type_info<"[B", jbyteArray>, + handle_type_info<"[C", jcharArray>, + handle_type_info<"[S", jshortArray>, + handle_type_info<"[I", jintArray>, + handle_type_info<"[J", jlongArray>, + handle_type_info<"[F", jfloatArray>, + handle_type_info<"[D", jdoubleArray>, + handle_type_info<"[", jobjectArray> // uses "begins_with" compare, must be last +>; + +template +constexpr std::size_t handle_type() +{ + auto class_name = class_name_v; + using bases = base_classes_t; + + std::size_t result = 0; + + meta::for_each>>([&](auto index) + { + if (std::tuple_element_t::class_name == class_name) + result = index; + }); + + if (!result) + { + if (class_name[0] == '[') + { + result = std::tuple_size_v - 1; + } + else if constexpr (std::tuple_size_v != 0) + { + result = handle_type>(); + } + } + + return result; +} + +} // namespace detail + +template +using handle_type_t = typename std::tuple_element_t>(), detail::handle_types>::handle_type; + +// is_convertible_object + +template +struct is_convertible_object; + +template +constexpr bool is_convertible_object_v = is_convertible_object, element_type_t>::value; + +template +concept convertible_object = is_convertible_object_v; + +template +struct is_convertible_object +{ + template + struct is_convertible : is_convertible_object {}; + + static constexpr bool value = + class_name_v == class_name_v || + meta::any_of_v, is_convertible> + ; +}; + +template +struct is_convertible_object +{ + static constexpr bool value = is_convertible_object_v; +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_TYPE_TRAITS_H diff --git a/source/scapix/jni/vm.h b/source/scapix/jni/vm.h new file mode 100644 index 0000000..0cbc6fb --- /dev/null +++ b/source/scapix/jni/vm.h @@ -0,0 +1,39 @@ +/* + scapix/jni/vm.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_VM_H +#define SCAPIX_JNI_VM_H + +#include +#include +#include +#include + +namespace scapix::jni { + +inline void create_vm(JavaVMInitArgs* args) +{ + auto r = JNI_CreateJavaVM(&detail::jvm_ptr, detail::env_.addr(), args); + + if (r != JNI_OK) + throw std::runtime_error("JNI_CreateJavaVM failed: " + std::to_string(r)); + + detail::init(); +} + +inline jint destroy_vm() noexcept +{ + auto r = detail::jvm()->DestroyJavaVM(); + + detail::jvm_ptr = nullptr; + detail::env_.ptr = nullptr; + + return r; +} + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_VM_H diff --git a/source/scapix/jni/vm_exception.h b/source/scapix/jni/vm_exception.h new file mode 100644 index 0000000..04d47a6 --- /dev/null +++ b/source/scapix/jni/vm_exception.h @@ -0,0 +1,79 @@ +/* + scapix/jni/vm_exception.h + + Copyright (c) 2019-2024 Boris Rasin (boris@scapix.com) +*/ + +#ifndef SCAPIX_JNI_VM_EXCEPTION_H +#define SCAPIX_JNI_VM_EXCEPTION_H + +#include +#include +#include +#include +#include +#include + +namespace scapix::jni { + +class vm_exception : public std::exception +{ +public: + + const char* what() const noexcept override + { + if (description.empty()) + { + try + { + description = prefix + class_name() + ": " + message(); + } + catch (...) + { + return prefix; + } + } + + return description.c_str(); + } + + template T> + ref instance_of() const noexcept + { + return exception->instance_of(); + } + + ref get() const noexcept + { + return exception; + } + +private: + + friend void detail::throw_exception(jthrowable e); + friend void detail::throw_exception_nested(jthrowable e); + + explicit vm_exception(local_ref&& e) : exception(std::move(e)) {} + +private: + + inline constexpr static char prefix[] = "jni::vm_exception: "; + + std::string class_name() const + { + return exception->get_object_class()->call_method<"getName", ref()>(); + } + + std::string message() const + { + return exception->call_method<"getMessage", ref()>(); + } + + local_ref exception; + mutable std::string description; + +}; + +} // namespace scapix::jni + +#endif // SCAPIX_JNI_VM_EXCEPTION_H