├── .github └── workflows │ └── jnipp.yml ├── .gitignore ├── .mailmap ├── Android.bp ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── android ├── AndroidManifest.xml └── CMakeLists.txt ├── build.gradle ├── changes ├── additions │ └── .gitkeep ├── changes │ ├── .gitkeep │ └── pr.40.md └── fixes │ ├── .gitkeep │ └── pr.49.md ├── jnipp.cpp ├── jnipp.h ├── jnipp.sln ├── jnipp.vcxproj ├── jnipp.vcxproj.filters ├── makefile ├── proclamation.json └── tests ├── CMakeLists.txt ├── external_create.cpp ├── external_detach.cpp ├── main.cpp ├── testing.h ├── tests.vcxproj └── tests.vcxproj.filters /.github/workflows/jnipp.yml: -------------------------------------------------------------------------------- 1 | name: jnipp 2 | on: [push] 3 | jobs: 4 | windows: 5 | name: windows 6 | runs-on: windows-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | name: Checkout project 10 | - uses: actions/setup-java@v4 11 | with: 12 | distribution: "temurin" 13 | java-version: "8" 14 | - name: Add msbuild to PATH 15 | uses: microsoft/setup-msbuild@v2 16 | with: 17 | vs-version: 16.5 18 | - name: Build 19 | run: msbuild jnipp.sln /p:Platform=X64 /m 20 | - name: Test 21 | run: .\x64\Debug\tests.exe 22 | 23 | - name: CMake Configure 24 | run: cmake -A x64 -S . -B build 25 | - name: CMake Build 26 | run: cmake --build build --config Debug 27 | - name: CMake Test 28 | run: ctest -V -C Debug --output-on-failure 29 | working-directory: build 30 | 31 | macos: 32 | name: macos 33 | runs-on: macos-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | name: Checkout project 37 | - uses: actions/setup-java@v4 38 | with: 39 | distribution: "temurin" 40 | java-version: "8" 41 | - name: Build 42 | run: make 43 | - name: Test 44 | run: ./test 45 | 46 | - name: CMake Configure 47 | run: cmake -G "Unix Makefiles" -S . -B build 48 | - name: CMake Build 49 | run: make 50 | working-directory: build 51 | - name: CMake Test 52 | run: ctest -V --output-on-failure 53 | working-directory: build 54 | 55 | ubuntu: 56 | name: ubuntu 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v4 60 | name: Checkout project 61 | - uses: actions/setup-java@v4 62 | with: 63 | distribution: "temurin" 64 | java-version: "8" 65 | - name: Build 66 | run: make 67 | - name: Test 68 | run: ./test 69 | 70 | - name: CMake Configure 71 | run: cmake -G "Unix Makefiles" -S . -B build 72 | - name: CMake Build 73 | run: make 74 | working-directory: build 75 | - name: CMake Test 76 | run: ctest -V --output-on-failure 77 | working-directory: build 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | 3 | !.gitconfig 4 | !.mailmap 5 | !.gitkeep 6 | !.github 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # DNX 50 | project.lock.json 51 | artifacts/ 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opendb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | *.VC.db 90 | *.VC.VC.opendb 91 | 92 | # Visual Studio profiler 93 | *.psess 94 | *.vsp 95 | *.vspx 96 | *.sap 97 | 98 | # TFS 2012 Local Workspace 99 | $tf/ 100 | 101 | # Guidance Automation Toolkit 102 | *.gpState 103 | 104 | # ReSharper is a .NET coding add-in 105 | _ReSharper*/ 106 | *.[Rr]e[Ss]harper 107 | *.DotSettings.user 108 | 109 | # JustCode is a .NET coding add-in 110 | .JustCode 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # NCrunch 119 | _NCrunch_* 120 | .*crunch*.local.xml 121 | nCrunchTemp_* 122 | 123 | # MightyMoose 124 | *.mm.* 125 | AutoTest.Net/ 126 | 127 | # Web workbench (sass) 128 | .sass-cache/ 129 | 130 | # Installshield output folder 131 | [Ee]xpress/ 132 | 133 | # DocProject is a documentation generator add-in 134 | DocProject/buildhelp/ 135 | DocProject/Help/*.HxT 136 | DocProject/Help/*.HxC 137 | DocProject/Help/*.hhc 138 | DocProject/Help/*.hhk 139 | DocProject/Help/*.hhp 140 | DocProject/Help/Html2 141 | DocProject/Help/html 142 | 143 | # Click-Once directory 144 | publish/ 145 | 146 | # Publish Web Output 147 | *.[Pp]ublish.xml 148 | *.azurePubxml 149 | # TODO: Comment the next line if you want to checkin your web deploy settings 150 | # but database connection strings (with potential passwords) will be unencrypted 151 | *.pubxml 152 | *.publishproj 153 | 154 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 155 | # checkin your Azure Web App publish settings, but sensitive information contained 156 | # in these scripts will be unencrypted 157 | PublishScripts/ 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | # NuGet v3's project.json files produces more ignoreable files 168 | *.nuget.props 169 | *.nuget.targets 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Windows Store app package directories and files 180 | AppPackages/ 181 | BundleArtifacts/ 182 | Package.StoreAssociation.xml 183 | _pkginfo.txt 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | *.out 254 | *.o 255 | /test 256 | 257 | # Gradle 258 | .gradle/ 259 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-2024, Rylie Pavlik 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Mitchell Dowd 5 | Mitchell Dowd 6 | Mitchell Dowd 7 | Mitchell Dowd 8 | 9 | Rylie Pavlik 10 | Rylie Pavlik 11 | Rylie Pavlik 12 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | 2 | cc_library_headers { 3 | name: "libjnipp-headers", 4 | export_include_dirs: ["."], 5 | vendor_available: true, 6 | } 7 | 8 | cc_library_static { 9 | name: "libjnipp", 10 | 11 | srcs: [ 12 | "jnipp.cpp", 13 | ], 14 | 15 | cppflags: [ "-fexceptions" ], 16 | header_libs: [ 17 | "libjnipp-headers", 18 | ], 19 | export_header_lib_headers: [ 20 | "libjnipp-headers" 21 | ], 22 | vendor_available: true, 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for jnipp 2 | 3 | ## jnipp 1.0.0 (25-October-2023) 4 | 5 | This release is somewhat of a formality, as jnipp has been used in production 6 | environments for some time. It was prompted by the need for an API breaking 7 | change to ensure continued compatibility with libc++ as they move to more 8 | strictly follow the C++ standard. 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021, Collabora, Ltd. 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | cmake_minimum_required(VERSION 3.10.2) 6 | project(jnipp) 7 | 8 | find_package(JNI REQUIRED) 9 | include(CTest) 10 | 11 | add_library(jnipp jnipp.cpp) 12 | target_include_directories( 13 | jnipp 14 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} 15 | PRIVATE ${JNI_INCLUDE_DIRS}) 16 | target_compile_features(jnipp PUBLIC cxx_std_11) 17 | target_link_libraries(jnipp PUBLIC ${CMAKE_DL_LIBS}) 18 | 19 | add_subdirectory(tests) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mitchell Dowd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Native Interface for C++ 2 | 3 | ## Overview 4 | 5 | JNIPP is just a C++ wrapper for the standard Java Native Interface (JNI). It 6 | tries to take some of the long-winded annoyance out of integrating your Java 7 | and C++ code. 8 | 9 | While this project has so far just been a utility library for my own usage, 10 | it seems to have caught the eye of some others who have also been looking for 11 | a suitable C++ JNI layer. If you have feature requests, do not hesitate to 12 | submit them as Github issues. Please be descriptive in your feature request. 13 | The more useful information you provide - along with justification on why it 14 | should be implemented - the more likely it is that I will add your feature. 15 | 16 | ## Requirements 17 | 18 | To compile you will need: 19 | 20 | - A C++11 compatible compiler 21 | - An installation of the Java Development Kit (JDK) 22 | - The `JAVA_HOME` environment variable, directed to your JDK installation. 23 | 24 | ## Usage 25 | 26 | > For comprehensive examples on how to use *jnipp*, see the `tests` project 27 | > in the project source code. 28 | 29 | There are two situations where the Java Native Interface would be needed. 30 | 31 | - A Java application calling C/C++ functions; or 32 | - A C/C++ application calling Java methods 33 | 34 | ### Calling Java from C++ 35 | 36 | The following is an example of calling Java from C++. 37 | 38 | ```C++ 39 | #include 40 | 41 | int main() 42 | { 43 | // An instance of the Java VM needs to be created. 44 | jni::Vm vm; 45 | 46 | // Create an instance of java.lang.Integer 47 | jni::Class Integer = jni::Class("java/lang/Integer"); 48 | jni::Object i = Integer.newInstance("1000"); 49 | 50 | // Call the `toString` method on that integer 51 | std::string str = i.call("toString"); 52 | 53 | // The Java VM is automatically destroyed when it goes out of scope. 54 | return 0; 55 | } 56 | ``` 57 | 58 | ### Calling C++ from Java 59 | 60 | Consider a basic Java program: 61 | 62 | ```Java 63 | package com.example; 64 | 65 | class Demo { 66 | public int value; 67 | 68 | public static void main(String[] args) { 69 | Demo demo = new Demo(); 70 | demo.value = 1000; 71 | demo.run(); 72 | } 73 | 74 | public native void run(); 75 | } 76 | ``` 77 | 78 | A matching C++ library which uses *jnipp* could look like: 79 | 80 | ```C++ 81 | #include 82 | #include 83 | 84 | /* 85 | The signature here is defind by the JNI standard, so must be adhered to. 86 | Although, to prevent pollution of the global namespace, the JNIEnv and 87 | jobject types defind by the standard JNI have been placed into the 88 | jni namespace. 89 | */ 90 | extern "C" void Java_com_example_Demo_run(jni::JNIEnv* env, jni::jobject obj) 91 | { 92 | // jnipp only needs initialising once, but it doesn't hurt to do it again. 93 | jni::init(env); 94 | 95 | // Capture the supplied object. 96 | jni::Object demo(obj); 97 | 98 | // Print the contents of the `value` field to stdout. 99 | std::cout << demo.get("value") << std::endl; 100 | } 101 | ``` 102 | 103 | ## Configuration 104 | 105 | By default, *jnipp* uses std::runtime_error as the base exception class. If you wish, 106 | you can define `JNIPP_EXCEPTION_CLASS` to be the exception class you wish to use, before 107 | including `jnipp.h`. It just needs a `const char*` constructor. 108 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.4.1) 2 | 3 | GET_FILENAME_COMPONENT(root_dir "../.." ABSOLUTE) 4 | 5 | LINK_DIRECTORIES("${root_dir}/${ANDROID_ABI}") 6 | 7 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${root_dir}/${ANDROID_ABI}") 8 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_STL=c++_static -std=gnu++11 -fexceptions") 9 | 10 | FILE(GLOB sources ../*.cpp ../common/*.cpp ../android/*.cpp) 11 | 12 | ADD_LIBRARY(jnipp SHARED ${sources}) 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion '28.0.3' 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 26 10 | } 11 | 12 | buildTypes { 13 | release { 14 | minifyEnabled = false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt') 16 | } 17 | } 18 | 19 | sourceSets { 20 | main { 21 | manifest.srcFile 'android/AndroidManifest.xml' 22 | } 23 | } 24 | 25 | externalNativeBuild { 26 | cmake { 27 | path 'android/CMakeLists.txt' 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /changes/additions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchdowd/jnipp/cdd6293fca985993129f5ef5441709fc49ee507f/changes/additions/.gitkeep -------------------------------------------------------------------------------- /changes/changes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchdowd/jnipp/cdd6293fca985993129f5ef5441709fc49ee507f/changes/changes/.gitkeep -------------------------------------------------------------------------------- /changes/changes/pr.40.md: -------------------------------------------------------------------------------- 1 | Stop using `std::basic_string` internally, because it is `std::basic_string` on some platforms, and this specialization is not technically provided by the standard library. libc++ is removing support for it, so this internal change is required to continue to compile against libc++. In place of it, use `std::vector`. 2 | -------------------------------------------------------------------------------- /changes/fixes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchdowd/jnipp/cdd6293fca985993129f5ef5441709fc49ee507f/changes/fixes/.gitkeep -------------------------------------------------------------------------------- /changes/fixes/pr.49.md: -------------------------------------------------------------------------------- 1 | Do not allow exceptions to escape destructors. 2 | -------------------------------------------------------------------------------- /jnipp.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | # define WIN32_LEAN_AND_MEAN 1 3 | 4 | // Windows Dependencies 5 | # include 6 | #else 7 | // UNIX Dependencies 8 | # include 9 | # include 10 | # include 11 | # include 12 | #endif 13 | 14 | // External Dependencies 15 | #include 16 | 17 | // Standard Dependencies 18 | #include 19 | #include 20 | #include 21 | 22 | // Local Dependencies 23 | #include "jnipp.h" 24 | 25 | namespace jni 26 | { 27 | // Static Variables 28 | static std::atomic_bool isVm(false); 29 | static JavaVM* javaVm = nullptr; 30 | 31 | static bool getEnv(JavaVM *vm, JNIEnv **env) { 32 | return vm->GetEnv((void **)env, JNI_VERSION_1_2) == JNI_OK; 33 | } 34 | 35 | static bool isAttached(JavaVM *vm) { 36 | JNIEnv *env = nullptr; 37 | return getEnv(vm, &env); 38 | } 39 | /** 40 | Maintains the lifecycle of a JNIEnv. 41 | */ 42 | class ScopedEnv final 43 | { 44 | public: 45 | ScopedEnv() noexcept : _vm(nullptr), _env(nullptr), _attached(false) {} 46 | ~ScopedEnv(); 47 | 48 | // Caution - throws if VM is nullptr! 49 | void init(JavaVM* vm); 50 | JNIEnv* get() const noexcept { return _env; } 51 | 52 | private: 53 | // Instance Variables 54 | JavaVM* _vm; 55 | JNIEnv* _env; 56 | bool _attached; ///< Manually attached, as opposed to already attached. 57 | }; 58 | 59 | ScopedEnv::~ScopedEnv() 60 | { 61 | if (_vm && _attached) 62 | _vm->DetachCurrentThread(); 63 | } 64 | 65 | void ScopedEnv::init(JavaVM* vm) 66 | { 67 | if (_env != nullptr) 68 | return; 69 | 70 | if (vm == nullptr) 71 | throw InitializationException("JNI not initialized"); 72 | 73 | if (!getEnv(vm, &_env)) 74 | { 75 | #ifdef __ANDROID__ 76 | if (vm->AttachCurrentThread(&_env, nullptr) != 0) 77 | #else 78 | if (vm->AttachCurrentThread((void**)&_env, nullptr) != 0) 79 | #endif 80 | throw InitializationException("Could not attach JNI to thread"); 81 | 82 | _attached = true; 83 | } 84 | 85 | _vm = vm; 86 | } 87 | 88 | /* 89 | Helper Functions 90 | */ 91 | 92 | #ifdef _WIN32 93 | 94 | static bool fileExists(const std::string& filePath) 95 | { 96 | DWORD attr = ::GetFileAttributesA(filePath.c_str()); 97 | 98 | return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY); 99 | } 100 | 101 | #else 102 | 103 | /** 104 | Convert from a UTF-16 Java string to a UTF-32 string. 105 | */ 106 | std::wstring toWString(const jchar* str, jsize length) 107 | { 108 | std::wstring result; 109 | 110 | result.reserve(length); 111 | 112 | for (jsize i = 0; i < length; ++i) 113 | { 114 | wchar_t ch = str[i]; 115 | 116 | // Check for a two-segment character. 117 | if (ch >= wchar_t(0xD800) && ch <= wchar_t(0xDBFF)) { 118 | if (i + 1 >= length) 119 | break; 120 | 121 | // Create a single, 32-bit character. 122 | ch = (ch - wchar_t(0xD800)) << 10; 123 | ch += str[i++] - wchar_t(0x1DC00); 124 | } 125 | 126 | result += ch; 127 | } 128 | 129 | return result; 130 | } 131 | 132 | /** 133 | Convert from a UTF-32 string to a UTF-16 Java string. 134 | */ 135 | std::vector toJString(const wchar_t* str, size_t length) 136 | { 137 | std::vector result; 138 | 139 | result.reserve(length * 2); // Worst case scenario. 140 | 141 | for (size_t i = 0; i < length; ++i) 142 | { 143 | wchar_t ch = str[i]; 144 | 145 | // Check for multi-byte UTF-16 character. 146 | if (ch > wchar_t(0xFFFF)) { 147 | ch -= uint32_t(0x10000); 148 | 149 | // Add the first of the two-segment character. 150 | result.push_back(jchar(0xD800 + (ch >> 10))); 151 | ch = wchar_t(0xDC00) + (ch & 0x03FF); 152 | } 153 | 154 | result.push_back(jchar(ch)); 155 | } 156 | 157 | return result; 158 | } 159 | 160 | #endif // _WIN32 161 | 162 | static ScopedEnv &scopedEnvInstance() noexcept 163 | { 164 | static thread_local ScopedEnv env; 165 | return env; 166 | } 167 | 168 | // may return nullptr, beware! 169 | JNIEnv *env_noexcept() noexcept 170 | { 171 | ScopedEnv& env = scopedEnvInstance(); 172 | if (env.get() != nullptr && !isAttached(javaVm)) 173 | { 174 | // we got detached, so clear it. 175 | // will be re-populated from static javaVm below. 176 | env = ScopedEnv{}; 177 | } 178 | 179 | if (env.get() == nullptr && javaVm != nullptr) 180 | { 181 | env.init(javaVm); 182 | } 183 | 184 | return env.get(); 185 | } 186 | 187 | JNIEnv* env() 188 | { 189 | JNIEnv *ret = env_noexcept(); 190 | if (ret == nullptr) 191 | { 192 | throw InitializationException("JNI not initialized"); 193 | } 194 | 195 | return ret; 196 | } 197 | 198 | static jclass findClass(const char* name) 199 | { 200 | jclass ref = env()->FindClass(name); 201 | 202 | if (ref == nullptr) 203 | { 204 | env()->ExceptionClear(); 205 | throw NameResolutionException(name); 206 | } 207 | 208 | return ref; 209 | } 210 | 211 | static void handleJavaExceptions() 212 | { 213 | JNIEnv* env = jni::env(); 214 | 215 | jthrowable exception = env->ExceptionOccurred(); 216 | 217 | if (exception != nullptr) 218 | { 219 | Object obj(exception, Object::Temporary); 220 | 221 | env->ExceptionClear(); 222 | std::string msg = obj.call("toString"); 223 | throw InvocationException(msg.c_str()); 224 | } 225 | } 226 | 227 | static std::string toString(jobject handle, bool deleteLocal = true) 228 | { 229 | std::string result; 230 | 231 | if (handle != nullptr) 232 | { 233 | JNIEnv* env = jni::env(); 234 | 235 | const char* chars = env->GetStringUTFChars(jstring(handle), nullptr); 236 | result.assign(chars, env->GetStringUTFLength(jstring(handle))); 237 | env->ReleaseStringUTFChars(jstring(handle), chars); 238 | 239 | if (deleteLocal) 240 | env->DeleteLocalRef(handle); 241 | } 242 | 243 | return result; 244 | } 245 | 246 | static std::wstring toWString(jobject handle, bool deleteLocal = true) 247 | { 248 | std::wstring result; 249 | 250 | if (handle != nullptr) 251 | { 252 | JNIEnv* env = jni::env(); 253 | 254 | const jchar* chars = env->GetStringChars(jstring(handle), nullptr); 255 | #ifdef _WIN32 256 | result.assign((const wchar_t*) chars, env->GetStringLength(jstring(handle))); 257 | #else 258 | result = toWString(chars, env->GetStringLength(jstring(handle))); 259 | #endif 260 | env->ReleaseStringChars(jstring(handle), chars); 261 | 262 | if (deleteLocal) 263 | env->DeleteLocalRef(handle); 264 | } 265 | 266 | return result; 267 | } 268 | 269 | 270 | /* 271 | Stand-alone Function Implementations 272 | */ 273 | 274 | void init(JNIEnv* env) 275 | { 276 | bool expected = false; 277 | 278 | if (isVm.compare_exchange_strong(expected, true)) 279 | { 280 | if (javaVm == nullptr && env->GetJavaVM(&javaVm) != 0) 281 | throw InitializationException("Could not acquire Java VM"); 282 | } 283 | } 284 | 285 | void init(JavaVM* vm) { 286 | bool expected = false; 287 | 288 | if (isVm.compare_exchange_strong(expected, true)) 289 | { 290 | javaVm = vm; 291 | } 292 | } 293 | /* 294 | Object Implementation 295 | */ 296 | 297 | Object::Object() noexcept : _handle(nullptr), _class(nullptr), _isGlobal(false) 298 | { 299 | } 300 | 301 | Object::Object(const Object& other) : _handle(nullptr), _class(nullptr), _isGlobal(!other.isNull()) 302 | { 303 | if (!other.isNull()) 304 | _handle = env()->NewGlobalRef(other._handle); 305 | } 306 | 307 | Object::Object(Object&& other) noexcept : _handle(other._handle), _class(other._class), _isGlobal(other._isGlobal) 308 | { 309 | other._handle = nullptr; 310 | other._class = nullptr; 311 | other._isGlobal = false; 312 | } 313 | 314 | Object::Object(jobject ref, int scopeFlags) : _handle(ref), _class(nullptr), _isGlobal((scopeFlags & Temporary) == 0) 315 | { 316 | if (!_isGlobal) 317 | return; 318 | 319 | JNIEnv* env = jni::env(); 320 | 321 | _handle = env->NewGlobalRef(ref); 322 | 323 | if (scopeFlags & DeleteLocalInput) 324 | env->DeleteLocalRef(ref); 325 | } 326 | 327 | Object::~Object() noexcept 328 | { 329 | JNIEnv* env = jni::env_noexcept(); 330 | if (env == nullptr) 331 | { 332 | // Better be empty. Cannot do anything useful. 333 | return; 334 | } 335 | 336 | if (_isGlobal && _handle != nullptr) 337 | env->DeleteGlobalRef(_handle); 338 | 339 | if (_class != nullptr) 340 | env->DeleteGlobalRef(_class); 341 | } 342 | 343 | Object& Object::operator=(const Object& other) 344 | { 345 | if (_handle != other._handle) 346 | { 347 | JNIEnv* env = jni::env(); 348 | 349 | // Ditch the old reference. 350 | if (_isGlobal) 351 | env->DeleteGlobalRef(_handle); 352 | if (_class != nullptr) 353 | env->DeleteGlobalRef(_class); 354 | 355 | // Assign the new reference. 356 | if ((_isGlobal = !other.isNull()) != false) 357 | _handle = env->NewGlobalRef(other._handle); 358 | 359 | _class = nullptr; 360 | } 361 | 362 | return *this; 363 | } 364 | 365 | bool Object::operator==(const Object& other) const 366 | { 367 | return env()->IsSameObject(_handle, other._handle) != JNI_FALSE; 368 | } 369 | 370 | Object& Object::operator=(Object&& other) 371 | { 372 | if (_handle != other._handle) 373 | { 374 | JNIEnv* env = jni::env(); 375 | 376 | // Ditch the old reference. 377 | if (_isGlobal) 378 | env->DeleteGlobalRef(_handle); 379 | if (_class != nullptr) 380 | env->DeleteGlobalRef(_class); 381 | 382 | // Assign the new reference. 383 | _handle = other._handle; 384 | _isGlobal = other._isGlobal; 385 | _class = other._class; 386 | 387 | other._handle = nullptr; 388 | other._isGlobal = false; 389 | other._class = nullptr; 390 | } 391 | 392 | return *this; 393 | } 394 | 395 | bool Object::isNull() const noexcept 396 | { 397 | return _handle == nullptr || env()->IsSameObject(_handle, nullptr); 398 | } 399 | 400 | void Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 401 | { 402 | env()->CallVoidMethodA(_handle, method, (jvalue*) args); 403 | handleJavaExceptions(); 404 | } 405 | 406 | bool Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 407 | { 408 | auto result = env()->CallBooleanMethodA(_handle, method, (jvalue*) args); 409 | handleJavaExceptions(); 410 | return result != 0; 411 | } 412 | 413 | bool Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 414 | { 415 | return env()->GetBooleanField(_handle, field) != 0; 416 | } 417 | 418 | template <> void Object::set(field_t field, const bool& value) 419 | { 420 | env()->SetBooleanField(_handle, field, value); 421 | } 422 | 423 | byte_t Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 424 | { 425 | auto result = env()->CallByteMethodA(_handle, method, (jvalue*) args); 426 | handleJavaExceptions(); 427 | return result; 428 | } 429 | 430 | wchar_t Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 431 | { 432 | auto result = env()->CallCharMethodA(_handle, method, (jvalue*) args); 433 | handleJavaExceptions(); 434 | return result; 435 | } 436 | 437 | short Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 438 | { 439 | auto result = env()->CallShortMethodA(_handle, method, (jvalue*) args); 440 | handleJavaExceptions(); 441 | return result; 442 | } 443 | 444 | int Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 445 | { 446 | auto result = env()->CallIntMethodA(_handle, method, (jvalue*) args); 447 | handleJavaExceptions(); 448 | return result; 449 | } 450 | 451 | long long Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 452 | { 453 | auto result = env()->CallLongMethodA(_handle, method, (jvalue*) args); 454 | handleJavaExceptions(); 455 | return result; 456 | } 457 | 458 | float Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 459 | { 460 | auto result = env()->CallFloatMethodA(_handle, method, (jvalue*) args); 461 | handleJavaExceptions(); 462 | return result; 463 | } 464 | 465 | double Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 466 | { 467 | auto result = env()->CallDoubleMethodA(_handle, method, (jvalue*) args); 468 | handleJavaExceptions(); 469 | return result; 470 | } 471 | 472 | std::string Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 473 | { 474 | auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); 475 | handleJavaExceptions(); 476 | return toString(result); 477 | } 478 | 479 | std::wstring Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 480 | { 481 | auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); 482 | handleJavaExceptions(); 483 | return toWString(result); 484 | } 485 | 486 | jni::Object Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 487 | { 488 | auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); 489 | handleJavaExceptions(); 490 | return Object(result, DeleteLocalInput); 491 | } 492 | 493 | jarray Object::callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const 494 | { 495 | auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); 496 | handleJavaExceptions(); 497 | return (jarray)result; 498 | } 499 | 500 | byte_t Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 501 | { 502 | return env()->GetByteField(_handle, field); 503 | } 504 | 505 | wchar_t Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 506 | { 507 | return env()->GetCharField(_handle, field); 508 | } 509 | 510 | short Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 511 | { 512 | return env()->GetShortField(_handle, field); 513 | } 514 | 515 | int Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 516 | { 517 | return env()->GetIntField(_handle, field); 518 | } 519 | 520 | long long Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 521 | { 522 | return env()->GetLongField(_handle, field); 523 | } 524 | 525 | float Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 526 | { 527 | return env()->GetFloatField(_handle, field); 528 | } 529 | 530 | double Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 531 | { 532 | return env()->GetDoubleField(_handle, field); 533 | } 534 | 535 | std::string Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 536 | { 537 | return toString(env()->GetObjectField(_handle, field)); 538 | } 539 | 540 | std::wstring Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 541 | { 542 | return toWString(env()->GetObjectField(_handle, field)); 543 | } 544 | 545 | Object Object::getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const 546 | { 547 | return Object(env()->GetObjectField(_handle, field), DeleteLocalInput); 548 | } 549 | 550 | template <> void Object::set(field_t field, const byte_t& value) 551 | { 552 | env()->SetByteField(_handle, field, value); 553 | } 554 | 555 | template <> void Object::set(field_t field, const wchar_t& value) 556 | { 557 | env()->SetCharField(_handle, field, value); 558 | } 559 | 560 | template <> void Object::set(field_t field, const short& value) 561 | { 562 | env()->SetShortField(_handle, field, value); 563 | } 564 | 565 | template <> void Object::set(field_t field, const int& value) 566 | { 567 | env()->SetIntField(_handle, field, value); 568 | } 569 | 570 | template <> void Object::set(field_t field, const long long& value) 571 | { 572 | env()->SetLongField(_handle, field, value); 573 | } 574 | 575 | template <> void Object::set(field_t field, const float& value) 576 | { 577 | env()->SetFloatField(_handle, field, value); 578 | } 579 | 580 | template <> void Object::set(field_t field, const double& value) 581 | { 582 | env()->SetDoubleField(_handle, field, value); 583 | } 584 | 585 | template <> void Object::set(field_t field, const std::string& value) 586 | { 587 | JNIEnv* env = jni::env(); 588 | 589 | jobject handle = env->NewStringUTF(value.c_str()); 590 | env->SetObjectField(_handle, field, handle); 591 | env->DeleteLocalRef(handle); 592 | } 593 | 594 | template <> void Object::set(field_t field, const std::wstring& value) 595 | { 596 | JNIEnv* env = jni::env(); 597 | 598 | #ifdef _WIN32 599 | jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); 600 | #else 601 | auto jstr = toJString(value.c_str(), value.length()); 602 | jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); 603 | #endif 604 | env->SetObjectField(_handle, field, handle); 605 | env->DeleteLocalRef(handle); 606 | } 607 | 608 | template <> void Object::set(field_t field, const wchar_t* const& value) 609 | { 610 | JNIEnv* env = jni::env(); 611 | #ifdef _WIN32 612 | jobject handle = env->NewString((const jchar*) value, jsize(std::wcslen(value))); 613 | #else 614 | auto jstr = toJString(value, std::wcslen(value)); 615 | jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); 616 | #endif 617 | env->SetObjectField(_handle, field, handle); 618 | env->DeleteLocalRef(handle); 619 | } 620 | 621 | template <> void Object::set(field_t field, const char* const& value) 622 | { 623 | JNIEnv* env = jni::env(); 624 | 625 | jobject handle = env->NewStringUTF(value); 626 | env->SetObjectField(_handle, field, handle); 627 | env->DeleteLocalRef(handle); 628 | } 629 | 630 | template <> void Object::set(field_t field, const Object& value) 631 | { 632 | env()->SetObjectField(_handle, field, value.getHandle()); 633 | } 634 | 635 | template <> void Object::set(field_t field, const Object* const& value) 636 | { 637 | env()->SetObjectField(_handle, field, value ? value->getHandle() : nullptr); 638 | } 639 | 640 | jclass Object::getClass() const 641 | { 642 | if (_class == nullptr) 643 | { 644 | JNIEnv* env = jni::env(); 645 | 646 | jclass classRef = env->GetObjectClass(_handle); 647 | _class = jclass(env->NewGlobalRef(classRef)); 648 | env->DeleteLocalRef(classRef); 649 | } 650 | 651 | return _class; 652 | } 653 | 654 | method_t Object::getMethod(const char* name, const char* signature) const 655 | { 656 | return Class(getClass(), Temporary).getMethod(name, signature); 657 | } 658 | 659 | method_t Object::getMethod(const char* nameAndSignature) const 660 | { 661 | return Class(getClass(), Temporary).getMethod(nameAndSignature); 662 | } 663 | 664 | field_t Object::getField(const char* name, const char* signature) const 665 | { 666 | return Class(getClass(), Temporary).getField(name, signature); 667 | } 668 | 669 | jobject Object::makeLocalReference() const 670 | { 671 | if (isNull()) 672 | return nullptr; 673 | return env()->NewLocalRef(_handle); 674 | } 675 | 676 | /* 677 | Class Implementation 678 | */ 679 | 680 | Class::Class(const char* name) : Object(findClass(name), DeleteLocalInput) 681 | { 682 | } 683 | 684 | Class::Class(jclass ref, int scopeFlags) : Object(ref, scopeFlags) 685 | { 686 | } 687 | 688 | Object Class::newInstance() const 689 | { 690 | method_t constructor = getMethod("", "()V"); 691 | jobject obj = env()->NewObject(getHandle(), constructor); 692 | 693 | handleJavaExceptions(); 694 | 695 | return Object(obj, Object::DeleteLocalInput); 696 | } 697 | 698 | field_t Class::getField(const char* name, const char* signature) const 699 | { 700 | jfieldID id = env()->GetFieldID(getHandle(), name, signature); 701 | 702 | if (id == nullptr) 703 | throw NameResolutionException(name); 704 | 705 | return id; 706 | } 707 | 708 | field_t Class::getStaticField(const char* name, const char* signature) const 709 | { 710 | jfieldID id = env()->GetStaticFieldID(getHandle(), name, signature); 711 | 712 | if (id == nullptr) 713 | throw NameResolutionException(name); 714 | 715 | return id; 716 | } 717 | 718 | method_t Class::getMethod(const char* name, const char* signature) const 719 | { 720 | jmethodID id = env()->GetMethodID(getHandle(), name, signature); 721 | 722 | if (id == nullptr) 723 | throw NameResolutionException(name); 724 | 725 | return id; 726 | } 727 | 728 | 729 | method_t Class::getMethod(const char* nameAndSignature) const 730 | { 731 | jmethodID id = nullptr; 732 | const char* sig = std::strchr(nameAndSignature, '('); 733 | 734 | if (sig != nullptr) 735 | return getMethod(std::string(nameAndSignature, sig - nameAndSignature).c_str(), sig); 736 | 737 | if (id == nullptr) 738 | throw NameResolutionException(nameAndSignature); 739 | 740 | return id; 741 | } 742 | 743 | method_t Class::getStaticMethod(const char* name, const char* signature) const 744 | { 745 | jmethodID id = env()->GetStaticMethodID(getHandle(), name, signature); 746 | 747 | if (id == nullptr) 748 | throw NameResolutionException(name); 749 | 750 | return id; 751 | } 752 | 753 | method_t Class::getStaticMethod(const char* nameAndSignature) const 754 | { 755 | jmethodID id = nullptr; 756 | const char* sig = std::strchr(nameAndSignature, '('); 757 | 758 | if (sig != nullptr) 759 | return getStaticMethod(std::string(nameAndSignature, sig - nameAndSignature).c_str(), sig); 760 | 761 | if (id == nullptr) 762 | throw NameResolutionException(nameAndSignature); 763 | 764 | return id; 765 | } 766 | 767 | Class Class::getParent() const 768 | { 769 | return Class(env()->GetSuperclass(getHandle()), DeleteLocalInput); 770 | } 771 | 772 | std::string Class::getName() const 773 | { 774 | return Object::call("getName"); 775 | } 776 | 777 | template <> bool Class::get(field_t field) const 778 | { 779 | return env()->GetStaticBooleanField(getHandle(), field) != 0; 780 | } 781 | 782 | template <> byte_t Class::get(field_t field) const 783 | { 784 | return env()->GetStaticByteField(getHandle(), field); 785 | } 786 | 787 | template <> wchar_t Class::get(field_t field) const 788 | { 789 | return env()->GetStaticCharField(getHandle(), field); 790 | } 791 | 792 | template <> short Class::get(field_t field) const 793 | { 794 | return env()->GetStaticShortField(getHandle(), field); 795 | } 796 | 797 | template <> int Class::get(field_t field) const 798 | { 799 | return env()->GetStaticIntField(getHandle(), field); 800 | } 801 | 802 | template <> long long Class::get(field_t field) const 803 | { 804 | return env()->GetStaticLongField(getHandle(), field); 805 | } 806 | 807 | template <> float Class::get(field_t field) const 808 | { 809 | return env()->GetStaticFloatField(getHandle(), field); 810 | } 811 | 812 | template <> double Class::get(field_t field) const 813 | { 814 | return env()->GetStaticDoubleField(getHandle(), field); 815 | } 816 | 817 | template <> std::string Class::get(field_t field) const 818 | { 819 | return toString(env()->GetStaticObjectField(getHandle(), field)); 820 | } 821 | 822 | template <> std::wstring Class::get(field_t field) const 823 | { 824 | return toWString(env()->GetStaticObjectField(getHandle(), field)); 825 | } 826 | 827 | template <> Object Class::get(field_t field) const 828 | { 829 | return Object(env()->GetStaticObjectField(getHandle(), field), DeleteLocalInput); 830 | } 831 | 832 | template <> void Class::set(field_t field, const bool& value) 833 | { 834 | env()->SetStaticBooleanField(getHandle(), field, value); 835 | } 836 | 837 | template <> void Class::set(field_t field, const byte_t& value) 838 | { 839 | env()->SetStaticByteField(getHandle(), field, value); 840 | } 841 | 842 | template <> void Class::set(field_t field, const wchar_t& value) 843 | { 844 | env()->SetStaticCharField(getHandle(), field, value); 845 | } 846 | 847 | template <> void Class::set(field_t field, const short& value) 848 | { 849 | env()->SetStaticShortField(getHandle(), field, value); 850 | } 851 | 852 | template <> void Class::set(field_t field, const int& value) 853 | { 854 | env()->SetStaticIntField(getHandle(), field, value); 855 | } 856 | 857 | template <> void Class::set(field_t field, const long long& value) 858 | { 859 | env()->SetStaticLongField(getHandle(), field, value); 860 | } 861 | 862 | template <> void Class::set(field_t field, const float& value) 863 | { 864 | env()->SetStaticFloatField(getHandle(), field, value); 865 | } 866 | 867 | template <> void Class::set(field_t field, const double& value) 868 | { 869 | env()->SetStaticDoubleField(getHandle(), field, value); 870 | } 871 | 872 | template <> void Class::set(field_t field, const Object& value) 873 | { 874 | env()->SetStaticObjectField(getHandle(), field, value.getHandle()); 875 | } 876 | 877 | template <> void Class::set(field_t field, const Object* const& value) 878 | { 879 | env()->SetStaticObjectField(getHandle(), field, value ? value->getHandle() : nullptr); 880 | } 881 | 882 | template <> void Class::set(field_t field, const std::string& value) 883 | { 884 | JNIEnv* env = jni::env(); 885 | 886 | jobject handle = env->NewStringUTF(value.c_str()); 887 | env->SetStaticObjectField(getHandle(), field, handle); 888 | env->DeleteLocalRef(handle); 889 | } 890 | 891 | template <> void Class::set(field_t field, const std::wstring& value) 892 | { 893 | JNIEnv* env = jni::env(); 894 | 895 | #ifdef _WIN32 896 | jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); 897 | #else 898 | auto jstr = toJString(value.c_str(), value.length()); 899 | jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); 900 | #endif 901 | env->SetStaticObjectField(getHandle(), field, handle); 902 | env->DeleteLocalRef(handle); 903 | } 904 | 905 | template <> void Class::callStaticMethod(method_t method, internal::value_t* args) const 906 | { 907 | env()->CallStaticVoidMethodA(getHandle(), method, (jvalue*) args); 908 | handleJavaExceptions(); 909 | } 910 | 911 | template <> bool Class::callStaticMethod(method_t method, internal::value_t* args) const 912 | { 913 | auto result = env()->CallStaticBooleanMethodA(getHandle(), method, (jvalue*) args); 914 | handleJavaExceptions(); 915 | return result != 0; 916 | } 917 | 918 | template <> byte_t Class::callStaticMethod(method_t method, internal::value_t* args) const 919 | { 920 | auto result = env()->CallStaticByteMethodA(getHandle(), method, (jvalue*) args); 921 | handleJavaExceptions(); 922 | return result; 923 | } 924 | 925 | template <> wchar_t Class::callStaticMethod(method_t method, internal::value_t* args) const 926 | { 927 | auto result = env()->CallStaticCharMethodA(getHandle(), method, (jvalue*) args); 928 | handleJavaExceptions(); 929 | return result; 930 | } 931 | 932 | template <> short Class::callStaticMethod(method_t method, internal::value_t* args) const 933 | { 934 | auto result = env()->CallStaticShortMethodA(getHandle(), method, (jvalue*) args); 935 | handleJavaExceptions(); 936 | return result; 937 | } 938 | 939 | template <> int Class::callStaticMethod(method_t method, internal::value_t* args) const 940 | { 941 | auto result = env()->CallStaticIntMethodA(getHandle(), method, (jvalue*) args); 942 | handleJavaExceptions(); 943 | return result; 944 | } 945 | 946 | template <> long long Class::callStaticMethod(method_t method, internal::value_t* args) const 947 | { 948 | auto result = env()->CallStaticLongMethodA(getHandle(), method, (jvalue*) args); 949 | handleJavaExceptions(); 950 | return result; 951 | } 952 | 953 | template <> float Class::callStaticMethod(method_t method, internal::value_t* args) const 954 | { 955 | auto result = env()->CallStaticFloatMethodA(getHandle(), method, (jvalue*) args); 956 | handleJavaExceptions(); 957 | return result; 958 | } 959 | 960 | template <> double Class::callStaticMethod(method_t method, internal::value_t* args) const 961 | { 962 | auto result = env()->CallStaticDoubleMethodA(getHandle(), method, (jvalue*) args); 963 | handleJavaExceptions(); 964 | return result; 965 | } 966 | 967 | template <> std::string Class::callStaticMethod(method_t method, internal::value_t* args) const 968 | { 969 | auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); 970 | handleJavaExceptions(); 971 | return toString(result); 972 | } 973 | 974 | template <> std::wstring Class::callStaticMethod(method_t method, internal::value_t* args) const 975 | { 976 | auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); 977 | handleJavaExceptions(); 978 | return toWString(result); 979 | } 980 | 981 | template <> jni::Object Class::callStaticMethod(method_t method, internal::value_t* args) const 982 | { 983 | auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); 984 | handleJavaExceptions(); 985 | return Object(result, DeleteLocalInput); 986 | } 987 | 988 | template <> void Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 989 | { 990 | env()->CallNonvirtualVoidMethodA(obj, getHandle(), method, (jvalue*) args); 991 | handleJavaExceptions(); 992 | } 993 | 994 | template <> bool Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 995 | { 996 | auto result = env()->CallNonvirtualBooleanMethodA(obj, getHandle(), method, (jvalue*) args); 997 | handleJavaExceptions(); 998 | return result != 0; 999 | } 1000 | 1001 | template <> byte_t Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1002 | { 1003 | auto result = env()->CallNonvirtualByteMethodA(obj, getHandle(), method, (jvalue*) args); 1004 | handleJavaExceptions(); 1005 | return result; 1006 | } 1007 | 1008 | template <> wchar_t Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1009 | { 1010 | auto result = env()->CallNonvirtualCharMethodA(obj, getHandle(), method, (jvalue*) args); 1011 | handleJavaExceptions(); 1012 | return result; 1013 | } 1014 | 1015 | template <> short Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1016 | { 1017 | auto result = env()->CallNonvirtualShortMethodA(obj, getHandle(), method, (jvalue*) args); 1018 | handleJavaExceptions(); 1019 | return result; 1020 | } 1021 | 1022 | template <> int Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1023 | { 1024 | auto result = env()->CallNonvirtualIntMethodA(obj, getHandle(), method, (jvalue*) args); 1025 | handleJavaExceptions(); 1026 | return result; 1027 | } 1028 | 1029 | template <> long long Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1030 | { 1031 | auto result = env()->CallNonvirtualLongMethodA(obj, getHandle(), method, (jvalue*) args); 1032 | handleJavaExceptions(); 1033 | return result; 1034 | } 1035 | 1036 | template <> float Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1037 | { 1038 | auto result = env()->CallNonvirtualFloatMethodA(obj, getHandle(), method, (jvalue*) args); 1039 | handleJavaExceptions(); 1040 | return result; 1041 | } 1042 | 1043 | template <> double Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1044 | { 1045 | auto result = env()->CallNonvirtualDoubleMethodA(obj, getHandle(), method, (jvalue*) args); 1046 | handleJavaExceptions(); 1047 | return result; 1048 | } 1049 | 1050 | template <> std::string Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1051 | { 1052 | auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); 1053 | handleJavaExceptions(); 1054 | return toString(result); 1055 | } 1056 | 1057 | template <> std::wstring Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1058 | { 1059 | auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); 1060 | handleJavaExceptions(); 1061 | return toWString(result); 1062 | } 1063 | 1064 | template <> Object Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const 1065 | { 1066 | auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); 1067 | handleJavaExceptions(); 1068 | return Object(result, DeleteLocalInput); 1069 | } 1070 | 1071 | Object Class::newObject(method_t constructor, internal::value_t* args) const 1072 | { 1073 | jobject ref = env()->NewObjectA(getHandle(), constructor, (jvalue*)args); 1074 | handleJavaExceptions(); 1075 | return Object(ref, DeleteLocalInput); 1076 | } 1077 | 1078 | /* 1079 | Enum Implementation 1080 | */ 1081 | 1082 | Enum::Enum(const char* name) : Class(name) 1083 | { 1084 | _name = "L"; 1085 | _name += name; 1086 | _name += ";"; 1087 | } 1088 | 1089 | Object Enum::get(const char* name) const 1090 | { 1091 | return Class::get(getStaticField(name, _name.c_str())); 1092 | } 1093 | 1094 | /* 1095 | Array Implementation 1096 | */ 1097 | 1098 | template <> Array::Array(long length) : Object(env()->NewBooleanArray(length)), _length(length) 1099 | { 1100 | } 1101 | 1102 | template <> Array::Array(long length) : Object(env()->NewByteArray(length)), _length(length) 1103 | { 1104 | } 1105 | 1106 | template <> Array::Array(long length) : Object(env()->NewCharArray(length)), _length(length) 1107 | { 1108 | } 1109 | 1110 | template <> Array::Array(long length) : Object(env()->NewShortArray(length)), _length(length) 1111 | { 1112 | } 1113 | 1114 | template <> Array::Array(long length) : Object(env()->NewIntArray(length)), _length(length) 1115 | { 1116 | } 1117 | 1118 | template <> Array::Array(long length) : Object(env()->NewLongArray(length)), _length(length) 1119 | { 1120 | } 1121 | 1122 | template <> Array::Array(long length) : Object(env()->NewFloatArray(length)), _length(length) 1123 | { 1124 | } 1125 | 1126 | template <> Array::Array(long length) : Object(env()->NewDoubleArray(length)), _length(length) 1127 | { 1128 | } 1129 | 1130 | template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/String").getHandle(), nullptr)), _length(length) 1131 | { 1132 | } 1133 | 1134 | template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/String").getHandle(), nullptr)), _length(length) 1135 | { 1136 | } 1137 | 1138 | template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/Object").getHandle(), nullptr)), _length(length) 1139 | { 1140 | } 1141 | 1142 | template <> Array::Array(long length, const Class& type) : Object(env()->NewObjectArray(length, type.getHandle(), nullptr)), _length(length) 1143 | { 1144 | } 1145 | 1146 | template <> bool Array::getElement(long index) const 1147 | { 1148 | jboolean output; 1149 | env()->GetBooleanArrayRegion(jbooleanArray(getHandle()), index, 1, &output); 1150 | handleJavaExceptions(); 1151 | return output; 1152 | } 1153 | 1154 | template <> byte_t Array::getElement(long index) const 1155 | { 1156 | jbyte output; 1157 | env()->GetByteArrayRegion(jbyteArray(getHandle()), index, 1, &output); 1158 | handleJavaExceptions(); 1159 | return output; 1160 | } 1161 | 1162 | template <> wchar_t Array::getElement(long index) const 1163 | { 1164 | jchar output; 1165 | env()->GetCharArrayRegion(jcharArray(getHandle()), index, 1, &output); 1166 | handleJavaExceptions(); 1167 | return output; 1168 | } 1169 | 1170 | template <> short Array::getElement(long index) const 1171 | { 1172 | jshort output; 1173 | env()->GetShortArrayRegion(jshortArray(getHandle()), index, 1, &output); 1174 | handleJavaExceptions(); 1175 | return output; 1176 | } 1177 | 1178 | template <> int Array::getElement(long index) const 1179 | { 1180 | jint output; 1181 | env()->GetIntArrayRegion(jintArray(getHandle()), index, 1, &output); 1182 | handleJavaExceptions(); 1183 | return output; 1184 | } 1185 | 1186 | template <> long long Array::getElement(long index) const 1187 | { 1188 | jlong output; 1189 | env()->GetLongArrayRegion(jlongArray(getHandle()), index, 1, &output); 1190 | handleJavaExceptions(); 1191 | return output; 1192 | } 1193 | 1194 | template <> float Array::getElement(long index) const 1195 | { 1196 | jfloat output; 1197 | env()->GetFloatArrayRegion(jfloatArray(getHandle()), index, 1, &output); 1198 | handleJavaExceptions(); 1199 | return output; 1200 | } 1201 | 1202 | template <> double Array::getElement(long index) const 1203 | { 1204 | jdouble output; 1205 | env()->GetDoubleArrayRegion(jdoubleArray(getHandle()), index, 1, &output); 1206 | handleJavaExceptions(); 1207 | return output; 1208 | } 1209 | 1210 | template <> std::string Array::getElement(long index) const 1211 | { 1212 | jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); 1213 | handleJavaExceptions(); 1214 | return toString(output); 1215 | } 1216 | 1217 | template <> std::wstring Array::getElement(long index) const 1218 | { 1219 | jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); 1220 | handleJavaExceptions(); 1221 | return toWString(output); 1222 | } 1223 | 1224 | template <> Object Array::getElement(long index) const 1225 | { 1226 | jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); 1227 | handleJavaExceptions(); 1228 | return Object(output, DeleteLocalInput); 1229 | } 1230 | 1231 | template <> void Array::setElement(long index, bool value) 1232 | { 1233 | jboolean jvalue = value; 1234 | env()->SetBooleanArrayRegion(jbooleanArray(getHandle()), index, 1, &jvalue); 1235 | handleJavaExceptions(); 1236 | } 1237 | 1238 | template <> void Array::setElement(long index, byte_t value) 1239 | { 1240 | jbyte jvalue = value; 1241 | env()->SetByteArrayRegion(jbyteArray(getHandle()), index, 1, &jvalue); 1242 | handleJavaExceptions(); 1243 | } 1244 | 1245 | template <> void Array::setElement(long index, wchar_t value) 1246 | { 1247 | jchar jvalue = value; 1248 | env()->SetCharArrayRegion(jcharArray(getHandle()), index, 1, &jvalue); 1249 | handleJavaExceptions(); 1250 | } 1251 | 1252 | template <> void Array::setElement(long index, short value) 1253 | { 1254 | jshort jvalue = value; 1255 | env()->SetShortArrayRegion(jshortArray(getHandle()), index, 1, &jvalue); 1256 | handleJavaExceptions(); 1257 | } 1258 | 1259 | template <> void Array::setElement(long index, int value) 1260 | { 1261 | jint jvalue = value; 1262 | env()->SetIntArrayRegion(jintArray(getHandle()), index, 1, &jvalue); 1263 | handleJavaExceptions(); 1264 | } 1265 | 1266 | template <> void Array::setElement(long index, long long value) 1267 | { 1268 | jlong jvalue = value; 1269 | env()->SetLongArrayRegion(jlongArray(getHandle()), index, 1, &jvalue); 1270 | handleJavaExceptions(); 1271 | } 1272 | 1273 | template <> void Array::setElement(long index, float value) 1274 | { 1275 | jfloat jvalue = value; 1276 | env()->SetFloatArrayRegion(jfloatArray(getHandle()), index, 1, &jvalue); 1277 | handleJavaExceptions(); 1278 | } 1279 | 1280 | template <> void Array::setElement(long index, double value) 1281 | { 1282 | jdouble jvalue = value; 1283 | env()->SetDoubleArrayRegion(jdoubleArray(getHandle()), index, 1, &jvalue); 1284 | handleJavaExceptions(); 1285 | } 1286 | 1287 | template <> void Array::setElement(long index, std::string value) 1288 | { 1289 | JNIEnv* env = jni::env(); 1290 | 1291 | jobject jvalue = env->NewStringUTF(value.c_str());; 1292 | env->SetObjectArrayElement(jobjectArray(getHandle()), index, jvalue); 1293 | env->DeleteLocalRef(jvalue); 1294 | handleJavaExceptions(); 1295 | } 1296 | 1297 | template <> void Array::setElement(long index, std::wstring value) 1298 | { 1299 | JNIEnv* env = jni::env(); 1300 | 1301 | #ifdef _WIN32 1302 | jobject jvalue = env->NewString((const jchar*) value.c_str(), jsize(value.length())); 1303 | #else 1304 | auto jstr = toJString(value.c_str(), value.length()); 1305 | jobject jvalue = env->NewString(jstr.data(), jsize(jstr.size())); 1306 | #endif 1307 | env->SetObjectArrayElement(jobjectArray(getHandle()), index, jvalue); 1308 | env->DeleteLocalRef(jvalue); 1309 | handleJavaExceptions(); 1310 | } 1311 | 1312 | template <> void Array::setElement(long index, Object value) 1313 | { 1314 | env()->SetObjectArrayElement(jobjectArray(getHandle()), index, value.getHandle()); 1315 | handleJavaExceptions(); 1316 | } 1317 | 1318 | /* 1319 | Vm Implementation 1320 | */ 1321 | 1322 | typedef jint (JNICALL *CreateVm_t)(JavaVM**, void**, void*); 1323 | 1324 | #ifndef _WIN32 1325 | static bool fileExists(const std::string& filePath) 1326 | { 1327 | return access(filePath.c_str(), F_OK) != -1; 1328 | } 1329 | 1330 | template 1331 | static ssize_t readlink_safe(const char *pathname, char (&output)[N]) { 1332 | auto len = readlink(pathname, output, N - 1); 1333 | if (len > 0) { 1334 | output[len] = '\0'; 1335 | } 1336 | return len; 1337 | } 1338 | 1339 | static std::pair 1340 | readlink_as_string(const char *pathname) { 1341 | char buf[2048] = {}; 1342 | auto len = readlink_safe(pathname, buf); 1343 | if (len <= 0) { 1344 | return {len, {}}; 1345 | } 1346 | return {len, std::string{buf, static_cast(len)}}; 1347 | } 1348 | static std::string readlink_deep(const char *pathname) { 1349 | std::string prev{pathname}; 1350 | ssize_t len = 0; 1351 | std::string next; 1352 | while (true) { 1353 | std::tie(len, next) = readlink_as_string(prev.c_str()); 1354 | if (!next.empty()) { 1355 | prev = next; 1356 | } else { 1357 | return prev; 1358 | } 1359 | } 1360 | } 1361 | 1362 | static std::string drop_path_components(const std::string & path, size_t num_components) { 1363 | size_t pos = std::string::npos; 1364 | size_t slash_pos = std::string::npos; 1365 | for (size_t i = 0; i < num_components; ++i) { 1366 | slash_pos = path.find_last_of('/', pos); 1367 | if (slash_pos == std::string::npos || slash_pos == 0) { 1368 | return {}; 1369 | } 1370 | pos = slash_pos - 1; 1371 | } 1372 | return std::string{path.c_str(), slash_pos}; 1373 | } 1374 | #endif 1375 | static std::string detectJvmPath() 1376 | { 1377 | std::string result; 1378 | 1379 | #ifdef _WIN32 1380 | 1381 | BYTE buffer[1024]; 1382 | DWORD size = sizeof(buffer); 1383 | HKEY versionKey; 1384 | 1385 | // Search via registry entries. 1386 | if (::RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\JavaSoft\\Java Runtime Environment\\", &versionKey) == ERROR_SUCCESS) 1387 | { 1388 | if (::RegQueryValueEx(versionKey, "CurrentVersion", NULL, NULL, buffer, &size) == ERROR_SUCCESS) 1389 | { 1390 | HKEY libKey; 1391 | 1392 | std::string keyName = std::string("Software\\JavaSoft\\Java Runtime Environment\\") + (const char*)buffer; 1393 | 1394 | ::RegCloseKey(versionKey); 1395 | 1396 | if (::RegOpenKeyA(HKEY_LOCAL_MACHINE, keyName.c_str(), &libKey) == ERROR_SUCCESS) 1397 | { 1398 | size = sizeof(buffer); 1399 | 1400 | if (::RegQueryValueEx(libKey, "RuntimeLib", NULL, NULL, buffer, &size) == ERROR_SUCCESS) 1401 | { 1402 | result = (const char*)buffer; 1403 | } 1404 | 1405 | ::RegCloseKey(libKey); 1406 | } 1407 | } 1408 | } 1409 | 1410 | if (result.length() == 0) 1411 | { 1412 | // Could not locate via registry. Try the JAVA_HOME environment variable. 1413 | if ((size = ::GetEnvironmentVariableA("JAVA_HOME", (LPSTR) buffer, sizeof(buffer))) != 0) 1414 | { 1415 | std::string javaHome((const char*) buffer, size); 1416 | 1417 | // Different installers put in different relative locations. 1418 | std::string options[] = { 1419 | javaHome + "\\jre\\bin\\client\\jvm.dll", 1420 | javaHome + "\\jre\\bin\\server\\jvm.dll", 1421 | javaHome + "\\bin\\client\\jvm.dll", 1422 | javaHome + "\\bin\\server\\jvm.dll" 1423 | }; 1424 | 1425 | for (auto const& i : options) 1426 | if (fileExists(i)) 1427 | return i; 1428 | } 1429 | } 1430 | 1431 | #else 1432 | 1433 | const char* javaHome = getenv("JAVA_HOME"); 1434 | if (javaHome != nullptr) { 1435 | #ifdef __APPLE__ 1436 | std::string libJvmPath = std::string(javaHome) + "/jre/lib/server/libjvm.dylib"; 1437 | #else 1438 | std::string libJvmPath = std::string(javaHome) + "/jre/lib/amd64/server/libjvm.so"; 1439 | #endif 1440 | result = libJvmPath; 1441 | } else { 1442 | std::string path = readlink_deep("/usr/bin/java"); 1443 | if (!path.empty()) { 1444 | // drop bin and java 1445 | auto javaHome = drop_path_components(path, 2); 1446 | if (!javaHome.empty()) { 1447 | std::string options[] = { 1448 | javaHome + "/jre/lib/amd64/server/libjvm.so", 1449 | javaHome + "/jre/lib/amd64/client/libjvm.so", 1450 | javaHome + "/jre/lib/server/libjvm.so", 1451 | javaHome + "/jre/lib/client/libjvm.so", 1452 | javaHome + "/lib/server/libjvm.so", 1453 | javaHome + "/lib/client/libjvm.so", 1454 | }; 1455 | 1456 | for (auto const &i : options) { 1457 | fprintf(stderr, "trying %s\n", i.c_str()); 1458 | if (fileExists(i)) { 1459 | return i; 1460 | } 1461 | } 1462 | } 1463 | } 1464 | // Best guess so far. 1465 | result = "/usr/lib/jvm/default-java/jre/lib/amd64/server/libjvm.so"; 1466 | } 1467 | 1468 | #endif // _WIN32 1469 | 1470 | return result; 1471 | } 1472 | 1473 | Vm::Vm(const char* path_) 1474 | { 1475 | bool expected = false; 1476 | 1477 | std::string path = path_ ? path_ : detectJvmPath(); 1478 | 1479 | if (path.length() == 0) 1480 | throw InitializationException("Could not locate Java Virtual Machine"); 1481 | if (!isVm.compare_exchange_strong(expected, true)) 1482 | throw InitializationException("Java Virtual Machine already initialized"); 1483 | 1484 | if (javaVm == nullptr) 1485 | { 1486 | JNIEnv* env; 1487 | JavaVMInitArgs args = {}; 1488 | args.version = JNI_VERSION_1_2; 1489 | 1490 | #ifdef _WIN32 1491 | 1492 | HMODULE lib = ::LoadLibraryA(path.c_str()); 1493 | 1494 | if (lib == NULL) 1495 | { 1496 | isVm.store(false); 1497 | throw InitializationException("Could not load JVM library"); 1498 | } 1499 | 1500 | CreateVm_t JNI_CreateJavaVM = (CreateVm_t) ::GetProcAddress(lib, "JNI_CreateJavaVM"); 1501 | 1502 | /** 1503 | Is your debugger catching an error here? This is normal. Just continue. The JVM 1504 | intentionally does this to test how the OS handles memory-reference exceptions. 1505 | */ 1506 | if (JNI_CreateJavaVM == NULL || JNI_CreateJavaVM(&javaVm, (void**) &env, &args) != 0) 1507 | { 1508 | isVm.store(false); 1509 | ::FreeLibrary(lib); 1510 | throw InitializationException("Java Virtual Machine failed during creation"); 1511 | } 1512 | 1513 | #else 1514 | 1515 | void* lib = ::dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL); 1516 | 1517 | if (lib == NULL) 1518 | { 1519 | isVm.store(false); 1520 | throw InitializationException("Could not load JVM library"); 1521 | } 1522 | 1523 | CreateVm_t JNI_CreateJavaVM = (CreateVm_t) ::dlsym(lib, "JNI_CreateJavaVM"); 1524 | 1525 | if (JNI_CreateJavaVM == NULL || JNI_CreateJavaVM(&javaVm, (void**) &env, &args) != 0) 1526 | { 1527 | isVm.store(false); 1528 | ::dlclose(lib); 1529 | throw InitializationException("Java Virtual Machine failed during creation"); 1530 | } 1531 | 1532 | #endif // _WIN32 1533 | } 1534 | } 1535 | 1536 | Vm::~Vm() 1537 | { 1538 | /* 1539 | Note that you can't ever *really* unload the JavaVM. If you call 1540 | DestroyJavaVM(), you can't then call JNI_CreateJavaVM() again. 1541 | So, instead we just flag it as "gone". 1542 | */ 1543 | isVm.store(false); 1544 | } 1545 | 1546 | // Forward Declarations 1547 | JNIEnv* env(); 1548 | 1549 | #ifndef _WIN32 1550 | extern std::vector toJString(const wchar_t* str, size_t length); 1551 | #endif 1552 | 1553 | namespace internal 1554 | { 1555 | // Base Type Conversions 1556 | void valueArg(value_t* v, bool a) { ((jvalue*) v)->z = jboolean(a); } 1557 | void valueArg(value_t* v, byte_t a) { ((jvalue*) v)->b = a; } 1558 | void valueArg(value_t* v, wchar_t a) { ((jvalue*) v)->c = jchar(a); } // Note: Possible truncation. 1559 | void valueArg(value_t* v, short a) { ((jvalue*) v)->s = a; } 1560 | void valueArg(value_t* v, int a) { ((jvalue*) v)->i = a; } 1561 | void valueArg(value_t* v, long long a) { ((jvalue*) v)->j = a; } 1562 | void valueArg(value_t* v, float a) { ((jvalue*) v)->f = a; } 1563 | void valueArg(value_t* v, double a) { ((jvalue*) v)->d = a; } 1564 | void valueArg(value_t* v, jobject a) { ((jvalue*) v)->l = a; } 1565 | void valueArg(value_t* v, const Object& a) { ((jvalue*) v)->l = a.getHandle(); } 1566 | void valueArg(value_t* v, const Object* const& a) { ((jvalue*) v)->l = a ? a->getHandle() : nullptr; } 1567 | 1568 | /* 1569 | Object Implementations 1570 | */ 1571 | 1572 | std::string valueSig(const Object* obj) 1573 | { 1574 | if (obj == nullptr || obj->isNull()) 1575 | return "Ljava/lang/Object;"; // One can always hope... 1576 | 1577 | std::string name = Class(obj->getClass(), Object::Temporary).getName(); 1578 | 1579 | // Change from "java.lang.Object" format to "java/lang/Object"; 1580 | for (size_t i = 0; i < name.length(); ++i) 1581 | if (name[i] == '.') 1582 | name[i] = '/'; 1583 | 1584 | return "L" + name + ";"; 1585 | } 1586 | 1587 | /* 1588 | String Implementations 1589 | */ 1590 | 1591 | void valueArg(value_t* v, const std::string& a) 1592 | { 1593 | ((jvalue*) v)->l = env()->NewStringUTF(a.c_str()); 1594 | } 1595 | 1596 | template <> void cleanupArg(value_t* v) 1597 | { 1598 | env()->DeleteLocalRef(((jvalue*) v)->l); 1599 | } 1600 | 1601 | void valueArg(value_t* v, const char* a) 1602 | { 1603 | ((jvalue*) v)->l = env()->NewStringUTF(a); 1604 | } 1605 | 1606 | void valueArg(value_t* v, std::nullptr_t) 1607 | { 1608 | ((jvalue*) v)->l = nullptr; 1609 | } 1610 | 1611 | template <> void cleanupArg(value_t* v) 1612 | { 1613 | env()->DeleteLocalRef(((jvalue*) v)->l); 1614 | } 1615 | #ifdef _WIN32 1616 | 1617 | void valueArg(value_t* v, const std::wstring& a) 1618 | { 1619 | ((jvalue*) v)->l = env()->NewString((const jchar*) a.c_str(), jsize(a.length())); 1620 | } 1621 | 1622 | void valueArg(value_t* v, const wchar_t* a) 1623 | { 1624 | ((jvalue*) v)->l = env()->NewString((const jchar*) a, jsize(std::wcslen(a))); 1625 | } 1626 | #else 1627 | 1628 | void valueArg(value_t* v, const std::wstring& a) 1629 | { 1630 | auto jstr = toJString(a.c_str(), a.length()); 1631 | ((jvalue*) v)->l = env()->NewString(jstr.data(), jsize(jstr.size())); 1632 | } 1633 | 1634 | void valueArg(value_t* v, const wchar_t* a) 1635 | { 1636 | auto jstr = toJString(a, std::wcslen(a)); 1637 | ((jvalue*) v)->l = env()->NewString(jstr.data(), jsize(jstr.size())); 1638 | } 1639 | 1640 | #endif 1641 | 1642 | template <> void cleanupArg(value_t* v) 1643 | { 1644 | env()->DeleteLocalRef(((jvalue*) v)->l); 1645 | } 1646 | 1647 | template <> void cleanupArg(value_t* v) 1648 | { 1649 | env()->DeleteLocalRef(((jvalue*) v)->l); 1650 | } 1651 | 1652 | long getArrayLength(jarray array) 1653 | { 1654 | return env()->GetArrayLength(array); 1655 | } 1656 | } 1657 | } 1658 | 1659 | -------------------------------------------------------------------------------- /jnipp.h: -------------------------------------------------------------------------------- 1 | #ifndef _JNIPP_H_ 2 | #define _JNIPP_H_ 1 3 | 4 | // Standard Dependencies 5 | #include 6 | #include // For std::runtime_error 7 | #include 8 | 9 | // Forward Declarations 10 | struct JNIEnv_; 11 | struct _JNIEnv; 12 | struct JavaVM_; 13 | struct _JavaVM; 14 | struct _jmethodID; 15 | struct _jfieldID; 16 | class _jobject; 17 | class _jclass; 18 | class _jarray; 19 | 20 | namespace jni 21 | { 22 | // JNI Base Types 23 | #ifdef __ANDROID__ 24 | typedef _JNIEnv JNIEnv; 25 | typedef _JavaVM JavaVM; 26 | #else 27 | typedef JNIEnv_ JNIEnv; 28 | typedef JavaVM_ JavaVM; 29 | #endif 30 | 31 | typedef _jobject* jobject; 32 | typedef _jclass* jclass; 33 | typedef _jarray* jarray; 34 | 35 | /** 36 | You can save a method via its handle using Class::getMethod() if it is 37 | going to be used often. This saves Object::call() from having to locate 38 | the method each time by name. 39 | 40 | Note that these handles are global and do not need to be deleted. 41 | */ 42 | typedef _jmethodID* method_t; 43 | 44 | /** 45 | You can save a field via its handle using Class::getField() if it is 46 | going to be used often. This saves Object::set() and Object::get() from 47 | having to locate the field each time by name. 48 | 49 | Note that these handles are global and do not need to be deleted. 50 | */ 51 | typedef _jfieldID* field_t; 52 | 53 | /** 54 | Type used to denote the Java byte type. 55 | */ 56 | typedef unsigned char byte_t; 57 | 58 | #ifdef JNIPP_EXCEPTION_CLASS 59 | 60 | /** 61 | Base class for thrown Exceptions. 62 | */ 63 | typedef JNIPP_EXCEPTION_CLASS Exception; 64 | 65 | #else 66 | 67 | /** 68 | Base class for thrown Exceptions. 69 | */ 70 | typedef std::runtime_error Exception; 71 | 72 | #endif // JNIPP_EXCEPTION_CLASS 73 | 74 | // Foward Declarations 75 | class Object; 76 | template class Array; 77 | 78 | /** 79 | This namespace is for messy implementation details only. It is not a part 80 | of the external API and is subject to change at any time. It is only in a 81 | header file due to the fact it is required by some template functions. 82 | 83 | Long story short... this stuff be messy, yo. 84 | */ 85 | namespace internal 86 | { 87 | /* 88 | Signature Generation 89 | */ 90 | 91 | inline std::string valueSig(const void*) { return "V"; } 92 | inline std::string valueSig(const bool*) { return "Z"; } 93 | inline std::string valueSig(const byte_t*) { return "B"; } 94 | inline std::string valueSig(const wchar_t*) { return "C"; } 95 | inline std::string valueSig(const short*) { return "S"; } 96 | inline std::string valueSig(const int*) { return "I"; } 97 | inline std::string valueSig(const long long*) { return "J"; } 98 | inline std::string valueSig(const float*) { return "F"; } 99 | inline std::string valueSig(const double*) { return "D"; } 100 | inline std::string valueSig(const std::string*) { return "Ljava/lang/String;"; } 101 | inline std::string valueSig(const std::wstring*) { return "Ljava/lang/String;"; } 102 | inline std::string valueSig(const char* const*) { return "Ljava/lang/String;"; } 103 | inline std::string valueSig(const wchar_t* const*) { return "Ljava/lang/String;"; } 104 | std::string valueSig(const Object* obj); 105 | inline std::string valueSig(const Object* const* obj) { return valueSig(obj ? *obj : nullptr); } 106 | 107 | template 108 | inline std::string valueSig(const Array*) { return "[" + valueSig((TArg*) nullptr); } 109 | 110 | template 111 | inline std::string valueSig(const TArg(*arg)[n]) { return valueSig((const TArg* const*)arg); } 112 | 113 | inline std::string sig() { return ""; } 114 | 115 | template 116 | std::string sig(const TArg& arg, const TArgs&... args) { 117 | return valueSig(&arg) + sig(args...); 118 | } 119 | 120 | /* 121 | Argument Conversion 122 | */ 123 | 124 | typedef long long value_t; 125 | 126 | void valueArg(value_t* v, bool a); 127 | void valueArg(value_t* v, byte_t a); 128 | void valueArg(value_t* v, wchar_t a); 129 | void valueArg(value_t* v, short a); 130 | void valueArg(value_t* v, int a); 131 | void valueArg(value_t* v, long long a); 132 | void valueArg(value_t* v, float a); 133 | void valueArg(value_t* v, double a); 134 | void valueArg(value_t* v, jobject a); 135 | void valueArg(value_t* v, const Object& a); 136 | void valueArg(value_t* v, const Object* const& a); 137 | void valueArg(value_t* v, const std::string& a); 138 | void valueArg(value_t* v, const char* a); 139 | void valueArg(value_t* v, const std::wstring& a); 140 | void valueArg(value_t* v, const wchar_t* a); 141 | void valueArg(value_t* v, std::nullptr_t); 142 | 143 | inline void args(value_t*) {} 144 | 145 | template 146 | void args(value_t* values, const TArg& arg, const TArgs&... args) { 147 | valueArg(values, arg); 148 | internal::args(values + 1, args...); 149 | } 150 | 151 | template void cleanupArg(value_t* /* value */) {} 152 | template <> void cleanupArg(value_t* value); 153 | template <> void cleanupArg(value_t* value); 154 | template <> void cleanupArg(value_t* value); 155 | template <> void cleanupArg(value_t* value); 156 | 157 | template 158 | void cleanupArgs(value_t* values) { 159 | cleanupArg(values); 160 | cleanupArgs(values + 1); 161 | } 162 | 163 | template <> 164 | inline void cleanupArgs(value_t* /* values */) {} 165 | 166 | template 167 | class ArgArray 168 | { 169 | public: 170 | ArgArray(const TArgs&... args) { 171 | std::memset(this, 0, sizeof(ArgArray)); 172 | internal::args(values, args...); 173 | } 174 | 175 | ~ArgArray() { 176 | cleanupArgs(values); 177 | } 178 | 179 | value_t values[sizeof...(TArgs)]; 180 | }; 181 | 182 | /* specialization for empty array - no args. Avoids "empty array" warning. */ 183 | template <> 184 | class ArgArray<> 185 | { 186 | public: 187 | ArgArray() { 188 | std::memset(this, 0, sizeof(ArgArray<>)); 189 | } 190 | 191 | ~ArgArray() { 192 | } 193 | 194 | value_t values[1]; 195 | }; 196 | long getArrayLength(jarray array); 197 | 198 | /** 199 | * @brief Used as a tag type for dispatching internally based on return type. 200 | * 201 | * @tparam T The type to wrap. 202 | */ 203 | template 204 | struct ReturnTypeWrapper 205 | { 206 | using type = T; 207 | }; 208 | } 209 | 210 | /** 211 | Initialises the Java Native Interface with the given JNIEnv handle, which 212 | gets passed into a native function which is called from Java. This only 213 | needs to be done once per process - further calls are no-ops. 214 | \param env A JNI environment handle. 215 | */ 216 | void init(JNIEnv* env); 217 | /** 218 | Initialises the Java Native Interface with the given JavaVM handle, 219 | which may be accessible. This (or the other overload) only needs to be 220 | done once per process - further calls are no-ops. 221 | \param vm A JNI VM handle. 222 | */ 223 | void init(JavaVM* vm); 224 | 225 | /** 226 | Get the appropriate JNI environment for this thread. 227 | */ 228 | JNIEnv* env(); 229 | 230 | /** 231 | Object corresponds with a `java.lang.Object` instance. With an Object, 232 | you can then call Java methods, and access fields on the Object. To 233 | instantiate an Object of a given class, use the `Class` class. 234 | */ 235 | class Object 236 | { 237 | public: 238 | /** Flags which can be passed to the Object constructor. */ 239 | enum ScopeFlags 240 | { 241 | Temporary = 1, ///< Temporary object. Do not create a global reference. 242 | DeleteLocalInput = 2 ///< The input reference is temporary and can be deleted. 243 | }; 244 | 245 | /** Default constructor. Creates a `null` object. */ 246 | Object() noexcept; 247 | 248 | /** 249 | Copies a reference to another Object. Note that this is not a deep 250 | copy operation, and both Objects will reference the same Java 251 | Object. 252 | \param other The Object to copy. 253 | */ 254 | Object(const Object& other); 255 | 256 | /** 257 | Move constructor. Copies the Object reference from the supplied 258 | Object, and then nulls the supplied Object reference. 259 | \param other The Object to move. 260 | */ 261 | Object(Object&& other) noexcept; 262 | 263 | /** 264 | Creates an Object from a local JNI reference. 265 | \param ref The local JNI reference. 266 | \param scopeFlags Bitmask of ScopeFlags values. 267 | */ 268 | Object(jobject ref, int scopeFlags = 0); 269 | 270 | /** 271 | Destructor. Releases this reference on the Java Object so it can be 272 | picked up by the garbage collector. 273 | */ 274 | virtual ~Object() noexcept; 275 | 276 | /** 277 | Assignment operator. Copies the object reference from the supplied 278 | Object. They will now both point to the same Java Object. 279 | \param other The Object to copy. 280 | \return This Object. 281 | */ 282 | Object& operator=(const Object& other); 283 | 284 | /** 285 | Assignment operator. Moves the object reference from the supplied 286 | Object to this one, and leaves the other one as a null. 287 | \param other The Object to move. 288 | \return This Object. 289 | */ 290 | Object& operator=(Object&& other); 291 | 292 | /** 293 | Tells whether the two Objects refer to the same Java Object. 294 | \param other the Object to compare with. 295 | \return `true` if the same, `false` otherwise. 296 | */ 297 | bool operator==(const Object& other) const; 298 | 299 | /** 300 | Tells whether the two Objects refer to the same Java Object. 301 | \param other the Object to compare with. 302 | \return `true` if the different, `false` otherwise. 303 | */ 304 | bool operator!=(const Object& other) const { return !operator==(other); } 305 | 306 | /** 307 | Calls the given method on this Object. The method should have no 308 | parameters. Note that the return type should be explicitly stated 309 | in the function call. 310 | \param method A method handle which applies to this Object. 311 | \return The method's return value. 312 | */ 313 | template 314 | TReturn call(method_t method) const { return callMethod(method, nullptr, internal::ReturnTypeWrapper{}); } 315 | 316 | /** 317 | Calls the method on this Object with the given name, and no arguments. 318 | Note that the return type should be explicitly stated in the function 319 | call. 320 | \param name The name of the method to call (with optional signature). 321 | \return The method's return value. 322 | */ 323 | template 324 | TReturn call(const char* name) const { 325 | if (std::strstr(name, "()")) 326 | return call(getMethod(name)); 327 | 328 | // No signature supplied. Generate our own. 329 | method_t method = getMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); 330 | return call(method); 331 | } 332 | 333 | /** 334 | Calls the method on this Object and supplies the given arguments. 335 | Note that the return type should be explicitly stated in the function 336 | call. 337 | \param method The method to call. 338 | \param args Arguments to supply to the method. 339 | \return The method's return value. 340 | */ 341 | template 342 | TReturn call(method_t method, const TArgs&... args) const { 343 | internal::ArgArray transform(args...); 344 | return callMethod(method, transform.values, internal::ReturnTypeWrapper{}); 345 | } 346 | 347 | /** 348 | Calls the method on this Object and supplies the given arguments. 349 | Note that the return type should be explicitly stated in the function 350 | call. The type signature of the method is calculated by the types of 351 | the supplied arguments. 352 | \param name The name of the method to call (and optional signature). 353 | \param args Arguments to supply to the method. 354 | \return The method's return value. 355 | */ 356 | template 357 | TReturn call(const char* name, const TArgs&... args) const { 358 | if (std::strchr(name, '(')) 359 | return call(getMethod(name), args...); 360 | 361 | std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); 362 | method_t method = getMethod(name, sig.c_str()); 363 | return call(method, args...); 364 | } 365 | 366 | /** 367 | Gets a field value from this Object. The field must belong to the 368 | Object's class. Note that the field type should be explicitly stated 369 | in the function call. 370 | \param field Identifier for the field to retrieve. 371 | \return The field's value. 372 | */ 373 | template 374 | TType get(field_t field) const { 375 | // If you get a compile error here, then you've asked for a type 376 | // we don't know how to get from JNI directly. 377 | return getFieldValue(field, internal::ReturnTypeWrapper{}); 378 | } 379 | 380 | /** 381 | Gets a field value from this Object. The field must belong to the 382 | Object's class. Note that the field type should be explicitly stated 383 | in the function call. 384 | \param name The name of the field to retrieve. 385 | \return The field's value. 386 | */ 387 | template 388 | TType get(const char* name) const { 389 | field_t field = getField(name, internal::valueSig((TType*) nullptr).c_str()); 390 | return get(field); 391 | } 392 | 393 | /** 394 | Sets a field's value on this Object. The field must belong to the 395 | Object's class, and the parameter's type should correspond to the 396 | type of the field. 397 | \param field The field to set the value to. 398 | \param value The value to set. 399 | */ 400 | template 401 | void set(field_t field, const TType& value); 402 | 403 | /** 404 | Sets a field's value on this Object. The field must belong to the 405 | Object's class, and the parameter's type should correspond to the 406 | type of the field. 407 | \param name The name of the field to set the value to. 408 | \param value The value to set. 409 | */ 410 | template 411 | void set(const char* name, const TType& value) { 412 | field_t field = getField(name, internal::valueSig((TType*) nullptr).c_str()); 413 | set(field, value); 414 | } 415 | 416 | /** 417 | Tells whether this Object is currently a `null` pointer. 418 | \return `true` if `null`, `false` if it references an object. 419 | */ 420 | bool isNull() const noexcept; 421 | 422 | /** 423 | Gets a handle for this Object's class. Ideally, this should just return a Class, 424 | but C++ won't let us do that that. 425 | \return The Object's Class's handle. 426 | */ 427 | jclass getClass() const; 428 | 429 | /** 430 | Gets the underlying JNI jobject handle. 431 | \return The JNI handle. 432 | */ 433 | jobject getHandle() const noexcept { return _handle; } 434 | 435 | /** 436 | Create a local reference for the underlying JNI handle. 437 | \return The local reference. 438 | */ 439 | jobject makeLocalReference() const; 440 | 441 | private: 442 | // Helper Functions 443 | method_t getMethod(const char* name, const char* signature) const; 444 | method_t getMethod(const char* nameAndSignature) const; 445 | field_t getField(const char* name, const char* signature) const; 446 | 447 | void callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 448 | bool callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 449 | byte_t callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 450 | wchar_t callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 451 | short callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 452 | int callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 453 | long long callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 454 | float callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 455 | double callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 456 | std::string callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 457 | std::wstring callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 458 | jni::Object callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 459 | jarray callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper const&) const; 460 | 461 | template 462 | jni::Array callMethod(method_t method, internal::value_t* args, internal::ReturnTypeWrapper> const&) const; 463 | 464 | void getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 465 | bool getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 466 | byte_t getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 467 | wchar_t getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 468 | short getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 469 | int getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 470 | long long getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 471 | float getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 472 | double getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 473 | std::string getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 474 | std::wstring getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 475 | jni::Object getFieldValue(field_t field, internal::ReturnTypeWrapper const&) const; 476 | 477 | // Instance Variables 478 | jobject _handle; 479 | mutable jclass _class; 480 | bool _isGlobal; 481 | }; 482 | 483 | /** 484 | Class corresponds with `java.lang.Class`, and allows you to instantiate 485 | Objects and get class members such as methods and fields. 486 | */ 487 | class Class : protected Object 488 | { 489 | public: 490 | /** 491 | Creates a null class reference. 492 | */ 493 | Class() : Object() {} 494 | 495 | /** 496 | Obtains a class reference to the Java class with the given qualified 497 | name. 498 | \param name The qualified class name (e.g. "java/lang/String"). 499 | */ 500 | Class(const char* name); 501 | 502 | /** 503 | Creates a Class object by JNI reference. 504 | \param ref The JNI class reference. 505 | \param scopeFlags Bitmask of Object::ScopeFlags. 506 | */ 507 | Class(jclass ref, int scopeFlags = Temporary); 508 | 509 | /** 510 | Tells whether this Class is null or valid. 511 | \return `true` if null, `false` if valid. 512 | */ 513 | bool isNull() const noexcept { return Object::isNull(); } 514 | 515 | /** 516 | Creates a new instance of this Java class and returns a reference to 517 | it. The item's parameterless constructor is called. 518 | \return The created instance. 519 | */ 520 | Object newInstance() const; 521 | 522 | /** 523 | Creates a new instance of this Java class and returns a reference to 524 | it. 525 | \param constructor The constructor to call. 526 | \param args Arguments to supply to the constructor. 527 | \return The created instance. 528 | */ 529 | template 530 | Object newInstance(method_t constructor, const TArgs&... args) const { 531 | internal::ArgArray transform(args...); 532 | return newObject(constructor, transform.values); 533 | } 534 | 535 | /** 536 | Creates a new instance of this Java class and returns a reference to 537 | it. The constructor signature is determined by the supplied parameters, 538 | and the parameters are then passed to the constructor. 539 | \param args Arguments to supply to the constructor. 540 | \return The created instance. 541 | */ 542 | template 543 | Object newInstance(const TArgs&... args) const { 544 | method_t constructor = getMethod("", ("(" + internal::sig(args...) + ")V").c_str()); 545 | return newInstance(constructor, args...); 546 | } 547 | 548 | /** 549 | Gets a handle to the field with the given name and type signature. 550 | This handle can then be stored so that the field does not need to 551 | be looked up by name again. It does not need to be deleted. 552 | \param name The name of the field. 553 | \param signature The JNI type signature of the field. 554 | \return The field ID. 555 | */ 556 | field_t getField(const char* name, const char* signature) const; 557 | 558 | /** 559 | Gets a handle to the field with the given name and the supplied type. 560 | This handle can then be stored so that the field does not need to 561 | be looked up by name again. It does not need to be deleted. 562 | \param name The name of the field. 563 | \return The field ID. 564 | */ 565 | template 566 | field_t getField(const char* name) const { 567 | return getField(name, internal::valueSig((TType*) nullptr).c_str()); 568 | } 569 | 570 | /** 571 | Gets a handle to the static field with the given name and type signature. 572 | This handle can then be stored so that the field does not need to 573 | be looked up by name again. It does not need to be deleted. 574 | \param name The name of the field. 575 | \param signature The JNI type signature of the field. 576 | \return The field ID. 577 | */ 578 | field_t getStaticField(const char* name, const char* signature) const; 579 | 580 | /** 581 | Gets a handle to the static field with the given name and the supplied type. 582 | This handle can then be stored so that the field does not need to 583 | be looked up by name again. It does not need to be deleted. 584 | \param name The name of the field. 585 | \return The field ID. 586 | */ 587 | template 588 | field_t getStaticField(const char* name) const { 589 | return getStaticField(name, internal::valueSig((TType*)nullptr).c_str()); 590 | } 591 | 592 | /** 593 | Gets a handle to the method with the given name and signature. 594 | This handle can then be stored so that the method does not need 595 | to be looked up by name again. It does not need to be deleted. 596 | \param name The name of the method. 597 | \param signature The JNI method signature. 598 | \return The method ID. 599 | */ 600 | method_t getMethod(const char* name, const char* signature) const; 601 | 602 | /** 603 | Gets a handle to the method with the given name and signature. 604 | This handle can then be stored so that the method does not need 605 | to be looked up by name again. It does not need to be deleted. 606 | \param nameAndSignature Name and signature identifier (e.g. "toString()Ljava/lang/String;"). 607 | \return The method ID. 608 | */ 609 | method_t getMethod(const char* nameAndSignature) const; 610 | 611 | /** 612 | Gets a handle to the static method with the given name and signature. 613 | This handle can then be stored so that the method does not need 614 | to be looked up by name again. It does not need to be deleted. 615 | \param name The name of the method. 616 | \param signature The JNI method signature. 617 | \return The method ID. 618 | */ 619 | method_t getStaticMethod(const char* name, const char* signature) const; 620 | 621 | /** 622 | Gets a handle to the static method with the given name and signature. 623 | This handle can then be stored so that the method does not need 624 | to be looked up by name again. It does not need to be deleted. 625 | \param nameAndSignature Name and signature identifier (e.g. "toString()Ljava/lang/String;"). 626 | \return The method ID. 627 | */ 628 | method_t getStaticMethod(const char* nameAndSignature) const; 629 | 630 | /** 631 | Gets a handle to the constructor for this Class with the given 632 | signature. Note that the return type should always be `void` ("V"). 633 | \param signature The JNI method signature for the constructor. 634 | \return The constructor method ID. 635 | */ 636 | method_t getConstructor(const char* signature) const { return getMethod("", signature); } 637 | 638 | /** 639 | Gets the parent Class of this Class. 640 | \return The parent class. 641 | */ 642 | Class getParent() const; 643 | 644 | /** 645 | Gets the JNI-qualified name of this Class. 646 | \return The Class name. 647 | */ 648 | std::string getName() const; 649 | 650 | /** 651 | Calls a static method on this Class. The method should have no 652 | parameters. Note that the return type should be explicitly stated 653 | in the function call. 654 | \param method A method handle which applies to this Object. 655 | \return The method's return value. 656 | */ 657 | template 658 | TReturn call(method_t method) const { return callStaticMethod(method, nullptr); } 659 | 660 | /** 661 | Calls a static method on this Class with the given name, and no arguments. 662 | Note that the return type should be explicitly stated in the function 663 | call. 664 | \param name The name of the method to call. 665 | \return The method's return value. 666 | */ 667 | template 668 | TReturn call(const char* name) const { 669 | method_t method = getStaticMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); 670 | return call(method); 671 | } 672 | 673 | /** 674 | Calls a static method on this Class and supplies the given arguments. 675 | Note that the return type should be explicitly stated in the function 676 | call. 677 | \param method The method to call. 678 | \param args Arguments to supply to the method. 679 | \return The method's return value. 680 | */ 681 | template 682 | TReturn call(method_t method, const TArgs&... args) const { 683 | internal::ArgArray transform(args...); 684 | return callStaticMethod(method, transform.values); 685 | } 686 | 687 | /** 688 | Calls a static method on this Class and supplies the given arguments. 689 | Note that the return type should be explicitly stated in the function 690 | call. The type signature of the method is calculated by the types of 691 | the supplied arguments. 692 | \param name The name of the method to call. 693 | \param args Arguments to supply to the method. 694 | \return The method's return value. 695 | */ 696 | template 697 | TReturn call(const char* name, const TArgs&... args) const { 698 | if (std::strchr(name, '(')) 699 | return call(getStaticMethod(name), args...); 700 | 701 | std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); 702 | method_t method = getStaticMethod(name, sig.c_str()); 703 | return call(method, args...); 704 | } 705 | 706 | /** 707 | Calls a non-static method on this Class, applying it to the supplied 708 | Object. The difference between this and Object.call() is that the 709 | specific class implementation of the method is called, rather than 710 | doing a virtual method lookup. 711 | \param obj The Object to call the method on. 712 | \param method The method to call. 713 | \return The method's return value. 714 | */ 715 | template 716 | TReturn call(const Object& obj, method_t method) const { 717 | return callExactMethod(obj.getHandle(), method, nullptr); 718 | } 719 | 720 | /** 721 | Calls a non-static method on this Class, applying it to the supplied 722 | Object. The difference between this and Object.call() is that the 723 | specific class implementation of the method is called, rather than 724 | doing a virtual method lookup. 725 | \param obj The Object to call the method on. 726 | \param name The name of the method to call. 727 | \return The method's return value. 728 | */ 729 | template 730 | TReturn call(const Object& obj, const char* name) const { 731 | method_t method = getMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); 732 | return call(obj, method); 733 | } 734 | template 735 | TReturn call(const Object* obj, const char* name) const { 736 | return call(obj, name); 737 | } 738 | 739 | /** 740 | Calls a non-static method on this Class, applying it to the supplied 741 | Object. The difference between this and Object.call() is that the 742 | specific class implementation of the method is called, rather than 743 | doing a virtual method lookup. 744 | \param obj The Object to call the method on. 745 | \param method The method to call. 746 | \param args Arguments to pass to the method. 747 | \return The method's return value. 748 | */ 749 | template 750 | TReturn call(const Object& obj, method_t method, const TArgs&... args) const { 751 | internal::ArgArray transform(args...); 752 | return callExactMethod(obj.getHandle(), method, transform.values); 753 | } 754 | template 755 | TReturn call(const Object* obj, method_t method, const TArgs&... args) const { 756 | return call(*obj, method, args...); 757 | } 758 | 759 | /** 760 | Calls a non-static method on this Class, applying it to the supplied 761 | Object. The difference between this and Object.call() is that the 762 | specific class implementation of the method is called, rather than 763 | doing a virtual method lookup. 764 | \param obj The Object to call the method on. 765 | \param name The name of the method to call. 766 | \param args Arguments to pass to the method. 767 | \return The method's return value. 768 | */ 769 | template 770 | TReturn call(const Object& obj, const char* name, const TArgs&... args) const { 771 | std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); 772 | method_t method = getMethod(name, sig.c_str()); 773 | return call(obj, method, args...); 774 | } 775 | template 776 | TReturn call(const Object* obj, const char* name, const TArgs&... args) const { 777 | return call(*obj, name, args...); 778 | } 779 | 780 | /** 781 | Gets a static field value from this Class. Note that the field type 782 | should be explicitly stated in the function call. 783 | \param field Identifier for the field to retrieve. 784 | \return The field's value. 785 | */ 786 | template 787 | TType get(field_t field) const; 788 | 789 | /** 790 | Gets a static field value from this Class. Note that the field type 791 | should be explicitly stated in the function call. 792 | \param name The name of the field to retrieve. 793 | \return The field's value. 794 | */ 795 | template 796 | TType get(const char* name) const { 797 | field_t field = getStaticField(name, internal::valueSig((TType*) nullptr).c_str()); 798 | return get(field); 799 | } 800 | 801 | /** 802 | Sets a static field's value on this Class. The parameter's type should 803 | correspond to the type of the field. 804 | \param field The field to set the value to. 805 | \param value The value to set. 806 | */ 807 | template 808 | void set(field_t field, const TType& value); 809 | 810 | /** 811 | Sets a static field's value on this Class. The parameter's type 812 | should correspond to the type of the field. 813 | \param name The name of the field to set the value to. 814 | \param value The value to set. 815 | */ 816 | template 817 | void set(const char* name, const TType& value) { 818 | field_t field = getStaticField(name, internal::valueSig((TType*) nullptr).c_str()); 819 | set(field, value); 820 | } 821 | 822 | /** 823 | Gets the underlying JNI jclass handle. 824 | \return The JNI handle. 825 | */ 826 | jclass getHandle() const noexcept { return jclass(Object::getHandle()); } 827 | 828 | private: 829 | // Helper Functions 830 | template TType callStaticMethod(method_t method, internal::value_t* values) const; 831 | template TType callExactMethod(jobject obj, method_t method, internal::value_t* values) const; 832 | Object newObject(method_t constructor, internal::value_t* args) const; 833 | }; 834 | 835 | /** 836 | Convenience class for dealing with Java enums. 837 | */ 838 | class Enum : protected Class 839 | { 840 | public: 841 | /** 842 | Loads the Enum with the given JNI-formatted name. 843 | \param name The name of the enum. 844 | */ 845 | Enum(const char* name); 846 | 847 | /** 848 | Gets the enum value with the given name. 849 | \param name The name of the enum value. 850 | \return The enum value identifier. 851 | */ 852 | Object get(const char* name) const; 853 | 854 | private: 855 | // Instance Variables 856 | std::string _name; 857 | }; 858 | 859 | /** 860 | Used to interact with native Java arrays. The element type can be any primitive 861 | type, jni::Object, std::string or std::wstring. 862 | */ 863 | template 864 | class Array : public Object 865 | { 866 | public: 867 | /** 868 | Default constructor. Creates a null array reference. 869 | */ 870 | Array() noexcept; 871 | 872 | /** 873 | Creates a Array object by JNI reference. 874 | \param ref The JNI array reference. 875 | \param scopeFlags Bitmask of Object::ScopeFlags. 876 | */ 877 | Array(jarray ref, int scopeFlags = Temporary); 878 | 879 | /** 880 | Creates an Array of the given length. The elements are default initialised 881 | to zero / null values. 882 | \param length The Array length. 883 | */ 884 | Array(long length); 885 | 886 | /** 887 | Creates an Array of the given length. A Class type is also specified, but can 888 | be left null to default to "java.lang.Object". 889 | \param length The Array length. 890 | \param type The element type. 891 | 892 | */ 893 | Array(long length, const Class& type); 894 | 895 | /** 896 | Copy constructor. Shares a reference to the Java array with the 897 | copied Array object. 898 | \param other The Array to copy. 899 | */ 900 | Array(const Array& other); 901 | 902 | /** 903 | Move constructor. Moves the array reference to the new Array, and leaves 904 | the previous Array as a null reference. 905 | \param other The Array to move. 906 | */ 907 | Array(Array&& other) noexcept; 908 | 909 | /** 910 | Assignment operator. Copies the array reference from the supplied 911 | Array. They will now both point to the same Java array. 912 | \param other The Array to copy. 913 | \return This Array. 914 | */ 915 | Array& operator=(const Array& other); 916 | 917 | /** 918 | Assignment operator. Moves the array reference from the supplied 919 | Array to this one, and leaves the other one as a null. 920 | \param other The Array to move. 921 | \return This Array. 922 | */ 923 | Array& operator=(Array&& other); 924 | 925 | /** 926 | Checks whether these Arrays both reference the same java array. 927 | \param other The Array to compare with. 928 | \return true if the same (or both null), false otherwise. 929 | */ 930 | bool operator==(const Array& other) const { return Object::operator==(other); } 931 | 932 | /** 933 | Checks whether these Arrays reference different the java arrays. 934 | \param other The Array to compare with. 935 | \return false if the same (or both null), true otherwise. 936 | */ 937 | bool operator!=(const Array& other) const { return !operator==(other); } 938 | 939 | /** 940 | Sets the element value at the given index in the Array. 941 | \param index The zero-based index. 942 | \param value The value to set. 943 | */ 944 | void setElement(long index, TElement value); 945 | 946 | /** 947 | Gets the value at the given index within the Array. 948 | \param index The zero-based index. 949 | \return The element at the given index. 950 | */ 951 | TElement getElement(long index) const; 952 | TElement operator[](long index) const { return getElement(index); } 953 | 954 | /** 955 | Gets the length of this Array. 956 | \return The array length. 957 | */ 958 | long getLength() const; 959 | 960 | /** 961 | Gets the underlying JNI jarray handle. 962 | \return The JNI handle. 963 | */ 964 | jarray getHandle() const noexcept { return jarray(Object::getHandle()); } 965 | 966 | private: 967 | // Instance Variables 968 | mutable long _length; ///< Mutable as it may only finally get set in a getLength() call. 969 | }; 970 | 971 | /** 972 | When the application's entry point is in C++ rather than in Java, it will 973 | need to spin up its own instance of the Java Virtual Machine (JVM) before 974 | it can initialize the Java Native Interface. Vm is used to create and 975 | destroy a running JVM instance. 976 | 977 | It uses the RAII idiom, so when the destructor is called, the Vm is shut 978 | down. 979 | 980 | Note that currently only one instance is supported. Attempts to create 981 | more will result in an InitializationException. 982 | */ 983 | class Vm final 984 | { 985 | public: 986 | /** 987 | Starts the Java Virtual Machine. 988 | \param path The path to the jvm.dll (or null for auto-detect). 989 | */ 990 | Vm(const char* path = nullptr); 991 | 992 | /** Destroys the running instance of the JVM. */ 993 | ~Vm(); 994 | }; 995 | 996 | /** 997 | A Java method call threw an Exception. 998 | */ 999 | class InvocationException : public Exception 1000 | { 1001 | public: 1002 | /** 1003 | Constructor with an error message. 1004 | \param msg Message to pass to the Exception. 1005 | */ 1006 | InvocationException(const char* msg = "Java Exception detected") : Exception(msg) {} 1007 | }; 1008 | 1009 | /** 1010 | A supplied name or type signature could not be resolved. 1011 | */ 1012 | class NameResolutionException : public Exception 1013 | { 1014 | public: 1015 | /** 1016 | Constructor with an error message. 1017 | \param name The name of the unresolved symbol. 1018 | */ 1019 | NameResolutionException(const char* name) : Exception(name) {} 1020 | }; 1021 | 1022 | /** 1023 | The Java Native Interface was not properly initialized. 1024 | */ 1025 | class InitializationException : public Exception 1026 | { 1027 | public: 1028 | /** 1029 | Constructor with an error message. 1030 | \param msg Message to pass to the Exception. 1031 | */ 1032 | InitializationException(const char* msg) : Exception(msg) {} 1033 | }; 1034 | 1035 | /* 1036 | Call method returning array: implementation 1037 | */ 1038 | template 1039 | inline jni::Array Object::callMethod(method_t method, internal::value_t* args, 1040 | internal::ReturnTypeWrapper> const&) const 1041 | { 1042 | jarray result = callMethod(method, args, internal::ReturnTypeWrapper{}); 1043 | return jni::Array(result, DeleteLocalInput); 1044 | } 1045 | 1046 | /* 1047 | Array Implementation 1048 | */ 1049 | 1050 | template 1051 | Array::Array() noexcept : Object(), _length(0) 1052 | { 1053 | } 1054 | 1055 | template 1056 | Array::Array(jarray ref, int scopeFlags) : Object((jobject) ref, scopeFlags), _length(-1) 1057 | { 1058 | } 1059 | 1060 | template 1061 | Array::Array(const Array& other) : Object(other), _length(other._length) 1062 | { 1063 | } 1064 | 1065 | template 1066 | Array::Array(Array&& other) noexcept : Object((Object&&)other), _length(other._length) 1067 | { 1068 | other._length = 0; 1069 | } 1070 | 1071 | template 1072 | Array& Array::operator=(const Array& other) 1073 | { 1074 | if (&other != this) 1075 | { 1076 | Object::operator=(other); 1077 | _length = other._length; 1078 | } 1079 | 1080 | return *this; 1081 | } 1082 | 1083 | template 1084 | Array& Array::operator=(Array&& other) 1085 | { 1086 | if (&other != this) 1087 | { 1088 | Object::operator=((Object&&) other); 1089 | _length = other._length; 1090 | 1091 | other._length = 0; 1092 | } 1093 | 1094 | return *this; 1095 | } 1096 | 1097 | template 1098 | long Array::getLength() const 1099 | { 1100 | if (_length < 0) 1101 | { 1102 | _length = internal::getArrayLength(getHandle()); 1103 | } 1104 | 1105 | return _length; 1106 | } 1107 | } 1108 | 1109 | #endif // _JNIPP_H_ 1110 | 1111 | -------------------------------------------------------------------------------- /jnipp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnipp", "jnipp.vcxproj", "{A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{5A4C5782-6F86-4413-9E19-3F6539DCD835}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | makefile = makefile 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests\tests.vcxproj", "{911DB449-3D76-4261-8FFD-9BA508ED1D5B}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|x64 = Release|x64 22 | Release|x86 = Release|x86 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x64.ActiveCfg = Debug|x64 26 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x64.Build.0 = Debug|x64 27 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x86.ActiveCfg = Debug|Win32 28 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x86.Build.0 = Debug|Win32 29 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x64.ActiveCfg = Release|x64 30 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x64.Build.0 = Release|x64 31 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x86.ActiveCfg = Release|Win32 32 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x86.Build.0 = Release|Win32 33 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x64.ActiveCfg = Debug|x64 34 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x64.Build.0 = Debug|x64 35 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x86.ActiveCfg = Debug|Win32 36 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x86.Build.0 = Debug|Win32 37 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x64.ActiveCfg = Release|x64 38 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x64.Build.0 = Release|x64 39 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x86.ActiveCfg = Release|Win32 40 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x86.Build.0 = Release|Win32 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /jnipp.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58} 23 | jnipp 24 | 10.0 25 | 26 | 27 | 28 | StaticLibrary 29 | true 30 | v142 31 | MultiByte 32 | 33 | 34 | StaticLibrary 35 | false 36 | v142 37 | true 38 | MultiByte 39 | 40 | 41 | StaticLibrary 42 | true 43 | v142 44 | MultiByte 45 | 46 | 47 | StaticLibrary 48 | false 49 | v142 50 | true 51 | MultiByte 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Level3 75 | Disabled 76 | true 77 | $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 78 | 79 | 80 | jvm.lib 81 | 82 | 83 | $(JAVA_HOME)\lib 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | true 91 | $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 92 | 93 | 94 | jvm.lib 95 | 96 | 97 | $(JAVA_HOME)\lib 98 | 99 | 100 | 101 | 102 | Level3 103 | MaxSpeed 104 | true 105 | true 106 | true 107 | $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 108 | 109 | 110 | true 111 | true 112 | 113 | 114 | jvm.lib 115 | 116 | 117 | $(JAVA_HOME)\lib 118 | 119 | 120 | 121 | 122 | Level3 123 | MaxSpeed 124 | true 125 | true 126 | true 127 | $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 128 | 129 | 130 | true 131 | true 132 | 133 | 134 | jvm.lib 135 | 136 | 137 | $(JAVA_HOME)\lib 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /jnipp.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC=g++ 2 | 3 | OS_NAME := linux 4 | 5 | ifeq ($(OS),Windows_NT) 6 | OS_NAME := win32 7 | RM := del 8 | else 9 | RM := rm -f 10 | UNAME_S := $(shell uname -s) 11 | ifeq ($(UNAME_S),Darwin) 12 | OS_NAME := darwin 13 | endif 14 | endif 15 | 16 | JAVA_HOME ?= /usr/lib/jvm/default-java 17 | 18 | CXXFLAGS=-I. -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/$(OS_NAME) -ldl -std=c++11 -Wall -g 19 | 20 | SRC=jnipp.o main.o 21 | VPATH=tests 22 | 23 | %.o: %.cpp 24 | $(CC) -c -o $@ $< $(CXXFLAGS) 25 | 26 | test: $(SRC) 27 | $(CC) -o test $(SRC) $(CXXFLAGS) 28 | 29 | clean: 30 | -$(RM) $(SRC) test 31 | 32 | .PHONY: clean 33 | -------------------------------------------------------------------------------- /proclamation.json: -------------------------------------------------------------------------------- 1 | { 2 | "#": "This is a config file for Proclamation, the changelog combiner: https://gitlab.com/proclamation/proclamation", 3 | "SPDX-License-Identifier: CC0-1.0": "", 4 | "SPDX-FileCopyrightText: 2020 Collabora, Ltd. and the Proclamation contributors": "", 5 | "$schema": "https://proclamation.gitlab.io/proclamation/proclamation.schema.json", 6 | "project_name": "jnipp", 7 | "base_url": "https://github.com/mitchdowd/jnipp", 8 | "news_filename": "CHANGELOG.md", 9 | "sections": { 10 | "Changes": { 11 | "directory": "changes/templates" 12 | }, 13 | "Fixes": { 14 | "directory": "changes/misc" 15 | }, 16 | "Additions": { 17 | "directory": "changes/script" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021, Collabora, Ltd. 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | add_executable(main_test main.cpp testing.h) 6 | target_link_libraries(main_test PRIVATE jnipp) 7 | add_test(NAME main_test COMMAND main_test) 8 | 9 | add_executable(external_create external_create.cpp testing.h) 10 | target_link_libraries(external_create PUBLIC jnipp ${JNI_LIBRARIES}) 11 | target_include_directories(external_create PUBLIC ${JNI_INCLUDE_DIRS}) 12 | add_test(NAME external_create COMMAND external_create) 13 | 14 | 15 | add_executable(external_detach external_detach.cpp testing.h) 16 | target_link_libraries(external_detach PUBLIC jnipp ${JNI_LIBRARIES}) 17 | target_include_directories(external_detach PUBLIC ${JNI_INCLUDE_DIRS}) 18 | add_test(NAME external_detach COMMAND external_detach) 19 | -------------------------------------------------------------------------------- /tests/external_create.cpp: -------------------------------------------------------------------------------- 1 | // Project Dependencies 2 | #include 3 | #include 4 | 5 | // Standard Dependencies 6 | #include 7 | 8 | // Local Dependencies 9 | #include "testing.h" 10 | 11 | /* 12 | jni::Vm Tests 13 | */ 14 | TEST(Vm_externalCreateAndAttach) { 15 | JNIEnv *env; 16 | JavaVMInitArgs args = {}; 17 | args.version = JNI_VERSION_1_2; 18 | JavaVM *javaVm{}; 19 | auto ret = JNI_CreateJavaVM(&javaVm, (void **)&env, &args); 20 | ASSERT(ret == 0); 21 | 22 | { 23 | jni::init(env); 24 | jni::Class cls("java/lang/String"); 25 | } 26 | JavaVM *localVmPointer{}; 27 | 28 | ret = env->GetJavaVM(&localVmPointer); 29 | ASSERT(ret == 0); 30 | } 31 | 32 | int main() { 33 | // jni::Vm Tests 34 | RUN_TEST(Vm_externalCreateAndAttach); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /tests/external_detach.cpp: -------------------------------------------------------------------------------- 1 | // Project Dependencies 2 | #include 3 | #include 4 | 5 | // Standard Dependencies 6 | #include 7 | 8 | // Local Dependencies 9 | #include "testing.h" 10 | 11 | /* 12 | jni::Vm Tests 13 | */ 14 | TEST(Vm_externalDetach) { 15 | jni::Vm vm; 16 | 17 | jni::Class cls("java/lang/String"); 18 | 19 | JNIEnv *env = (JNIEnv *)jni::env(); 20 | JavaVM *localVmPointer{}; 21 | 22 | auto ret = env->GetJavaVM(&localVmPointer); 23 | ASSERT(ret == 0); 24 | ret = localVmPointer->DetachCurrentThread(); 25 | ASSERT(ret == 0); 26 | 27 | ASSERT(1); 28 | } 29 | 30 | int main() { 31 | // jni::Vm Tests 32 | RUN_TEST(Vm_externalDetach); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | // Project Dependencies 2 | #include 3 | 4 | // Standard Dependencies 5 | #include 6 | 7 | // Local Dependencies 8 | #include "testing.h" 9 | 10 | /* 11 | jni::Vm Tests 12 | */ 13 | 14 | 15 | TEST(Vm_detectsJreInstall) 16 | { 17 | try 18 | { 19 | jni::Vm vm; 20 | } 21 | catch (jni::InitializationException&) 22 | { 23 | ASSERT(0); 24 | return; 25 | } 26 | 27 | ASSERT(1); 28 | } 29 | 30 | TEST(Vm_notAllowedMultipleVms) 31 | { 32 | try 33 | { 34 | jni::Vm firstVm; 35 | jni::Vm secondVm; // Throws an exception. 36 | } 37 | catch (jni::InitializationException&) 38 | { 39 | ASSERT(1); 40 | return; 41 | } 42 | 43 | ASSERT(0); 44 | } 45 | 46 | /* 47 | jni::Class Tests 48 | */ 49 | 50 | 51 | TEST(Class_findByName_success) 52 | { 53 | jni::Class cls("java/lang/String"); 54 | 55 | ASSERT(!cls.isNull()); 56 | } 57 | 58 | TEST(Class_findByName_failure) 59 | { 60 | try 61 | { 62 | jni::Class cls("does/not/Exist"); 63 | } 64 | catch (jni::NameResolutionException&) 65 | { 66 | ASSERT(1); 67 | return; 68 | } 69 | 70 | ASSERT(0); 71 | } 72 | 73 | TEST(Class_getName) 74 | { 75 | jni::Class cls("java/lang/String"); 76 | 77 | ASSERT(cls.getName() == "java.lang.String"); 78 | } 79 | 80 | TEST(Class_getParent) 81 | { 82 | jni::Class parent = jni::Class("java/lang/Integer").getParent(); 83 | 84 | ASSERT(parent.getName() == "java.lang.Number"); 85 | } 86 | 87 | TEST(Class_newInstance) 88 | { 89 | jni::Class Integer("java/lang/Integer"); 90 | jni::method_t constructor = Integer.getConstructor("(Ljava/lang/String;)V"); 91 | jni::Object i = Integer.newInstance(constructor, "123"); 92 | jni::Object str = jni::Class("java/lang/String").newInstance(); 93 | 94 | ASSERT(!i.isNull()); 95 | ASSERT(!str.isNull()); 96 | } 97 | 98 | TEST(Class_newInstance_withArgs) 99 | { 100 | jni::Object str1 = jni::Class("java/lang/String").newInstance("Testing..."); 101 | jni::Object str2 = jni::Class("java/lang/String").newInstance(L"Testing..."); 102 | 103 | ASSERT(!str1.isNull()); 104 | ASSERT(!str2.isNull()); 105 | } 106 | 107 | TEST(Class_getStaticField) 108 | { 109 | jni::field_t field = jni::Class("java/lang/Integer").getStaticField("MAX_VALUE", "I"); 110 | 111 | ASSERT(field); 112 | } 113 | 114 | TEST(Class_getMethod) 115 | { 116 | jni::method_t method1 = jni::Class("java/lang/Integer").getMethod("intValue", "()I"); 117 | jni::method_t method2 = jni::Class("java/lang/Integer").getMethod("intValue()I"); 118 | 119 | ASSERT(method1); 120 | ASSERT(method2); 121 | } 122 | 123 | TEST(Class_getStaticMethod) 124 | { 125 | jni::method_t method = jni::Class("java/lang/Integer").getStaticMethod("compare", "(II)I"); 126 | 127 | ASSERT(method); 128 | } 129 | 130 | TEST(Class_call) 131 | { 132 | jni::Class Integer("java/lang/Math"); 133 | 134 | double x = Integer.call("random"); 135 | 136 | ASSERT(x >= 0.0); 137 | ASSERT(x < 1.0); 138 | } 139 | 140 | TEST(Class_get_staticField) 141 | { 142 | jni::Class Integer("java/lang/Integer"); 143 | jni::field_t field = Integer.getStaticField("SIZE", "I"); 144 | 145 | ASSERT(Integer.get(field) == 32); 146 | } 147 | 148 | TEST(Class_get_staticField_byName) 149 | { 150 | jni::Class Integer("java/lang/Integer"); 151 | jni::Class Character("java/lang/Character"); 152 | jni::Class Short("java/lang/Short"); 153 | jni::Class Long("java/lang/Long"); 154 | jni::Class Float("java/lang/Float"); 155 | jni::Class Double("java/lang/Double"); 156 | 157 | ASSERT(Short.get("MAX_VALUE") == short(0x7FFF)); 158 | ASSERT(Character.get("MAX_VALUE") == L'\xFFFF') 159 | ASSERT(Integer.get("MAX_VALUE") == int(0x7FFFFFFF)); 160 | ASSERT(Long.get("MAX_VALUE") == (long long) (0x7FFFFFFFFFFFFFFF)); 161 | ASSERT(std::isnan(Float.get("NaN"))); 162 | ASSERT(std::isnan(Double.get("NaN"))); 163 | } 164 | 165 | TEST(Class_getConstructor) 166 | { 167 | jni::Class Integer("java/lang/Integer"); 168 | jni::method_t constructor = Integer.getConstructor("(Ljava/lang/String;)V"); 169 | 170 | ASSERT(constructor); 171 | } 172 | 173 | TEST(Class_call_staticMethod) 174 | { 175 | jni::Class Integer("java/lang/Integer"); 176 | jni::method_t method = Integer.getStaticMethod("parseInt", "(Ljava/lang/String;)I"); 177 | 178 | int i = Integer.call(method, "1000"); 179 | 180 | ASSERT(i == 1000); 181 | } 182 | 183 | TEST(Class_call_staticMethod_byName) 184 | { 185 | int i = jni::Class("java/lang/Integer").call("parseInt", "1000"); 186 | bool b = jni::Class("java/lang/Boolean").call("parseBoolean", "true"); 187 | wchar_t c = jni::Class("java/lang/Character").call("toLowerCase", L'X'); 188 | short s = jni::Class("java/lang/Short").call("parseShort", "1000"); 189 | long long l = jni::Class("java/lang/Long").call("parseLong", "1000"); 190 | float f = jni::Class("java/lang/Float").call("parseFloat", "123.0"); 191 | double d = jni::Class("java/lang/Double").call("parseDouble", "123.0"); 192 | 193 | ASSERT(i == 1000); 194 | ASSERT(b == true); 195 | ASSERT(c == L'x'); 196 | ASSERT(s == 1000); 197 | ASSERT(l == 1000); 198 | ASSERT(f == 123.0); // Warning: floating point comparison. 199 | ASSERT(d == 123.0); // Warning: floating point comparison. 200 | } 201 | 202 | /* 203 | jni::Object Tests 204 | */ 205 | 206 | // Must run before loading JVM 207 | TEST(Object_noDestructorException) 208 | { 209 | jni::Object o; 210 | } 211 | 212 | TEST(Object_defaultConstructor_isNull) 213 | { 214 | jni::Object o; 215 | 216 | ASSERT(o.isNull()); 217 | } 218 | 219 | TEST(Object_copyConstructorIsSameObject) 220 | { 221 | jni::Object a = jni::Class("java/lang/String").newInstance(); 222 | jni::Object b = a; 223 | 224 | ASSERT(a == b); 225 | } 226 | 227 | TEST(Object_moveConstructor) 228 | { 229 | jni::Object a = jni::Class("java/lang/String").newInstance(); 230 | jni::Object b = std::move(a); 231 | 232 | ASSERT(a.isNull()); 233 | ASSERT(!b.isNull()); 234 | } 235 | 236 | TEST(Object_copyAssignmentOperator) 237 | { 238 | jni::Object a = jni::Class("java/lang/String").newInstance(); 239 | jni::Object b = jni::Class("java/lang/Integer").newInstance(0); 240 | 241 | a = b; 242 | 243 | ASSERT(a == b); 244 | } 245 | 246 | TEST(Object_moveAssignmentOperator) 247 | { 248 | jni::Object a = jni::Class("java/lang/String").newInstance(); 249 | jni::Object b = jni::Class("java/lang/Integer").newInstance(0); 250 | 251 | a = std::move(b); 252 | 253 | ASSERT(!a.isNull()); 254 | ASSERT(b.isNull()); 255 | } 256 | 257 | TEST(Object_nullary_construct_from_signature) 258 | { 259 | jni::Class String("java/lang/String"); 260 | jni::method_t init = String.getMethod("", "()V"); 261 | jni::Object i = String.newInstance(init); 262 | ASSERT(!i.isNull()); 263 | jni::internal::ArgArray<> a; 264 | } 265 | 266 | TEST(Object_call) 267 | { 268 | jni::Class Integer("java/lang/Integer"); 269 | jni::method_t intValue = Integer.getMethod("intValue", "()I"); 270 | jni::Object i = Integer.newInstance(100); 271 | 272 | ASSERT(i.call(intValue) == 100); 273 | } 274 | 275 | TEST(Object_call_byName) 276 | { 277 | jni::Object i = jni::Class("java/lang/Integer").newInstance(100); 278 | jni::Object b = jni::Class("java/lang/Boolean").newInstance(true); 279 | jni::Object s = jni::Class("java/lang/Short").newInstance(short(100)); 280 | jni::Object l = jni::Class("java/lang/Long").newInstance(100LL); 281 | jni::Object f = jni::Class("java/lang/Float").newInstance(100.0f); 282 | jni::Object d = jni::Class("java/lang/Double").newInstance(100.0); 283 | 284 | ASSERT(i.call("intValue") == 100); 285 | ASSERT(s.call("shortValue") == 100); 286 | ASSERT(b.call("booleanValue") == true); 287 | ASSERT(l.call("longValue") == 100LL); 288 | ASSERT(f.call("floatValue") == 100.0f); // Warning: Floating point comparison. 289 | ASSERT(d.call("doubleValue") == 100.0); // Warning: Floating point comparison. 290 | ASSERT(i.call("toString") == L"100"); 291 | ASSERT(i.call("toString") == "100"); 292 | } 293 | 294 | TEST(Object_call_withArgs) 295 | { 296 | jni::Class String("java/lang/String"); 297 | jni::method_t charAt = String.getMethod("charAt", "(I)C"); 298 | jni::Object str = String.newInstance("Testing"); 299 | 300 | ASSERT(str.call(charAt, 1) == L'e'); 301 | } 302 | 303 | TEST(Object_call_byNameWithArgs) 304 | { 305 | jni::Object str = jni::Class("java/lang/String").newInstance("Testing"); 306 | jni::Object str2 = jni::Class("java/lang/String").newInstance(L"Testing"); 307 | 308 | ASSERT(str.call("charAt", 1) == L'e'); 309 | ASSERT(str2.call("charAt", 1) == L'e'); 310 | } 311 | 312 | TEST(Object_call_returningArray) { 313 | jni::Object str = jni::Class("java/lang/String").newInstance("Testing"); 314 | 315 | { 316 | auto getBytes = 317 | jni::Class("java/lang/String").getMethod("getBytes", "()[B"); 318 | auto bytes = str.call>(getBytes); 319 | ASSERT(bytes.getLength() == 7); 320 | } 321 | { 322 | auto bytes = str.call>("getBytes"); 323 | ASSERT(bytes.getLength() == 7); 324 | } 325 | } 326 | 327 | TEST(Object_makeLocalReference) 328 | { 329 | jni::Object str = jni::Class("java/lang/String").newInstance("Testing"); 330 | 331 | jni::jobject local = str.makeLocalReference(); 332 | ASSERT(local != nullptr); 333 | ASSERT(local != str.getHandle()); 334 | 335 | jni::Object fromLocal(local, jni::Object::DeleteLocalInput); 336 | ASSERT(!fromLocal.isNull()); 337 | ASSERT(str == fromLocal); 338 | } 339 | 340 | /* 341 | jni::Enum Tests 342 | */ 343 | 344 | 345 | TEST(Enum_get) 346 | { 347 | jni::Class Thread("java/lang/Thread"); 348 | jni::Enum State("java/lang/Thread$State"); 349 | jni::method_t currentThread = Thread.getStaticMethod("currentThread", "()Ljava/lang/Thread;"); 350 | jni::method_t getState = Thread.getMethod("getState", "()Ljava/lang/Thread$State;"); 351 | 352 | jni::Object thread = Thread.call(currentThread); 353 | jni::Object state = thread.call(getState); 354 | 355 | ASSERT(state == State.get("RUNNABLE")); 356 | } 357 | 358 | /* 359 | jni::Array Tests 360 | */ 361 | 362 | TEST(Array_defaultConstructor) 363 | { 364 | jni::Array a; 365 | 366 | ASSERT(a.getLength() == 0); 367 | ASSERT(a.isNull()); 368 | } 369 | 370 | TEST(Array_constructor) 371 | { 372 | jni::Array a(10); 373 | 374 | ASSERT(a.getLength() == 10); 375 | ASSERT(!a.isNull()); 376 | } 377 | 378 | TEST(Array_constructor_eachType) 379 | { 380 | jni::Array b(10); 381 | jni::Array c(10); 382 | jni::Array s(10); 383 | jni::Array i(10); 384 | jni::Array l(10); 385 | jni::Array f(10); 386 | jni::Array d(10); 387 | jni::Array str(10); 388 | jni::Array wstr(10); 389 | jni::Array obj(10); 390 | jni::Array obj2(10, jni::Class("java/lang/Integer")); 391 | 392 | jni::Object* objs[] = { &b, &c, &s, &i, &l, &f, &d, &str, &wstr, &obj, &obj2 }; 393 | 394 | for (auto o : objs) 395 | ASSERT(!o->isNull()); 396 | } 397 | 398 | TEST(Array_constructor_withType) 399 | { 400 | jni::Array a(10, jni::Class("java/lang/Integer")); 401 | 402 | ASSERT(a.getLength() == 10); 403 | ASSERT(!a.isNull()); 404 | } 405 | 406 | TEST(Array_copyConstructor) 407 | { 408 | jni::Array a(10); 409 | jni::Array b = a; 410 | 411 | ASSERT(a == b); 412 | } 413 | 414 | TEST(Array_moveConstructor) 415 | { 416 | jni::Array a(10); 417 | jni::Array b = std::move(a); 418 | 419 | ASSERT(a.isNull()); 420 | ASSERT(!b.isNull()); 421 | } 422 | 423 | TEST(Array_copyAssignmentOperator) 424 | { 425 | jni::Array a(10); 426 | jni::Array b(11); 427 | 428 | a = b; 429 | 430 | ASSERT(a == b); 431 | } 432 | 433 | TEST(Array_moveAssignmentOperator) 434 | { 435 | jni::Array a(10); 436 | jni::Array b(11); 437 | 438 | a = std::move(b); 439 | 440 | ASSERT(!a.isNull()); 441 | ASSERT(b.isNull()); 442 | } 443 | 444 | TEST(Array_getElement_defaultValue) 445 | { 446 | jni::Array a(10); 447 | jni::Array s(10); 448 | 449 | ASSERT(a.getElement(0) == 0); 450 | ASSERT(s.getElement(0).length() == 0); 451 | } 452 | 453 | TEST(Array_getElement_indexException) 454 | { 455 | jni::Array a(10); 456 | 457 | try 458 | { 459 | int result = a.getElement(1000); 460 | ASSERT(0); 461 | } 462 | catch (jni::Exception&) 463 | { 464 | ASSERT(1); 465 | } 466 | } 467 | 468 | TEST(Array_setElement_basicType) 469 | { 470 | jni::Array a(10); 471 | 472 | for (int i = 0; i < 10; i++) 473 | a.setElement(i, i); 474 | 475 | for (int i = 0; i < 10; i++) 476 | ASSERT(a.getElement(i) == i); 477 | } 478 | 479 | TEST(Array_setElement_string) 480 | { 481 | jni::Array a(10); 482 | 483 | for (int i = 0; i < 10; i++) 484 | a.setElement(i, std::to_wstring(i)); 485 | 486 | for (int i = 0; i < 10; i++) 487 | ASSERT(a.getElement(i) == std::to_wstring(i)); 488 | } 489 | 490 | TEST(Array_setElement_indexException) 491 | { 492 | jni::Array s(10); 493 | 494 | try 495 | { 496 | auto result = s.getElement(1000); 497 | ASSERT(0); 498 | } 499 | catch (jni::Exception&) 500 | { 501 | ASSERT(1); 502 | } 503 | } 504 | 505 | /* 506 | Argument Type Tests 507 | */ 508 | 509 | 510 | TEST(Arg_bool) 511 | { 512 | std::string str1 = jni::Class("java/lang/String").call("valueOf", true); 513 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", true); 514 | 515 | ASSERT(str1 == "true"); 516 | ASSERT(str2 == L"true"); 517 | } 518 | 519 | TEST(Arg_wchar) 520 | { 521 | std::string str1 = jni::Class("java/lang/String").call("valueOf", L'X'); 522 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", L'X'); 523 | 524 | ASSERT(str1 == "X"); 525 | ASSERT(str2 == L"X"); 526 | } 527 | 528 | TEST(Arg_double) 529 | { 530 | std::string str1 = jni::Class("java/lang/String").call("valueOf", 123.0); 531 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123.0); 532 | 533 | ASSERT(str1 == "123.0"); 534 | ASSERT(str2 == L"123.0"); 535 | } 536 | 537 | TEST(Arg_float) 538 | { 539 | std::string str1 = jni::Class("java/lang/String").call("valueOf", 123.0f); 540 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123.0f); 541 | 542 | ASSERT(str1 == "123.0"); 543 | ASSERT(str2 == L"123.0"); 544 | } 545 | 546 | TEST(Arg_int) 547 | { 548 | std::string str1 = jni::Class("java/lang/String").call("valueOf", 123); 549 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123); 550 | 551 | ASSERT(str1 == "123"); 552 | ASSERT(str2 == L"123"); 553 | } 554 | 555 | TEST(Arg_longLong) 556 | { 557 | std::string str1 = jni::Class("java/lang/String").call("valueOf", 123LL); 558 | std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123LL); 559 | 560 | ASSERT(str1 == "123"); 561 | ASSERT(str2 == L"123"); 562 | } 563 | 564 | TEST(Arg_Object) 565 | { 566 | jni::Object str1 = jni::Class("java/lang/String").newInstance("123"); 567 | int i = jni::Class("java/lang/Integer").call("parseInt", str1); 568 | 569 | ASSERT(i == 123); 570 | } 571 | 572 | TEST(Arg_ObjectPtr) 573 | { 574 | jni::Object str1 = jni::Class("java/lang/String").newInstance("123"); 575 | int i = jni::Class("java/lang/Integer").call("parseInt", &str1); 576 | 577 | ASSERT(i == 123); 578 | } 579 | 580 | int main() 581 | { 582 | // Tests that depend on having no JVM 583 | RUN_TEST(Object_noDestructorException); 584 | 585 | // jni::Vm Tests 586 | RUN_TEST(Vm_detectsJreInstall); 587 | RUN_TEST(Vm_notAllowedMultipleVms); 588 | 589 | { 590 | jni::Vm vm; 591 | 592 | // jni::Class Tests 593 | RUN_TEST(Class_findByName_success); 594 | RUN_TEST(Class_findByName_failure); 595 | RUN_TEST(Class_getName); 596 | RUN_TEST(Class_getParent); 597 | RUN_TEST(Class_newInstance); 598 | RUN_TEST(Class_newInstance_withArgs); 599 | RUN_TEST(Class_getStaticField); 600 | RUN_TEST(Class_getMethod); 601 | RUN_TEST(Class_getStaticMethod); 602 | RUN_TEST(Class_call); 603 | RUN_TEST(Class_get_staticField); 604 | RUN_TEST(Class_get_staticField_byName); 605 | RUN_TEST(Class_call_staticMethod_byName); 606 | RUN_TEST(Class_getConstructor); 607 | 608 | // jni::Object Tests 609 | RUN_TEST(Object_defaultConstructor_isNull); 610 | RUN_TEST(Object_nullary_construct_from_signature); 611 | RUN_TEST(Object_copyConstructorIsSameObject); 612 | RUN_TEST(Object_moveConstructor); 613 | RUN_TEST(Object_copyAssignmentOperator); 614 | RUN_TEST(Object_moveAssignmentOperator); 615 | RUN_TEST(Object_call); 616 | RUN_TEST(Object_call_byName); 617 | RUN_TEST(Object_call_withArgs); 618 | RUN_TEST(Object_call_byNameWithArgs); 619 | RUN_TEST(Object_call_returningArray); 620 | RUN_TEST(Object_makeLocalReference); 621 | 622 | // jni::Enum Tests 623 | RUN_TEST(Enum_get); 624 | 625 | // jni::Array Tests 626 | RUN_TEST(Array_defaultConstructor); 627 | RUN_TEST(Array_constructor); 628 | RUN_TEST(Array_constructor_eachType); 629 | RUN_TEST(Array_constructor_withType); 630 | RUN_TEST(Array_copyConstructor); 631 | RUN_TEST(Array_moveConstructor); 632 | RUN_TEST(Array_getElement_defaultValue); 633 | RUN_TEST(Array_getElement_indexException); 634 | RUN_TEST(Array_setElement_basicType); 635 | RUN_TEST(Array_setElement_string); 636 | RUN_TEST(Array_setElement_indexException); 637 | 638 | // Argument Type Tests 639 | RUN_TEST(Arg_bool); 640 | RUN_TEST(Arg_wchar); 641 | RUN_TEST(Arg_double); 642 | RUN_TEST(Arg_float); 643 | RUN_TEST(Arg_int); 644 | RUN_TEST(Arg_longLong); 645 | RUN_TEST(Arg_Object); 646 | RUN_TEST(Arg_ObjectPtr); 647 | } 648 | 649 | return 0; 650 | } 651 | 652 | -------------------------------------------------------------------------------- /tests/testing.h: -------------------------------------------------------------------------------- 1 | #ifndef _TESTING_H_ 2 | #define _TESTING_H_ 1 3 | 4 | // Standard Dependencies 5 | #include 6 | #include 7 | 8 | #ifdef ASSERT 9 | # undef ASSERT 10 | #endif 11 | 12 | /** Run the test with the given name. */ 13 | #define RUN_TEST(TestName) { \ 14 | bool __test_result = true; \ 15 | std::cout << "Executing test " << std::left << std::setw(40) << #TestName; \ 16 | TestName(__test_result); \ 17 | std::cout << "=> " << (__test_result ? "Success" : "Fail") << std::endl; \ 18 | } 19 | 20 | /** Define a test with the given name. */ 21 | #define TEST(TestName) \ 22 | void TestName(bool& __test_result) 23 | 24 | /** Ensures the given condition passes for the test to pass. */ 25 | #define ASSERT(condition) { \ 26 | if (!(condition)) { \ 27 | __test_result = false; \ 28 | return; \ 29 | } \ 30 | } 31 | 32 | #endif // _TESTING_H_ 33 | 34 | -------------------------------------------------------------------------------- /tests/tests.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {911DB449-3D76-4261-8FFD-9BA508ED1D5B} 23 | tests 24 | 10.0 25 | 26 | 27 | 28 | Application 29 | true 30 | v142 31 | MultiByte 32 | 33 | 34 | Application 35 | false 36 | v142 37 | true 38 | MultiByte 39 | 40 | 41 | Application 42 | true 43 | v142 44 | MultiByte 45 | 46 | 47 | Application 48 | false 49 | v142 50 | true 51 | MultiByte 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Level3 75 | Disabled 76 | true 77 | $(SolutionDir) 78 | 79 | 80 | 81 | 82 | Level3 83 | Disabled 84 | true 85 | $(SolutionDir) 86 | 87 | 88 | 89 | 90 | Level3 91 | MaxSpeed 92 | true 93 | true 94 | true 95 | $(SolutionDir) 96 | 97 | 98 | true 99 | true 100 | 101 | 102 | 103 | 104 | Level3 105 | MaxSpeed 106 | true 107 | true 108 | true 109 | $(SolutionDir) 110 | 111 | 112 | true 113 | true 114 | 115 | 116 | 117 | 118 | {a32d4b7d-7009-4c87-9de8-d7db659f5b58} 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /tests/tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------