From 574ddf2e0d6a0fe464ab10aa856efb16d27f98f8 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 00:49:04 -0400 Subject: [PATCH 1/8] Dynamically load libjvm rather than requiring it at build time --- Package.swift | 163 ------ Sources/CSwiftJavaJNI/AndroidSupport.cpp | 97 ---- Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h | 498 +++++++++++++++++- .../BridgedValues/JavaValue+Integers.swift | 22 - .../SwiftJavaJNICore/JavaEnvironment.swift | 7 +- .../VirtualMachine/JavaVirtualMachine.swift | 108 +++- 6 files changed, 588 insertions(+), 307 deletions(-) diff --git a/Package.swift b/Package.swift index c57c7cc..f0f747e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,133 +1,7 @@ // swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. - -import Foundation import PackageDescription -// Note: the JAVA_HOME environment variable must be set to point to where -// Java is installed, e.g., -// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. -func findJavaHome() -> String { - if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { - return home - } - - // This is a workaround for envs (some IDEs) which have trouble with - // picking up env variables during the build process - let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" - if let home = try? String(contentsOfFile: path, encoding: .utf8) { - if let lastChar = home.last, lastChar.isNewline { - return String(home.dropLast()) - } - - return home - } - - if let home = getJavaHomeFromLibexecJavaHome(), - !home.isEmpty - { - return home - } - - if let home = getJavaHomeFromSDKMAN() { - return home - } - - if let home = getJavaHomeFromPath() { - return home - } - - if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" - && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil - { - return "" - } - fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") -} - -/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. -func getJavaHomeFromLibexecJavaHome() -> String? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") - - guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { - return nil - } - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - - do { - try task.run() - task.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) - - if task.terminationStatus == 0 { - return output - } else { - return nil - } - } catch { - return nil - } -} - -func getJavaHomeFromSDKMAN() -> String? { - let home = FileManager.default.homeDirectoryForCurrentUser - .appendingPathComponent(".sdkman/candidates/java/current") - - let javaBin = home.appendingPathComponent("bin/java").path - if FileManager.default.isExecutableFile(atPath: javaBin) { - return home.path - } - return nil -} - -func getJavaHomeFromPath() -> String? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/which") - task.arguments = ["java"] - - let pipe = Pipe() - task.standardOutput = pipe - - do { - try task.run() - task.waitUntilExit() - guard task.terminationStatus == 0 else { return nil } - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - guard - let javaPath = String(data: data, encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines), - !javaPath.isEmpty - else { return nil } - - let resolved = URL(fileURLWithPath: javaPath).resolvingSymlinksInPath() - return - resolved - .deletingLastPathComponent() - .deletingLastPathComponent() - .path - } catch { - return nil - } -} - -let javaHome = findJavaHome() - -let javaIncludePath = "\(javaHome)/include" -#if os(Linux) -let javaPlatformIncludePath = "\(javaIncludePath)/linux" -#elseif os(macOS) -let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#elseif os(Windows) -let javaPlatformIncludePath = "\(javaIncludePath)/win32" -#endif - let package = Package( name: "swift-java-jni-core", products: [ @@ -139,45 +13,12 @@ let package = Package( targets: [ .target( name: "CSwiftJavaJNI", - cSettings: [ - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) - ], - swiftSettings: [ - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) - ], - linkerSettings: [ - .linkedLibrary("log", .when(platforms: [.android])) - ] ), .target( name: "SwiftJavaJNICore", dependencies: [ "CSwiftJavaJNI" - ], - swiftSettings: [ - .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])), - ], - linkerSettings: [ - .unsafeFlags( - [ - "-L\(javaHome)/lib/server", - "-Xlinker", "-rpath", - "-Xlinker", "\(javaHome)/lib/server", - ], - .when(platforms: [.linux, .macOS]) - ), - .unsafeFlags( - [ - "-L\(javaHome)/lib" - ], - .when(platforms: [.windows]) - ), - .linkedLibrary( - "jvm", - .when(platforms: [.linux, .macOS, .windows]) - ), ] ), @@ -185,10 +26,6 @@ let package = Package( name: "SwiftJavaJNICoreTests", dependencies: [ "SwiftJavaJNICore" - ], - swiftSettings: [ - .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])), ] ), ] diff --git a/Sources/CSwiftJavaJNI/AndroidSupport.cpp b/Sources/CSwiftJavaJNI/AndroidSupport.cpp index a223530..8b13789 100644 --- a/Sources/CSwiftJavaJNI/AndroidSupport.cpp +++ b/Sources/CSwiftJavaJNI/AndroidSupport.cpp @@ -1,98 +1 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 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 diff --git a/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h index fb5f71a..d44aaac 100644 --- a/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h +++ b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h @@ -12,9 +12,499 @@ // //===----------------------------------------------------------------------===// -#ifndef CSwiftJavaJNI_h -#define CSwiftJavaJNI_h +#ifdef __ANDROID__ +// jni.h comes along "for free" on Android +#import +#else +// Minimal C-only JNI header providing the subset of types, structs, and +// function-pointer tables required by swift-java-jni-core. +// +// This is ABI-compatible with the standard JNI specification at +// http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + +#include +#include + +// --------------------------------------------------------------------------- +// Primitive types +// --------------------------------------------------------------------------- + +typedef uint8_t jboolean; /* unsigned 8 bits */ +typedef int8_t jbyte; /* signed 8 bits */ +typedef uint16_t jchar; /* unsigned 16 bits */ +typedef int16_t jshort; /* signed 16 bits */ +typedef int32_t jint; /* signed 32 bits */ +typedef int64_t jlong; /* signed 64 bits */ +typedef float jfloat; /* 32-bit IEEE 754 */ +typedef double jdouble; /* 64-bit IEEE 754 */ + +typedef jint jsize; + +// --------------------------------------------------------------------------- +// Reference types +// --------------------------------------------------------------------------- + +typedef void* jobject; +typedef jobject jclass; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jobjectArray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jobject jthrowable; +typedef jobject jweak; + +// --------------------------------------------------------------------------- +// Field / method IDs +// --------------------------------------------------------------------------- + +struct _jfieldID; +typedef struct _jfieldID* jfieldID; + +struct _jmethodID; +typedef struct _jmethodID* jmethodID; + +// --------------------------------------------------------------------------- +// jvalue union +// --------------------------------------------------------------------------- + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +// --------------------------------------------------------------------------- +// Object reference type enum +// --------------------------------------------------------------------------- + +typedef enum jobjectRefType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + +// --------------------------------------------------------------------------- +// Native method descriptor +// --------------------------------------------------------------------------- + +typedef struct { + const char* name; + const char* signature; + void* fnPtr; +} JNINativeMethod; + +// --------------------------------------------------------------------------- +// Forward declarations & typedefs for JNIEnv / JavaVM +// --------------------------------------------------------------------------- + +struct JNINativeInterface; +struct JNIInvokeInterface; + +typedef const struct JNINativeInterface* JNIEnv; +typedef const struct JNIInvokeInterface* JavaVM; + +// --------------------------------------------------------------------------- +// JNINativeInterface – complete function-pointer table +// +// Every slot is present so that the struct layout is +// ABI-compatible with the standard JNI specification. +// --------------------------------------------------------------------------- + +struct JNINativeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + void* reserved3; + + jint (*GetVersion)(JNIEnv*); + + jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize); + jclass (*FindClass)(JNIEnv*, const char*); + + jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); + jfieldID (*FromReflectedField)(JNIEnv*, jobject); + jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean); + + jclass (*GetSuperclass)(JNIEnv*, jclass); + jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass); + + jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean); + + jint (*Throw)(JNIEnv*, jthrowable); + jint (*ThrowNew)(JNIEnv*, jclass, const char*); + jthrowable (*ExceptionOccurred)(JNIEnv*); + void (*ExceptionDescribe)(JNIEnv*); + void (*ExceptionClear)(JNIEnv*); + void (*FatalError)(JNIEnv*, const char*); + + jint (*PushLocalFrame)(JNIEnv*, jint); + jobject (*PopLocalFrame)(JNIEnv*, jobject); + + jobject (*NewGlobalRef)(JNIEnv*, jobject); + void (*DeleteGlobalRef)(JNIEnv*, jobject); + void (*DeleteLocalRef)(JNIEnv*, jobject); + jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); + + jobject (*NewLocalRef)(JNIEnv*, jobject); + jint (*EnsureLocalCapacity)(JNIEnv*, jint); + + jobject (*AllocObject)(JNIEnv*, jclass); + jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); + jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + jclass (*GetObjectClass)(JNIEnv*, jobject); + jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass); + jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); + + /* Instance method calls */ + jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); + jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); + jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); + jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); + jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); + jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); + jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); + jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); + jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); + jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); + void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list); + void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + + /* Non-virtual instance method calls */ + jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, jmethodID, ...); + void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass, jmethodID, va_list); + void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass, jmethodID, const jvalue*); + + /* Instance fields */ + jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); + jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); + jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); + jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); + jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); + jint (*GetIntField)(JNIEnv*, jobject, jfieldID); + jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); + jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); + jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); + + void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); + void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); + void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); + void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); + void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); + void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); + void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); + void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); + void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble); + + /* Static methods */ + jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); + jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); + jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); + jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); + jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); + jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); + jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); + jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); + jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); + jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); + void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list); + void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + /* Static fields */ + jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); + jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); + jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); + jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); + jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); + jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); + jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); + jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); + jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); + + void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); + void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); + void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); + void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); + void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); + void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); + void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); + void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); + void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); + + /* Strings */ + jstring (*NewString)(JNIEnv*, const jchar*, jsize); + jsize (*GetStringLength)(JNIEnv*, jstring); + const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); + jstring (*NewStringUTF)(JNIEnv*, const char*); + jsize (*GetStringUTFLength)(JNIEnv*, jstring); + const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*); + + /* Arrays */ + jsize (*GetArrayLength)(JNIEnv*, jarray); + + jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject); + jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); + void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject); + + jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); + jbyteArray (*NewByteArray)(JNIEnv*, jsize); + jcharArray (*NewCharArray)(JNIEnv*, jsize); + jshortArray (*NewShortArray)(JNIEnv*, jsize); + jintArray (*NewIntArray)(JNIEnv*, jsize); + jlongArray (*NewLongArray)(JNIEnv*, jsize); + jfloatArray (*NewFloatArray)(JNIEnv*, jsize); + jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize); + + jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); + jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); + jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); + jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); + jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); + jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); + jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); + jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*); + + void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*, jint); + void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray, jbyte*, jint); + void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray, jchar*, jint); + void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray, jshort*, jint); + void (*ReleaseIntArrayElements)(JNIEnv*, jintArray, jint*, jint); + void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray, jlong*, jint); + void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray, jfloat*, jint); + void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray, jdouble*, jint); + + void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray, jsize, jsize, jboolean*); + void (*GetByteArrayRegion)(JNIEnv*, jbyteArray, jsize, jsize, jbyte*); + void (*GetCharArrayRegion)(JNIEnv*, jcharArray, jsize, jsize, jchar*); + void (*GetShortArrayRegion)(JNIEnv*, jshortArray, jsize, jsize, jshort*); + void (*GetIntArrayRegion)(JNIEnv*, jintArray, jsize, jsize, jint*); + void (*GetLongArrayRegion)(JNIEnv*, jlongArray, jsize, jsize, jlong*); + void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray, jsize, jsize, jfloat*); + void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray, jsize, jsize, jdouble*); + + void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray, jsize, jsize, const jboolean*); + void (*SetByteArrayRegion)(JNIEnv*, jbyteArray, jsize, jsize, const jbyte*); + void (*SetCharArrayRegion)(JNIEnv*, jcharArray, jsize, jsize, const jchar*); + void (*SetShortArrayRegion)(JNIEnv*, jshortArray, jsize, jsize, const jshort*); + void (*SetIntArrayRegion)(JNIEnv*, jintArray, jsize, jsize, const jint*); + void (*SetLongArrayRegion)(JNIEnv*, jlongArray, jsize, jsize, const jlong*); + void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray, jsize, jsize, const jfloat*); + void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray, jsize, jsize, const jdouble*); + + /* Native method registration */ + jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, jint); + jint (*UnregisterNatives)(JNIEnv*, jclass); + + /* Monitors */ + jint (*MonitorEnter)(JNIEnv*, jobject); + jint (*MonitorExit)(JNIEnv*, jobject); + + /* VM access */ + jint (*GetJavaVM)(JNIEnv*, JavaVM**); + + /* String region access */ + void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*); + void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); + + /* Critical array / string access */ + void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); + void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint); + + const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*); + + /* Weak global references */ + jweak (*NewWeakGlobalRef)(JNIEnv*, jobject); + void (*DeleteWeakGlobalRef)(JNIEnv*, jweak); + + /* Exception checking */ + jboolean (*ExceptionCheck)(JNIEnv*); + + /* Direct byte buffers */ + jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong); + void* (*GetDirectBufferAddress)(JNIEnv*, jobject); + jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject); + + /* Added in JNI 1.6 */ + jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject); +}; + +// --------------------------------------------------------------------------- +// _JNIEnv wrapper struct (C version – no methods) +// --------------------------------------------------------------------------- + +struct _JNIEnv { + const struct JNINativeInterface* functions; +}; + +// --------------------------------------------------------------------------- +// JNI invocation interface +// --------------------------------------------------------------------------- + +struct JNIInvokeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + + jint (*DestroyJavaVM)(JavaVM*); + jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); + jint (*DetachCurrentThread)(JavaVM*); + jint (*GetEnv)(JavaVM*, void**, jint); + jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); +}; + +// --------------------------------------------------------------------------- +// _JavaVM wrapper struct (C version – no methods) +// --------------------------------------------------------------------------- + +struct _JavaVM { + const struct JNIInvokeInterface* functions; +}; + +// --------------------------------------------------------------------------- +// VM attach / initialization structs +// --------------------------------------------------------------------------- + +typedef struct JavaVMAttachArgs { + jint version; + const char* name; + jobject group; +} JavaVMAttachArgs; + +typedef struct JavaVMOption { + const char* optionString; + void* extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +// --------------------------------------------------------------------------- +// JVM initialization functions +// --------------------------------------------------------------------------- + +jint JNI_GetDefaultJavaVMInitArgs(void*); +jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); +jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*); + +// --------------------------------------------------------------------------- +// Prototypes for functions exported by loadable shared libs +// --------------------------------------------------------------------------- + +__attribute__ ((visibility ("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved); +__attribute__ ((visibility ("default"))) void JNI_OnUnload(JavaVM* vm, void* reserved); + +// --------------------------------------------------------------------------- +// Manifest constants +// --------------------------------------------------------------------------- + +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#define JNI_OK (0) +#define JNI_ERR (-1) +#define JNI_EDETACHED (-2) +#define JNI_EVERSION (-3) +#define JNI_ENOMEM (-4) +#define JNI_EEXIST (-5) +#define JNI_EINVAL (-6) -#include +#define JNI_COMMIT 1 +#define JNI_ABORT 2 -#endif /* CSwiftJavaJNI_h */ +#endif diff --git a/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift b/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift index c5b72e0..567a6c5 100644 --- a/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift +++ b/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift @@ -354,34 +354,12 @@ extension UInt64: JavaValue { public func getJNIValue(in environment: JNIEnvironment) -> JNIType { // `jlong` is always 64-bit, no matter the system pointer size. - // Due to differences in JNI headers between Android, JDK and how Swift sees these type imports - // we have to handle this a bit differently. - // - // On 32-bit, the standard JDK jlong is defined in C as `long long`, which yields Swift `Int64` - // On 64-bit, the standard JDK jlong is defined in C as `long`, which yields Swift `Int` - // On Android it's correctly marked as int64_t, always yielding `Int64` in Swift. - #if os(Android) || _pointerBitWidth(_32) return Int64(bitPattern: self) - #else - return Int(bitPattern: UInt(self)) - #endif } public init(fromJNI value: JNIType, in environment: JNIEnvironment) { // `jlong` is always 64-bit, no matter the system pointer size. - // Due to differences in JNI headers between Android, JDK and how Swift sees these type imports - // we have to handle this a bit differently. - // - // On 32-bit, the standard JDK jlong is defined in C as `long long`, which yields Swift `Int64` - // On 64-bit, the standard JDK jlong is defined in C as `long`, which yields Swift `Int` - // On Android it's correctly marked as int64_t, always yielding `Int64` in Swift. - #if os(Android) || _pointerBitWidth(_32) - // In this case `jlong` is seen in Swift as `Int64`. self = UInt64(bitPattern: value) - #else - // In this case `jlong` is since as just `Int`, which is always Int64 - self = UInt64(bitPattern: Int64(value)) - #endif } public static var javaType: JavaType { .long } diff --git a/Sources/SwiftJavaJNICore/JavaEnvironment.swift b/Sources/SwiftJavaJNICore/JavaEnvironment.swift index 1612799..84998bc 100644 --- a/Sources/SwiftJavaJNICore/JavaEnvironment.swift +++ b/Sources/SwiftJavaJNICore/JavaEnvironment.swift @@ -12,11 +12,6 @@ // //===----------------------------------------------------------------------===// - -#if canImport(Android) -public typealias JNINativeInterface_ = JNINativeInterface -#endif - extension UnsafeMutablePointer { - public var interface: JNINativeInterface_ { self.pointee!.pointee } + public var interface: JNINativeInterface { self.pointee!.pointee } } diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 96eba79..1ba7067 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -18,19 +18,33 @@ import FoundationEssentials import Foundation #endif -public typealias JavaVMPointer = UnsafeMutablePointer #if canImport(Android) -typealias JNIEnvPointer = UnsafeMutablePointer -#else -typealias JNIEnvPointer = UnsafeMutableRawPointer +import Android +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(Darwin) +import Darwin #endif +public typealias JavaVMPointer = UnsafeMutablePointer +typealias JNIEnvPointer = UnsafeMutablePointer + extension FileManager { #if os(Windows) static let pathSeparator = ";" #else static let pathSeparator = ":" #endif + + #if os(Windows) + static let libraryExtension = "dll" + #elseif canImport(Darwin) + static let libraryExtension = "dylib" + #else + static let libraryExtension = "so" + #endif } public final class JavaVirtualMachine: @unchecked Sendable { @@ -118,8 +132,13 @@ public final class JavaVirtualMachine: @unchecked Sendable { vmArgs.options = optionsBuffer.baseAddress vmArgs.nOptions = jint(optionsBuffer.count) + typealias CreateJavaVM = @convention(c) (_ pvm: UnsafeMutablePointer?, _ penv: UnsafeMutablePointer?, _ args: UnsafeMutableRawPointer) -> jint + guard let createJavaVM = dlsym(try Self.loadLibJava(), "JNI_CreateJavaVM").map({ unsafeBitCast($0, to: (CreateJavaVM).self) }) else { + throw VMError.cannotLoadCreateJavaVM + } + // Create the JVM instance. - if let createError = VMError(fromJNIError: JNI_CreateJavaVM(&jvm, &environment, &vmArgs)) { + if let createError = VMError(fromJNIError: createJavaVM(&jvm, &environment, &vmArgs)) { throw createError } @@ -176,11 +195,7 @@ 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 @@ -198,11 +213,7 @@ extension JavaVirtualMachine { JavaVirtualMachine.destroyTLS.set(jniEnv!) - #if canImport(Android) return jniEnv! - #else - return jniEnv!.assumingMemoryBound(to: JNIEnv?.self) - #endif } /// Detach the current thread from the Java Virtual Machine. All Java @@ -273,14 +284,19 @@ extension JavaVirtualMachine { } } + typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint + guard let getCreatedJavaVMs = dlsym(try loadLibJava(), "JNI_GetCreatedJavaVMs").map({ unsafeBitCast($0, to: (GetCreatedJavaVMs).self) }) else { + throw VMError.cannotLoadGetCreatedJavaVMs + } + while true { var wasExistingVM: Bool = false while true { // Query the JVM itself to determine whether there is a JVM // instance that we don't yet know about. - var jvm: UnsafeMutablePointer? = nil + var jvm: JavaVMPointer? = nil var numJVMs: jsize = 0 - if JNI_GetCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { + if getCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { // Adopt this JVM into a new instance of the JavaVirtualMachine // wrapper. let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) @@ -316,6 +332,53 @@ extension JavaVirtualMachine { } } + /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table + private static func loadLibJava() throws -> UnsafeMutableRawPointer { + #if os(Android) + for libname in ["libart.so", "libdvm.so", "libnativehelper.so"] { + if let lib = dlopen(libname, RTLD_NOW) { + return lib + } + } + #endif + + guard let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] ?? { + // if JAVA_HOME is unset, look in some standard locations + [ + "/opt/homebrew/opt/java", // macOS Homebrew + "/usr/local/opt/java", + "/usr/lib/jvm/default-java", // Ubuntu/Debian + "/usr/lib/jvm/default", // Arch + ].first(where: { + FileManager.default.fileExists(atPath: $0) + }) + }() else { + throw VMError.javaHomeNotFound + } + + let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true) + + let ext = FileManager.libraryExtension + let libjvmPaths = [ + URL(fileURLWithPath: "jre/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "lib/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "libexec/openjdk.jdk/Contents/Home/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + ] + + guard let libjvmPath = libjvmPaths.first(where: { + FileManager.default.isReadableFile(atPath: $0.path) + }) else { + throw VMError.libjvmNotFound + } + + guard let dylib = dlopen(libjvmPath.path, RTLD_NOW) else { + throw VMError.libjvmNotLoaded + } + + return dylib + } + /// "Forget" the shared JavaVirtualMachine instance. /// /// This will allow the shared JavaVirtualMachine instance to be deallocated. @@ -352,6 +415,21 @@ extension JavaVirtualMachine { /// Invalid arguments. case invalidArguments + /// Cannot locate a `JAVA_HOME` + case javaHomeNotFound + + /// Cannot find `libjvm` + case libjvmNotFound + + /// Cannot `dlopen` `libjvm` + case libjvmNotLoaded + + /// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm` + case cannotLoadGetCreatedJavaVMs + + /// Cannot load `JNI_CreateJavaVM` from `libjvm` + case cannotLoadCreateJavaVM + /// Unknown JNI error. case unknown(jint, file: String, line: UInt) From 69fe1671edad95613e044bd9672d4024c3ca2faf Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 01:09:21 -0400 Subject: [PATCH 2/8] Update formatting --- Package.swift | 2 +- Sources/CSwiftJavaJNI/AndroidSupport.cpp | 1 - .../BridgedValues/JavaValue+Integers.swift | 2 +- .../VirtualMachine/JavaVirtualMachine.swift | 28 +++++++++++-------- 4 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 Sources/CSwiftJavaJNI/AndroidSupport.cpp diff --git a/Package.swift b/Package.swift index f0f747e..fad8874 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], targets: [ .target( - name: "CSwiftJavaJNI", + name: "CSwiftJavaJNI" ), .target( diff --git a/Sources/CSwiftJavaJNI/AndroidSupport.cpp b/Sources/CSwiftJavaJNI/AndroidSupport.cpp deleted file mode 100644 index 8b13789..0000000 --- a/Sources/CSwiftJavaJNI/AndroidSupport.cpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift b/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift index 567a6c5..8e86536 100644 --- a/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift +++ b/Sources/SwiftJavaJNICore/BridgedValues/JavaValue+Integers.swift @@ -354,7 +354,7 @@ extension UInt64: JavaValue { public func getJNIValue(in environment: JNIEnvironment) -> JNIType { // `jlong` is always 64-bit, no matter the system pointer size. - return Int64(bitPattern: self) + Int64(bitPattern: self) } public init(fromJNI value: JNIType, in environment: JNIEnvironment) { diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 1ba7067..085baa1 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -342,17 +342,20 @@ extension JavaVirtualMachine { } #endif - guard let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] ?? { - // if JAVA_HOME is unset, look in some standard locations - [ - "/opt/homebrew/opt/java", // macOS Homebrew - "/usr/local/opt/java", - "/usr/lib/jvm/default-java", // Ubuntu/Debian - "/usr/lib/jvm/default", // Arch - ].first(where: { - FileManager.default.fileExists(atPath: $0) - }) - }() else { + guard + let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] + ?? { + // if JAVA_HOME is unset, look in some standard locations + [ + "/opt/homebrew/opt/java", // macOS Homebrew + "/usr/local/opt/java", + "/usr/lib/jvm/default-java", // Ubuntu/Debian + "/usr/lib/jvm/default", // Arch + ].first(where: { + FileManager.default.fileExists(atPath: $0) + }) + }() + else { throw VMError.javaHomeNotFound } @@ -366,7 +369,8 @@ extension JavaVirtualMachine { URL(fileURLWithPath: "libexec/openjdk.jdk/Contents/Home/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), ] - guard let libjvmPath = libjvmPaths.first(where: { + guard + let libjvmPath = libjvmPaths.first(where: { FileManager.default.isReadableFile(atPath: $0.path) }) else { throw VMError.libjvmNotFound From e988ee5f14a848b0ffe942d850e90390b20c3f96 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 01:12:42 -0400 Subject: [PATCH 3/8] Add amazonlinux2 back to CI and formatting fix --- .github/workflows/pull_request.yml | 2 +- .../SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7a7d10e..e64e792 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -18,7 +18,7 @@ jobs: enable_macos_checks: false enable_ios_checks: false enable_linux_checks: true - linux_os_versions: '["bookworm", "jammy", "noble", "rhel-ubi9"]' + linux_os_versions: '["amazonlinux2", "bookworm", "jammy", "noble", "jammy", "rhel-ubi9"]' linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" linux_pre_build_command: | if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 085baa1..e3dcc94 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -371,8 +371,9 @@ extension JavaVirtualMachine { guard let libjvmPath = libjvmPaths.first(where: { - FileManager.default.isReadableFile(atPath: $0.path) - }) else { + FileManager.default.isReadableFile(atPath: $0.path) + }) + else { throw VMError.libjvmNotFound } From 2d2907898cab94665acf80ce89867acc969f53f8 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 01:20:56 -0400 Subject: [PATCH 4/8] Update CI to include macOS and Windows and not pre-install Java on Linux --- .github/workflows/pull_request.yml | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e64e792..1e7034d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,29 +12,12 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - # TODO: need to pre-install Java - enable_windows_checks: false - # TODO: need to pre-install Java - enable_macos_checks: false + enable_windows_checks: true + enable_macos_checks: true enable_ios_checks: false enable_linux_checks: true - linux_os_versions: '["amazonlinux2", "bookworm", "jammy", "noble", "jammy", "rhel-ubi9"]' + linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" - linux_pre_build_command: | - if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy - if command -v sudo &> /dev/null && [ "$EUID" -ne 0 ]; then - sudo apt-get update -y - sudo apt-get install -y default-jdk - else - apt-get update -y - apt-get install -y default-jdk - fi - elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2, rhel-ubi9 - # TODO: amazonlinux2 needs more help finding JAVA_HOME - yum update -y - yum install -y java-devel - fi - enable_android_sdk_build: true enable_android_sdk_checks: true soundness: From 8c5cd7160fbbdd93486a5d50c7e22941ee59f89f Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 01:35:53 -0400 Subject: [PATCH 5/8] Update dynamic library loading to support Windows --- .github/workflows/pull_request.yml | 3 +- .../VirtualMachine/JavaVirtualMachine.swift | 134 +++++++++++------- 2 files changed, 82 insertions(+), 55 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1e7034d..571a447 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,8 +13,9 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: enable_windows_checks: true + windows_swift_versions: '["nightly-main"]' enable_macos_checks: true - enable_ios_checks: false + enable_ios_checks: true enable_linux_checks: true linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index e3dcc94..915ba83 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -133,7 +133,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { vmArgs.nOptions = jint(optionsBuffer.count) typealias CreateJavaVM = @convention(c) (_ pvm: UnsafeMutablePointer?, _ penv: UnsafeMutablePointer?, _ args: UnsafeMutableRawPointer) -> jint - guard let createJavaVM = dlsym(try Self.loadLibJava(), "JNI_CreateJavaVM").map({ unsafeBitCast($0, to: (CreateJavaVM).self) }) else { + guard let createJavaVM: CreateJavaVM = symbol(try loadLibJava(), "JNI_CreateJavaVM") else { throw VMError.cannotLoadCreateJavaVM } @@ -285,7 +285,7 @@ extension JavaVirtualMachine { } typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint - guard let getCreatedJavaVMs = dlsym(try loadLibJava(), "JNI_GetCreatedJavaVMs").map({ unsafeBitCast($0, to: (GetCreatedJavaVMs).self) }) else { + guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { throw VMError.cannotLoadGetCreatedJavaVMs } @@ -332,58 +332,6 @@ extension JavaVirtualMachine { } } - /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table - private static func loadLibJava() throws -> UnsafeMutableRawPointer { - #if os(Android) - for libname in ["libart.so", "libdvm.so", "libnativehelper.so"] { - if let lib = dlopen(libname, RTLD_NOW) { - return lib - } - } - #endif - - guard - let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] - ?? { - // if JAVA_HOME is unset, look in some standard locations - [ - "/opt/homebrew/opt/java", // macOS Homebrew - "/usr/local/opt/java", - "/usr/lib/jvm/default-java", // Ubuntu/Debian - "/usr/lib/jvm/default", // Arch - ].first(where: { - FileManager.default.fileExists(atPath: $0) - }) - }() - else { - throw VMError.javaHomeNotFound - } - - let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true) - - let ext = FileManager.libraryExtension - let libjvmPaths = [ - URL(fileURLWithPath: "jre/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), - URL(fileURLWithPath: "lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), - URL(fileURLWithPath: "lib/libjvm.\(ext)", relativeTo: javaHomeURL), - URL(fileURLWithPath: "libexec/openjdk.jdk/Contents/Home/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), - ] - - guard - let libjvmPath = libjvmPaths.first(where: { - FileManager.default.isReadableFile(atPath: $0.path) - }) - else { - throw VMError.libjvmNotFound - } - - guard let dylib = dlopen(libjvmPath.path, RTLD_NOW) else { - throw VMError.libjvmNotLoaded - } - - return dylib - } - /// "Forget" the shared JavaVirtualMachine instance. /// /// This will allow the shared JavaVirtualMachine instance to be deallocated. @@ -455,3 +403,81 @@ extension JavaVirtualMachine { case classpathEntryNotFound(entry: String, classpath: [String]) } } + +#if os(Windows) +private typealias DylibType = HMODULE + +private func symbol(_ handle: DylibType?, _ name: String) -> T? { + guard let handle = handle, let result = GetProcAddress(handle, name) else { + return nil + } + return unsafeBitCast(result, to: T.self) +} +#else +private typealias DylibType = UnsafeMutableRawPointer + +private func symbol(_ handle: DylibType?, _ name: String) -> T? { + guard let handle = handle, let result = dlsym(handle, name) else { + return nil + } + return unsafeBitCast(result, to: T.self) +} +#endif + +/// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table +private func loadLibJava() throws -> DylibType { + #if os(Android) + for libname in ["libart.so", "libdvm.so", "libnativehelper.so"] { + if let lib = dlopen(libname, RTLD_NOW) { + return lib + } + } + #endif + + guard + let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] + ?? { + // if JAVA_HOME is unset, look in some standard locations + [ + "/opt/homebrew/opt/java", // macOS Homebrew + "/usr/local/opt/java", + "/usr/lib/jvm/default-java", // Ubuntu/Debian + "/usr/lib/jvm/default", // Arch + ].first(where: { + FileManager.default.fileExists(atPath: $0) + }) + }() + else { + throw JavaVirtualMachine.VMError.javaHomeNotFound + } + + let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true) + + let ext = FileManager.libraryExtension + let libjvmPaths = [ + URL(fileURLWithPath: "jre/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "lib/libjvm.\(ext)", relativeTo: javaHomeURL), + URL(fileURLWithPath: "libexec/openjdk.jdk/Contents/Home/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), + ] + + guard + let libjvmPath = libjvmPaths.first(where: { + FileManager.default.isReadableFile(atPath: $0.path) + }) + else { + throw JavaVirtualMachine.VMError.libjvmNotFound + } + + #if os(Windows) + guard let dylib = LoadLibraryA(libjvmPath.path) else { + throw JavaVirtualMachine.VMError.libjvmNotLoaded + } + #else + guard let dylib = dlopen(libjvmPath.path, RTLD_NOW) else { + throw JavaVirtualMachine.VMError.libjvmNotLoaded + } + #endif + + return dylib +} From f6ccc879bf9009518439f65921be89be944e7023 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 01:43:54 -0400 Subject: [PATCH 6/8] Add imports for Windows --- .github/workflows/pull_request.yml | 2 +- .../SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 571a447..282d5ea 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -15,7 +15,7 @@ jobs: enable_windows_checks: true windows_swift_versions: '["nightly-main"]' enable_macos_checks: true - enable_ios_checks: true + enable_ios_checks: false enable_linux_checks: true linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 915ba83..50c6603 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -20,6 +20,9 @@ import Foundation #if canImport(Android) import Android +#elseif os(Windows) +import ucrt +import WinSDK #elseif canImport(Glibc) import Glibc #elseif canImport(Musl) From 30933ffebc65d0700bb0e5170caabec6c84c97b6 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 11:23:16 -0400 Subject: [PATCH 7/8] Cleanup and compact --- .../VirtualMachine/JavaVirtualMachine.swift | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 50c6603..1fd0629 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -37,15 +37,12 @@ typealias JNIEnvPointer = UnsafeMutablePointer extension FileManager { #if os(Windows) static let pathSeparator = ";" - #else - static let pathSeparator = ":" - #endif - - #if os(Windows) static let libraryExtension = "dll" #elseif canImport(Darwin) + static let pathSeparator = ":" static let libraryExtension = "dylib" #else + static let pathSeparator = ":" static let libraryExtension = "so" #endif } @@ -407,17 +404,19 @@ extension JavaVirtualMachine { } } +// ==== ------------------------------------------------------------------------ +// MARK: Utilities for loading libjvm and JNI entry point symbols. + #if os(Windows) private typealias DylibType = HMODULE -private func symbol(_ handle: DylibType?, _ name: String) -> T? { - guard let handle = handle, let result = GetProcAddress(handle, name) else { - return nil - } - return unsafeBitCast(result, to: T.self) +/// Forward `dlsym` to Windows' `GetProcAddress` +private func dlsym(_ handle: DylibType?, _ name: String) -> DylibType? { + GetProcAddress(handle, name) } #else private typealias DylibType = UnsafeMutableRawPointer +#endif private func symbol(_ handle: DylibType?, _ name: String) -> T? { guard let handle = handle, let result = dlsym(handle, name) else { @@ -425,7 +424,6 @@ private func symbol(_ handle: DylibType?, _ name: String) -> T? { } return unsafeBitCast(result, to: T.self) } -#endif /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table private func loadLibJava() throws -> DylibType { @@ -458,6 +456,7 @@ private func loadLibJava() throws -> DylibType { let ext = FileManager.libraryExtension let libjvmPaths = [ + // look through some standard locations relative to JAVA_HOME URL(fileURLWithPath: "jre/lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), URL(fileURLWithPath: "lib/server/libjvm.\(ext)", relativeTo: javaHomeURL), URL(fileURLWithPath: "lib/libjvm.\(ext)", relativeTo: javaHomeURL), @@ -473,14 +472,14 @@ private func loadLibJava() throws -> DylibType { } #if os(Windows) - guard let dylib = LoadLibraryA(libjvmPath.path) else { - throw JavaVirtualMachine.VMError.libjvmNotLoaded - } + let dylib = LoadLibraryA(libjvmPath.path) #else - guard let dylib = dlopen(libjvmPath.path, RTLD_NOW) else { - throw JavaVirtualMachine.VMError.libjvmNotLoaded - } + let dylib = dlopen(libjvmPath.path, RTLD_NOW) #endif + guard let dylib else { + throw JavaVirtualMachine.VMError.libjvmNotLoaded + } + return dylib } From 23c7774f5787978855257cb3ddcf5220e6f47251 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 11:39:55 -0400 Subject: [PATCH 8/8] Fix fomatting and cleanup symbol loading --- .../VirtualMachine/JavaVirtualMachine.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 1fd0629..04c2367 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -410,20 +410,22 @@ extension JavaVirtualMachine { #if os(Windows) private typealias DylibType = HMODULE -/// Forward `dlsym` to Windows' `GetProcAddress` -private func dlsym(_ handle: DylibType?, _ name: String) -> DylibType? { - GetProcAddress(handle, name) +private func symbol(_ handle: DylibType, _ name: String) -> T? { + guard let result = GetProcAddress(handle, name) else { + return nil + } + return unsafeBitCast(result, to: T.self) } #else private typealias DylibType = UnsafeMutableRawPointer -#endif -private func symbol(_ handle: DylibType?, _ name: String) -> T? { - guard let handle = handle, let result = dlsym(handle, name) else { +private func symbol(_ handle: DylibType, _ name: String) -> T? { + guard let result = dlsym(handle, name) else { return nil } return unsafeBitCast(result, to: T.self) } +#endif /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table private func loadLibJava() throws -> DylibType { @@ -478,7 +480,7 @@ private func loadLibJava() throws -> DylibType { #endif guard let dylib else { - throw JavaVirtualMachine.VMError.libjvmNotLoaded + throw JavaVirtualMachine.VMError.libjvmNotLoaded } return dylib