Skip to content

Commit

Permalink
JavaKit: support for Android NDK JVM
Browse files Browse the repository at this point in the history
  • Loading branch information
lhoward committed Nov 9, 2024
1 parent c922cbe commit 720f33d
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 7 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion Samples/JavaKitSampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Sources/ExampleSwiftLibrary/MySwiftLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import Glibc
import CRT
#elseif canImport(Darwin)
import Darwin.C
#elseif canImport(Android)
import Android
#endif

public func helloWorld() {
Expand Down
4 changes: 4 additions & 0 deletions Sources/JavaKit/JavaEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

import JavaRuntime

#if canImport(Android)
typealias JNINativeInterface_ = JNINativeInterface
#endif

extension UnsafeMutablePointer<JNIEnv?> {
var interface: JNINativeInterface_ { self.pointee!.pointee }
}
25 changes: 20 additions & 5 deletions Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import Foundation
#endif

public typealias JavaVMPointer = UnsafeMutablePointer<JavaVM?>
#if canImport(Android)
typealias JNIEnvPointer = UnsafeMutablePointer<JNIEnv?>
#else
typealias JNIEnvPointer = UnsafeMutableRawPointer
#endif

public final class JavaVirtualMachine: @unchecked Sendable {
/// The JNI version that we depend on.
Expand Down Expand Up @@ -57,7 +62,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)
Expand Down Expand Up @@ -138,22 +143,32 @@ 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.
if let attachError = VMError(fromJNIError: attachResult) {
throw attachError
}

JavaVirtualMachine.destroyTLS.set(environment!)
JavaVirtualMachine.destroyTLS.set(jniEnv!)

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
Expand Down
98 changes: 98 additions & 0 deletions Sources/JavaRuntime/AndroidSupport.cpp
Original file line number Diff line number Diff line change
@@ -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 <JavaRuntime.h>
#include <android/log.h>
#include <dlfcn.h>

// 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<JavaRuntime_GetDefaultJavaVMInitArgs_fn>(
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<JavaRuntime_CreateJavaVM_fn>(
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<JavaRuntime_GetCreatedJavaVMs_fn>(
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

0 comments on commit 720f33d

Please sign in to comment.