From 1509f9219e3ceb31bba12713389c1c785337355b Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Thu, 31 Oct 2024 13:42:20 +1100 Subject: [PATCH] JavaKit: support for Android NDK JVM --- Package.swift | 4 +- Samples/JavaKitSampleApp/Package.swift | 2 +- .../JavaKitExample/JavaKitExample.swift | 3 + .../ExampleSwiftLibrary/MySwiftLibrary.swift | 2 + Sources/JavaKit/JavaEnvironment.swift | 4 + .../JavaKitVM/JavaVirtualMachine.swift | 23 ++++- Sources/JavaRuntime/AndroidSupport.cpp | 98 +++++++++++++++++++ 7 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 Sources/JavaRuntime/AndroidSupport.cpp diff --git a/Package.swift b/Package.swift index cd456d76..81eeeed2 100644 --- a/Package.swift +++ b/Package.swift @@ -181,7 +181,9 @@ let package = Package( "-L\(javaHome)/lib" ], .when(platforms: [.windows])), - .linkedLibrary("jvm"), + .linkedLibrary( + "jvm", + .when(platforms: [.iOS, .macOS, .tvOS, .watchOS, .macCatalyst, .linux, .openbsd, .wasi, .windows])), ] ), .target( diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 8976bd41..8014effb 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -30,7 +30,7 @@ func findJavaHome() -> String { } let javaHome = findJavaHome() -let javaIncludePath = "\(javaHome)/include" +let javaIncludePath = ProcessInfo.processInfo.environment["JAVA_INCLUDE_PATH"] ?? "\(javaHome)/include" #if os(Linux) let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift index 42a5b789..89fa9ce4 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift @@ -27,7 +27,10 @@ extension HelloSwift: HelloSwiftNativeMethods { let answer = self.sayHelloBack(i + j) print("Swift got back \(answer) from Java") +#if !canImport(Android) + // no class variables in Kotlin print("We expect the above value to be the initial value, \(self.javaClass.initialValue)") +#endif print("Updating Java field value to something different") self.value = 2.71828 diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 36e09b14..b006128c 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Glibc import CRT #elseif canImport(Darwin) import Darwin.C +#elseif canImport(Android) +import Android #endif public func helloWorld() { diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index d74146ab..7dcc242f 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -14,6 +14,10 @@ import JavaRuntime +#if canImport(Android) +typealias JNINativeInterface_ = JNINativeInterface +#endif + extension UnsafeMutablePointer { var interface: JNINativeInterface_ { self.pointee!.pointee } } diff --git a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift index 7769fa17..fb7c64fe 100644 --- a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift @@ -19,6 +19,11 @@ import Foundation #endif public typealias JavaVMPointer = UnsafeMutablePointer +#if canImport(Android) +typealias JNIEnvPointer = UnsafeMutablePointer +#else +typealias JNIEnvPointer = UnsafeMutableRawPointer +#endif public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. @@ -52,7 +57,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { ignoreUnrecognized: Bool = false ) throws { var jvm: JavaVMPointer? = nil - var environment: UnsafeMutableRawPointer? = nil + var environment: JNIEnvPointer? = nil var vmArgs = JavaVMInitArgs() vmArgs.version = JavaVirtualMachine.jniVersion vmArgs.ignoreUnrecognized = jboolean(ignoreUnrecognized ? JNI_TRUE : JNI_FALSE) @@ -133,12 +138,18 @@ extension JavaVirtualMachine { return environment.assumingMemoryBound(to: JNIEnv?.self) } +#if canImport(Android) + var jniEnv = environment?.assumingMemoryBound(to: JNIEnv?.self) +#else + var jniEnv = environment +#endif + // Attach the current thread to the JVM. let attachResult: jint if asDaemon { - attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &jniEnv, nil) } else { - attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &jniEnv, nil) } // If we failed to attach, report that. @@ -146,7 +157,11 @@ extension JavaVirtualMachine { throw attachError } - return environment!.assumingMemoryBound(to: JNIEnv?.self) +#if canImport(Android) + return jniEnv! +#else + return jniEnv!.assumingMemoryBound(to: JNIEnv?.self) +#endif } /// Detach the current thread from the Java Virtual Machine. All Java diff --git a/Sources/JavaRuntime/AndroidSupport.cpp b/Sources/JavaRuntime/AndroidSupport.cpp new file mode 100644 index 00000000..3556b371 --- /dev/null +++ b/Sources/JavaRuntime/AndroidSupport.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#ifdef __ANDROID__ + +#include +#include +#include + +// these are not exported by the Android SDK + +extern "C" { + using JavaRuntime_GetDefaultJavaVMInitArgs_fn = jint (*)(void *vm_args); + using JavaRuntime_CreateJavaVM_fn = jint (*)(JavaVM **, JNIEnv **, void *); + using JavaRuntime_GetCreatedJavaVMs_fn = jint (*)(JavaVM **, jsize, jsize *); +} + +static JavaRuntime_GetDefaultJavaVMInitArgs_fn + JavaRuntime_GetDefaultJavaVMInitArgs; +static JavaRuntime_CreateJavaVM_fn JavaRuntime_CreateJavaVM; +static JavaRuntime_GetCreatedJavaVMs_fn JavaRuntime_GetCreatedJavaVMs; + +static void *JavaRuntime_dlhandle; + +__attribute__((constructor)) static void JavaRuntime_init(void) { + JavaRuntime_dlhandle = dlopen("libnativehelper.so", RTLD_NOW | RTLD_LOCAL); + if (JavaRuntime_dlhandle == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "failed to open libnativehelper.so: %s", dlerror()); + return; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetDefaultJavaVMInitArgs")); + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetDefaultJavaVMInitArgs not found: %s", + dlerror()); + + JavaRuntime_CreateJavaVM = reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_CreateJavaVM")); + if (JavaRuntime_CreateJavaVM == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_CreateJavaVM not found: %s", dlerror()); + + JavaRuntime_GetCreatedJavaVMs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetCreatedJavaVMs")); + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetCreatedJavaVMs not found: %s", dlerror()); +} + +__attribute__((destructor)) static void JavaRuntime_deinit(void) { + if (JavaRuntime_dlhandle) { + dlclose(JavaRuntime_dlhandle); + JavaRuntime_dlhandle = nullptr; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = nullptr; + JavaRuntime_CreateJavaVM = nullptr; + JavaRuntime_GetCreatedJavaVMs = nullptr; +} + +jint JNI_GetDefaultJavaVMInitArgs(void *vm_args) { + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetDefaultJavaVMInitArgs)(vm_args); +} + +jint JNI_CreateJavaVM(JavaVM **vm, JNIEnv **env, void *vm_args) { + if (JavaRuntime_CreateJavaVM == nullptr) + return JNI_ERR; + + return (*JavaRuntime_CreateJavaVM)(vm, env, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetCreatedJavaVMs)(vmBuf, bufLen, nVMs); +} + +#endif