diff --git a/Sources/SwiftJNI/SwiftJNI.swift b/Sources/SwiftJNI/SwiftJNI.swift index 0baff4c..06c1394 100644 --- a/Sources/SwiftJNI/SwiftJNI.swift +++ b/Sources/SwiftJNI/SwiftJNI.swift @@ -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(_ 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 @@ -124,19 +86,26 @@ extension JNI { public convenience init(jvm: UnsafeMutablePointer) { self.init(adoptingJVM: jvm) } +} - /// Our reference to the Java Virtual Machine, to be set on init - var _jvm: UnsafeMutablePointer { - get { - var jvm: UnsafeMutablePointer? = 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(_ 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(_ 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() + return try block() +} + #else /// Gateway to JVM and JNI functionality. public class JNI { @@ -157,7 +126,6 @@ public class JNI { self._jvm = jvm } } -#endif extension JNI { /// Perform an operation with the current thread's `JNIEnviPointer`. @@ -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(_ 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(options: JConvertibleOptions, _ block: (JNINativeInterface, JNIEnvPointer) throws -> T) throws -> T { @@ -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 @@ -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)