-
Notifications
You must be signed in to change notification settings - Fork 6
/
SwiftJNI.swift
216 lines (175 loc) · 9.03 KB
/
SwiftJNI.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import CJNI
public var jni: JNI! // this gets set "OnLoad" so should always exist
@_silgen_name("JNI_OnLoad")
public func JNI_OnLoad(jvm: UnsafeMutablePointer<JavaVM>, reserved: UnsafeMutablePointer<Void>) -> jint {
guard let localJNI = JNI(jvm: jvm) else {
fatalError("Couldn't initialise JNI")
}
jni = localJNI // set the global for use elsewhere
return JNI_VERSION_1_6
}
extension jboolean : BooleanLiteralConvertible {
public init(booleanLiteral value: Bool) {
self = value ? jboolean(JNI_TRUE) : jboolean(JNI_FALSE)
}
}
// SwiftJNI Public API
public class SwiftJNI : JNI {
public func RegisterNatives(jClass: jclass, methods: [JNINativeMethod]) -> Bool {
let _env = self._env
let env = _env.memory.memory
let result = env.RegisterNatives(_env, jClass, methods, jint(methods.count))
return (result == 0)
}
public func ThrowNew(message: String) {
let _env = self._env
let env = _env.memory.memory
env.ThrowNew(_env, env.FindClass(_env, "java/lang/Exception"), message)
}
/// - Note: This shouldn't need to be cleaned up because we're not taking ownership of the reference
public func GetStringUTFChars(string: jstring) -> UnsafePointer<CChar> {
let _env = self._env
var didCopyStringChars = jboolean() // XXX: this gets set below, check it!
return _env.memory.memory.GetStringUTFChars(_env, string, &didCopyStringChars)
}
// MARK: References
public func NewGlobalRef(object: jobject) -> jobject? {
let _env = self._env
let result = _env.memory.memory.NewGlobalRef(_env, object)
return (result != nil) ? result : .None
}
// MARK: Classes and Methods
public func FindClass(className: String) -> jclass? {
let _env = self._env
let result = _env.memory.memory.FindClass(_env, className)
return (result != nil) ? result : .None
}
public func GetMethodID(javaClass: jclass, methodName: UnsafePointer<CChar>, methodSignature: UnsafePointer<CChar>) -> jmethodID? {
let _env = self._env
let result = _env.memory.memory.GetMethodID(_env, javaClass, methodName, methodSignature)
return (result != nil) ? result : .None
}
// TODO: make parameters take [JValue], being a swifty version of [jvalue] with reference counting etc.
public func CallVoidMethodA(object: jobject, methodID method: jmethodID, parameters: [jvalue]) {
let _env = self._env
var methodArgs = parameters
_env.memory.memory.CallVoidMethodA(_env, object, method, &methodArgs)
}
// MARK: Arrays
public func GetArrayLength(array: jarray) -> Int {
let _env = self._env
let result = _env.memory.memory.GetArrayLength(_env, array)
return Int(result)
}
public func NewIntArray(count: Int) -> jarray? {
let _env = self._env
let result = _env.memory.memory.NewIntArray(_env, jsize(count))
return (result != nil) ? result : .None
}
public func GetIntArrayRegion(array: jintArray, startIndex: Int = 0, numElements: Int = -1) -> [Int] {
let _env = self._env
var count = numElements
if numElements < 0 {
count = GetArrayLength(array)
}
var result = [jint](count: count, repeatedValue: 0)
_env.memory.memory.GetIntArrayRegion(_env, array, jsize(startIndex), jsize(count), &result)
return result.map { Int($0) }
}
public func SetIntArrayRegion(array: jintArray, startIndex: Int = 0, from sourceElements: [Int]) {
let _env = self._env
var newElements = sourceElements.map { jint($0) } // make mutable copy
_env.memory.memory.SetIntArrayRegion(_env, array, jsize(startIndex), jsize(newElements.count), &newElements)
}
public func NewFloatArray(count: Int) -> jarray? {
let _env = self._env
let result = _env.memory.memory.NewFloatArray(_env, jsize(count))
return (result != nil) ? result : .None
}
public func GetFloatArrayRegion(array: jfloatArray, startIndex: Int = 0, numElements: Int = -1) -> [Float] {
let _env = self._env
var count = numElements
if numElements < 0 {
count = GetArrayLength(array)
}
var result = [jfloat](count: count, repeatedValue: 0)
_env.memory.memory.GetFloatArrayRegion(_env, array, jsize(startIndex), jsize(count), &result)
return result.map { Float($0) }
}
public func SetFloatArrayRegion(array: jfloatArray, startIndex: Int = 0, from sourceElements: [Float]) {
let _env = self._env
var newElements = sourceElements.map { jfloat($0) } // make mutable copy
_env.memory.memory.SetFloatArrayRegion(_env, array, jsize(startIndex), jsize(newElements.count), &newElements)
}
}
/**
Allows a (Void) Java method to be called from Swift. Takes a global jobj (a class instance), a method name and its signature. The resulting callback can be called via javaCallback.call(param1, param2...), or javaCallback.apply([params]). Each param must be a jvalue.
Needs more error checking and handling. The basis is there, but from memory I had issues with either the optional or the throwing on Android.
*/
public struct JavaCallback {
private let jobj: jobject // must be a JNI Global Reference
private let methodID: jmethodID
// Eventually we should check how many parameters are required by the method signature
// And also which return type is expected (to allow calling non-Void methods)
// For now this implementation remains unsafe
//let expectedParameterCount: Int
/// Errors describing the various things that can go wrong when calling a Java method via JNI.
/// - __InvalidParameters__: One character per method parameter is required. For example, with a methodSignature of "(FF)V", you need to pass two floats as parameters.
/// - __InvalidMethod__: Couldn't get the requested method from the jobject provided (are you calling with the right jobject instance / calling on the correct class?)
/// - __IncorrectMethodSignature__: The JNI is separated into Java method calls to functions with various return types. So if you perform `callJavaMethod`, you need to accept the return value with the corresponding type. *XXX: currently only Void methods are implemented*.
enum Error: ErrorType {
case JNINotReady
case InvalidParameters
case IncorrectMethodSignature
case InvalidClass
case InvalidMethod
}
/**
- Parameters:
- globalJobj: The class instance you want to perform the method on. Note this must be a jni GlobalRef, otherwise your callback will either crash or just silently not work.
- methodName: A `String` with the name of the Java method to call
- methodSignature: A `String` containing the method's exact signature. Although it is possible to call non-Void Java methods via the JNI, that is not yet implemented in the the current Swift binding. This means that, for now, `methodSignature` must end with `"V"` i.e., return `Void`
- **`"(F)V"`** would reference a method that accepts one Float and returns Void.
- **Z** boolean
- **B** byte
- **C** char
- **S** short
- **I** int
- **J** long
- **F** float
- **D** double
- **Lfully-qualified-class;** fully-qualified-class
- **[type** type[]
- **(arg-types)ret-type** method type
- e.g. **`"([I)V"`** would accept one array of Ints and return Void.
- parameters: Any number of jvalues (*must have the same number and type as the* `methodSignature` *you're trying to call*)
- Throws: `JavaCallback.Error`
*/
public init (_ globalJobj: jobject, methodName: String, methodSignature: String) {
// At the moment we can only call Void methods, fail if user tries to return something else
guard let returnType = methodSignature.characters.last where returnType == "V"/*oid*/ else {
// LOG JavaMethodCallError.IncorrectMethodSignature
fatalError("JavaMethodCallError.IncorrectMethodSignature")
}
// With signature "(FF)V", parameters count should be 2, ignoring the two brackets and the V
// XXX: This test isn't robust, but it will prevent simple user errors
// Doesn't work with more complex object types, arrays etc. we should determine the signature based on parameters.
// TODO: Check methodSignature here and determine expectedParameterCount
guard
let javaClass = jni.GetObjectClass(globalJobj),
let methodID = jni.GetMethodID(javaClass, methodName: methodName, methodSignature: methodSignature)
else {
// XXX: We should throw here and keep throwing til it gets back to Java
fatalError("Failed to make JavaCallback")
}
self.jobj = globalJobj
self.methodID = methodID
}
public func apply(args: [jvalue]) {
jni.CallVoidMethodA(jobj, methodID: methodID, args: args)
}
/// Send variadic parameters to the func that takes an array
public func call(args: jvalue...) {
self.apply(args)
}
}