├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── main.cpp /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'README.md' 7 | - '.gitignore' 8 | - 'LICENSE' 9 | 10 | jobs: 11 | define-matrix: 12 | runs-on: ubuntu-latest 13 | 14 | outputs: 15 | configs: ${{ steps.matrix.outputs.configs }} 16 | 17 | steps: 18 | - name: Define Matrix 19 | id: matrix 20 | shell: python 21 | run: | 22 | import json 23 | import os 24 | 25 | abis = ['x86_64'] 26 | 27 | apis = [29, 30, 35] 28 | 29 | ndks = { 30 | '21.3.6528147': 30, 31 | '27.2.12479018': 35, 32 | '29.0.13113456': 35 33 | } 34 | 35 | configs = [] 36 | 37 | for ndk, max_api in ndks.items(): 38 | for api in apis: 39 | if api > max_api: 40 | continue 41 | for abi in abis: 42 | configs.append({'version': ndk, 'api': api, 'arch': abi, 'jobname': f'{ndk} - API{api} - {abi}'}) 43 | 44 | with open(os.environ['GITHUB_OUTPUT'], 'w') as env: 45 | print('configs=' + json.dumps(configs), file=env) 46 | 47 | build: 48 | needs: define-matrix 49 | name: ${{ matrix.jobname }} 50 | runs-on: ubuntu-latest 51 | strategy: 52 | fail-fast: false 53 | matrix: 54 | include: ${{ fromJSON(needs.define-matrix.outputs.configs) }} 55 | 56 | steps: 57 | - name: Checkout 58 | uses: actions/checkout@v4 59 | 60 | - name: Enable KVM 61 | run: | 62 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 63 | sudo udevadm control --reload-rules 64 | sudo udevadm trigger --name-match=kvm 65 | 66 | - name: AVD cache 67 | uses: actions/cache@v4 68 | id: avd-cache 69 | with: 70 | path: | 71 | ~/.android/avd/* 72 | ~/.android/adb* 73 | key: avd-${{ matrix.version }}-${{ matrix.arch }}-${{ matrix.api }} 74 | 75 | - name: Create AVD and generate snapshot for caching 76 | if: steps.avd-cache.outputs.cache-hit != 'true' 77 | uses: reactivecircus/android-emulator-runner@v2 78 | with: 79 | api-level: ${{ matrix.api }} 80 | arch: ${{matrix.arch}} 81 | target: google_apis 82 | ndk: ${{ matrix.version }} 83 | force-avd-creation: false 84 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics 85 | disable-animations: false 86 | script: echo "Generated AVD snapshot for caching." 87 | 88 | - name: Run tests 89 | uses: reactivecircus/android-emulator-runner@v2 90 | with: 91 | api-level: ${{ matrix.api }} 92 | arch: ${{matrix.arch}} 93 | target: google_apis 94 | ndk: ${{ matrix.version }} 95 | force-avd-creation: false 96 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics 97 | disable-animations: true 98 | script: | 99 | cmake -S . -B out $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_TOOLCHAIN_FILE:FILEPATH=$ANDROID_SDK_ROOT/ndk/${{ matrix.version }}/build/cmake/android.toolchain.cmake -DANDROID_ABI:STRING=${{ matrix.arch }} -DANDROID_PLATFORM:STRING=${{ matrix.api }} -DANDROID_STL:STRING=c++_static 100 | cmake --build out --config Release --target run 101 | killall -INT crashpad_handler || true 102 | 103 | 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Eugene Gershnik 3 | # 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file or at 6 | # https://github.com/gershnik/android-cmdline-jni/blob/master/LICENSE 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.16) 10 | 11 | set(CMAKE_CXX_STANDARD 17) 12 | 13 | project(hello) 14 | 15 | add_executable(hello 16 | main.cpp 17 | ) 18 | 19 | target_link_options(hello PRIVATE 20 | 21 | "$<$:-Wl,--export-dynamic>" 22 | ) 23 | 24 | set(RUN_COMMAND "") 25 | 26 | if (${CMAKE_SYSTEM_NAME} STREQUAL Android) 27 | 28 | set(ANDROID_RUN_DIR /data/local/tmp/hellodir) 29 | set(ANDROID_SDK_DIR ${CMAKE_ANDROID_NDK}/../..) 30 | set(ADB ${ANDROID_SDK_DIR}/platform-tools/adb) 31 | 32 | if(${CMAKE_ANDROID_ARCH} STREQUAL x86 OR ${CMAKE_ANDROID_ARCH} STREQUAL arm) 33 | set(ANDROID_LD_LIBRARY_PATH /apex/com.android.art/lib:/apex/com.android.runtime/lib) 34 | else() 35 | set(ANDROID_LD_LIBRARY_PATH /apex/com.android.art/lib64:/apex/com.android.runtime/lib64) 36 | endif() 37 | 38 | list(APPEND RUN_COMMAND COMMAND ${ADB} shell mkdir -p ${ANDROID_RUN_DIR}) 39 | list(APPEND RUN_COMMAND COMMAND ${ADB} push hello ${ANDROID_RUN_DIR}) 40 | list(APPEND RUN_COMMAND COMMAND ${ADB} shell LD_LIBRARY_PATH=${ANDROID_LD_LIBRARY_PATH} ${ANDROID_RUN_DIR}/hello) 41 | 42 | else() 43 | list(APPEND RUN_COMMAND COMMAND hello) 44 | endif() 45 | 46 | add_custom_target(run 47 | DEPENDS hello 48 | ${RUN_COMMAND} 49 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Eugene Gershnik 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository contains minimal code to create a native Android command-line application that loads and uses ART virtual machine. 2 | 3 | More details can be found at https://gershnik.github.io/2021/03/26/load-art-from-native.html 4 | 5 | To build and run: 6 | 7 | * Make sure you have an emulator running or device connected. 8 | 9 | * Run the following, substituting 10 | * your NDK location for `$ANDROID_NDK_PATH` 11 | * `Release` or `Debug` etc. for `$BUILD_TYPE` 12 | * `x86_64`, `x86`, `armeabi-v7a` or `arm64-v8a` for `$NDK_ARCH` 13 | * If desired change `ANDROID_PLATFORM` value to something bigger than 19 14 | 15 | ```bash 16 | mkdir -p build 17 | cd build 18 | cmake .. \ 19 | -DCMAKE_BUILD_TYPE:STRING=$BUILD_TYPE \ 20 | -DCMAKE_TOOLCHAIN_FILE:FILEPATH=$ANDROID_NDK_PATH/build/cmake/android.toolchain.cmake \ 21 | -DANDROID_ABI:STRING=$NDK_ARCH \ 22 | -DANDROID_PLATFORM:STRING=19 \ 23 | -DANDROID_STL:STRING=c++_static 24 | cmake --build . --config $BUILD_TYPE --target run 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Eugene Gershnik 3 | // 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file or at 6 | // https://github.com/gershnik/android-cmdline-jni/blob/master/LICENSE 7 | // 8 | 9 | #include 10 | 11 | 12 | #if defined(__ANDROID__) 13 | 14 | #include 15 | #include 16 | 17 | extern "C" 18 | { 19 | typedef int JNI_CreateJavaVM_t(void *, void *, void *); 20 | 21 | __attribute__((visibility("default"))) void AddSpecialSignalHandlerFn() 22 | { } 23 | } 24 | 25 | static auto load_art() -> std::pair 26 | { 27 | JavaVMOption opt[] = { 28 | { "-Djava.library.path=/data/local/tmp", nullptr }, 29 | { "-verbose:jni", nullptr } 30 | }; 31 | 32 | JavaVMInitArgs args = { 33 | JNI_VERSION_1_6, 34 | std::size(opt), 35 | opt, 36 | JNI_FALSE 37 | }; 38 | 39 | void * libart = dlopen("libart.so", RTLD_NOW); 40 | if (!libart) 41 | { 42 | std::cerr << dlerror() << std::endl; 43 | abort(); 44 | } 45 | 46 | auto JNI_CreateJavaVM = (JNI_CreateJavaVM_t *)dlsym(libart, "JNI_CreateJavaVM"); 47 | if (!JNI_CreateJavaVM) 48 | { 49 | std::cerr << "No JNI_CreateJavaVM: " << dlerror() << std::endl; 50 | abort(); 51 | } 52 | 53 | std::pair ret; 54 | int res = JNI_CreateJavaVM(&ret.first, &ret.second, &args); 55 | if (res != 0) 56 | { 57 | std::cerr << "Failed to create VM: " << res << std::endl; 58 | abort(); 59 | } 60 | return ret; 61 | } 62 | 63 | #endif 64 | 65 | int main() { 66 | #if defined(__ANDROID__) 67 | 68 | auto [vm, env] = load_art(); 69 | 70 | jclass systemCls = env->FindClass("java/lang/System"); 71 | jfieldID outField = env->GetStaticFieldID(systemCls, "out", "Ljava/io/PrintStream;"); 72 | 73 | jclass printStreamCls = env->FindClass("java/io/PrintStream"); 74 | jmethodID printMethod = env->GetMethodID(printStreamCls, "print", "(Ljava/lang/String;)V"); 75 | 76 | 77 | jobject outObj = env->GetStaticObjectField(systemCls, outField); 78 | const char16_t text[] = u"Hello, World from Java!\n"; 79 | jstring jtext = env->NewString((const jchar *)text, std::size(text) - 1); 80 | env->CallVoidMethod(outObj, printMethod, jtext); 81 | 82 | #else 83 | std::cout << "Hello, World!\n"; 84 | #endif 85 | } 86 | --------------------------------------------------------------------------------