Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 60 additions & 51 deletions Sources/SwiftJNI/SwiftJNI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,44 +65,6 @@ public var isJNIInitialized: Bool {
JNI.jni != nil
}

/// Establish a context in which to perform JNI operations.
///
/// - Warning: You cannot initiate JNI operations from native code outside of a context.
public func jniContext<T>(_ block: () throws -> T) rethrows -> T {
precondition(JNI.jni != nil, "JNI.jni was unset")
precondition(JNI.jni._jvm.pointee != nil, "JNI.jni._jvm.pointee was nil")

let jvm: JNIInvokeInterface = JNI.jni._jvm.pointee!.pointee
var tenv: UnsafeMutableRawPointer?
let threadStatus = jvm.GetEnv(JNI.jni._jvm, &tenv, JavaInt(JNI_VERSION_1_6))

// Ensure that there is a `JNIEnvPointer` for the current thread
// See: https://developer.android.com/training/articles/perf-jni#threads
switch threadStatus {
case JNI_OK:
return try block()
case JNI_EDETACHED:
// we weren't attached to the Java thread; attach, perform the block, and then detach
var tenv: JNIEnvPointer!
if jvm.AttachCurrentThread(JNI.jni._jvm, &tenv, nil) != JNI_OK {
fatalError("SwiftJNI: unable to attach JNI to current thread")
}
defer {
if jvm.DetachCurrentThread(JNI.jni._jvm) != JNI_OK {
fatalError("SwiftJNI: unable to detach JNI from thread")
}
}

// We set the ClassLoader for the current thread to be the application ClassLoader, otherwise classes defined in the app may not be found when loaded from a natively-created thread when loaded via reflection
JClassLoader.setThreadClassLoader()
return try block()
case JNI_EVERSION:
fatalError("SwiftJNI: unsupported JNI version")
default:
fatalError("SwiftJNI: unexpected JNI thread status: \(threadStatus)")
}
}

#if SWIFT_JAVA_JNI_CORE
public typealias JNI = SwiftJavaJNICore.JavaVirtualMachine

Expand All @@ -124,19 +86,26 @@ extension JNI {
public convenience init(jvm: UnsafeMutablePointer<JavaVM?>) {
self.init(adoptingJVM: jvm)
}
}

/// Our reference to the Java Virtual Machine, to be set on init
var _jvm: UnsafeMutablePointer<JavaVM?> {
get {
var jvm: UnsafeMutablePointer<JavaVM?>? = nil
let env = try! environment()
guard env.pointee!.pointee.GetJavaVM(env, &jvm) == JNI_OK, let jvm else {
fatalError("unable to call getJavaVM")
}
return jvm
}
extension JNI {
/// Perform an operation with the current thread's `JNIEnviPointer`.
public func withEnv<T>(_ block: (JNINativeInterface, JNIEnvPointer) throws -> T) rethrows -> T {
let env = try! environment()
return try block(env.pointee!.pointee, env)
}
}

/// Establish a context in which to perform JNI operations.
///
/// - Warning: You cannot initiate JNI operations from native code outside of a context.
public func jniContext<T>(_ block: () throws -> T) rethrows -> T {
// We set the ClassLoader for the current thread to be the application ClassLoader, otherwise classes defined in the app may not be found when loaded from a natively-created thread when loaded via reflection
// TODO: cache and optimize
JClassLoader.setThreadClassLoader()
try block()
}

#else
/// Gateway to JVM and JNI functionality.
public class JNI {
Expand All @@ -157,7 +126,6 @@ public class JNI {
self._jvm = jvm
}
}
#endif

extension JNI {
/// Perform an operation with the current thread's `JNIEnviPointer`.
Expand All @@ -171,7 +139,48 @@ extension JNI {
let env = tenv!.assumingMemoryBound(to: JNIEnv?.self)
return try block(env.pointee!.pointee, env)
}
}

/// Establish a context in which to perform JNI operations.
///
/// - Warning: You cannot initiate JNI operations from native code outside of a context.
public func jniContext<T>(_ block: () throws -> T) rethrows -> T {
precondition(JNI.jni != nil, "JNI.jni was unset")
precondition(JNI.jni._jvm.pointee != nil, "JNI.jni._jvm.pointee was nil")

let jvm: JNIInvokeInterface = JNI.jni._jvm.pointee!.pointee
var tenv: UnsafeMutableRawPointer?
let threadStatus = jvm.GetEnv(JNI.jni._jvm, &tenv, JavaInt(JNI_VERSION_1_6))

// Ensure that there is a `JNIEnvPointer` for the current thread
// See: https://developer.android.com/training/articles/perf-jni#threads
switch threadStatus {
case JNI_OK:
return try block()
case JNI_EDETACHED:
// we weren't attached to the Java thread; attach, perform the block, and then detach
var tenv: JNIEnvPointer!
if jvm.AttachCurrentThread(JNI.jni._jvm, &tenv, nil) != JNI_OK {
fatalError("SwiftJNI: unable to attach JNI to current thread")
}
defer {
if jvm.DetachCurrentThread(JNI.jni._jvm) != JNI_OK {
fatalError("SwiftJNI: unable to detach JNI from thread")
}
}

// We set the ClassLoader for the current thread to be the application ClassLoader, otherwise classes defined in the app may not be found when loaded from a natively-created thread when loaded via reflection
JClassLoader.setThreadClassLoader()
return try block()
case JNI_EVERSION:
fatalError("SwiftJNI: unsupported JNI version")
default:
fatalError("SwiftJNI: unexpected JNI thread status: \(threadStatus)")
}
}
#endif

extension JNI {
/// Same as `withEnv`, but also checks for any java exceptions. If an exception occurred,
/// it will throw a `JavaException` and clear the JNI exception.
public func withEnvThrowing<T>(options: JConvertibleOptions, _ block: (JNINativeInterface, JNIEnvPointer) throws -> T) throws -> T {
Expand Down Expand Up @@ -1395,7 +1404,7 @@ extension String: JObjectProtocol, JConvertible {
fatalError("Could not get characters from String")
}
defer { jni.ReleaseStringUTFChars(env, obj, chars) }
guard let str = String(validatingUTF8: chars) else {
guard let str = String(validatingCString: chars) else {
fatalError("Could not get valid UTF8 characters from String")
}
return str
Expand Down Expand Up @@ -1578,7 +1587,7 @@ extension JNI {
}
}
let JAVA_HOME = getenv("JAVA_HOME")!
let javaHome = URL(fileURLWithPath: String(validatingUTF8: JAVA_HOME)!)
let javaHome = URL(fileURLWithPath: String(validatingCString: JAVA_HOME)!)

let ext: String
#if os(Windows)
Expand Down
Loading