Skip to content

Commit e0792c6

Browse files
NakaokaReiclaude
andcommitted
fix: add C-compatible JNI type aliases for C++ interoperability
This commit introduces C-compatible type aliases (CJNIEnv, Cjobject, Cjstring, etc.) to resolve type mismatches when Swift modules with C++ interoperability enabled import SwiftJava. The root cause is that jni.h defines JNI types differently in C vs C++: - C mode: JNIEnv = const struct JNINativeInterface_ * (pointer) - C++ mode: JNIEnv = JNIEnv_ (struct) When a module like HelloWorld has C++ interop enabled but imports SwiftJava (which is compiled without C++ interop), the JNI type definitions conflict, causing compilation errors. Changes: - CSwiftJavaJNI.h: Add C-compatible type aliases that resolve to the same underlying type in both C and C++ modes - JavaType+JNI.swift: Use C-compatible types (Cjstring, Cjobject, etc.) in generated JNI method signatures - JNISwift2JavaGenerator: Add unsafeBitCast for return values to convert between SwiftJava's internal JNI types and C-compatible types - JavaValue.swift, JavaEnvironment.swift: Use CJNIEnv in JNIEnvironment typealias for consistent type definitions Fixes: #391 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 12d3595 commit e0792c6

File tree

8 files changed

+84
-23
lines changed

8 files changed

+84
-23
lines changed

Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,52 @@
1717

1818
#include <jni.h>
1919

20+
// Provide C-compatible type aliases for JNI types.
21+
// When Swift modules have C++ interoperability enabled, jni.h is parsed
22+
// in C++ mode where JNIEnv is defined as a struct (JNIEnv_). However,
23+
// SwiftJava modules are compiled without C++ interop, causing JNIEnv
24+
// to be a pointer type. This mismatch causes type errors.
25+
//
26+
// These typedefs provide consistent C-style pointer types that work
27+
// regardless of the C++ interoperability mode.
28+
// See: https://github.com/swiftlang/swift-java/issues/391
29+
#ifdef __cplusplus
30+
// Android NDK uses JNINativeInterface instead of JNINativeInterface_
31+
#ifdef __ANDROID__
32+
typedef const JNINativeInterface *CJNIEnv;
33+
#else
34+
typedef const JNINativeInterface_ *CJNIEnv;
35+
#endif
36+
typedef _jobject *Cjobject;
37+
typedef _jclass *Cjclass;
38+
typedef _jstring *Cjstring;
39+
typedef _jarray *Cjarray;
40+
typedef _jobjectArray *CjobjectArray;
41+
typedef _jbooleanArray *CjbooleanArray;
42+
typedef _jbyteArray *CjbyteArray;
43+
typedef _jcharArray *CjcharArray;
44+
typedef _jshortArray *CjshortArray;
45+
typedef _jintArray *CjintArray;
46+
typedef _jlongArray *CjlongArray;
47+
typedef _jfloatArray *CjfloatArray;
48+
typedef _jdoubleArray *CjdoubleArray;
49+
typedef _jthrowable *Cjthrowable;
50+
#else
51+
typedef JNIEnv CJNIEnv;
52+
typedef jobject Cjobject;
53+
typedef jclass Cjclass;
54+
typedef jstring Cjstring;
55+
typedef jarray Cjarray;
56+
typedef jobjectArray CjobjectArray;
57+
typedef jbooleanArray CjbooleanArray;
58+
typedef jbyteArray CjbyteArray;
59+
typedef jcharArray CjcharArray;
60+
typedef jshortArray CjshortArray;
61+
typedef jintArray CjintArray;
62+
typedef jlongArray CjlongArray;
63+
typedef jfloatArray CjfloatArray;
64+
typedef jdoubleArray CjdoubleArray;
65+
typedef jthrowable Cjthrowable;
66+
#endif
67+
2068
#endif /* CSwiftJavaJNI_h */
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
module CSwiftJavaJNI {
22
umbrella header "CSwiftJavaJNI.h"
3-
// Force C-mode JNI type definitions to ensure compatibility with
4-
// Swift modules that have C++ interoperability enabled.
5-
// See: https://github.com/swiftlang/swift-java/issues/391
6-
requires !cplusplus
73
export *
84
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,13 @@ extension JNISwift2JavaGenerator {
355355
let loweredResult = nativeSignature.result.conversion.render(&printer, result)
356356

357357
if !decl.functionSignature.result.type.isVoid {
358-
return "return \(loweredResult)"
358+
let resultType = nativeSignature.result.javaType.jniTypeName
359+
// Use unsafeBitCast for C++ interoperability compatibility.
360+
// SwiftJava uses JNI types (jstring, jobject, etc.) internally,
361+
// but generated thunks use C-compatible types (Cjstring, Cjobject, etc.)
362+
// to ensure compatibility with modules that have C++ interop enabled.
363+
// See: https://github.com/swiftlang/swift-java/issues/391
364+
return "return unsafeBitCast(\(loweredResult), to: \(resultType).self)"
359365
} else {
360366
return loweredResult
361367
}
@@ -441,7 +447,9 @@ extension JNISwift2JavaGenerator {
441447

442448
let thunkParameters =
443449
[
444-
"environment: UnsafeMutablePointer<JNIEnv?>!",
450+
// Use CJNIEnv for C++ interoperability compatibility.
451+
// See: https://github.com/swiftlang/swift-java/issues/391
452+
"environment: UnsafeMutablePointer<CJNIEnv?>!",
445453
"thisClass: jclass"
446454
] + translatedParameters
447455
let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : ""

Sources/JExtractSwiftLib/JavaParameter.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ struct JavaParameter {
4747
var jniTypeName: String {
4848
switch self {
4949
case .concrete(let type): type.jniTypeName
50-
case .generic: "jobject?"
50+
// Use C-compatible type for C++ interoperability
51+
case .generic: "Cjobject?"
5152
}
5253
}
5354

Sources/JavaTypes/JavaType+JNI.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
extension JavaType {
1616
/// Map this Java type to the appropriate JNI type name.
17+
/// Uses C-compatible types (Cjobject, Cjstring, etc.) for C++ interoperability.
18+
/// See: https://github.com/swiftlang/swift-java/issues/391
1719
package var jniTypeName: String {
1820
switch self {
1921
case .boolean: "jboolean"
@@ -25,19 +27,19 @@ extension JavaType {
2527
case .int: "jint"
2628
case .long: "jlong"
2729
case .void: "void"
28-
case .array(.boolean): "jbooleanArray?"
29-
case .array(.byte): "jbyteArray?"
30-
case .array(.char): "jcharArray?"
31-
case .array(.short): "jshortArray?"
32-
case .array(.int): "jintArray?"
33-
case .array(.long): "jlongArray?"
34-
case .array(.float): "jfloatArray?"
35-
case .array(.double): "jdoubleArray?"
36-
case .array: "jobjectArray?"
37-
case .class(package: "java.lang", name: "String", _): "jstring?"
38-
case .class(package: "java.lang", name: "Class", _): "jclass?"
39-
case .class(package: "java.lang", name: "Throwable", _): "jthrowable?"
40-
case .class: "jobject?"
30+
case .array(.boolean): "CjbooleanArray?"
31+
case .array(.byte): "CjbyteArray?"
32+
case .array(.char): "CjcharArray?"
33+
case .array(.short): "CjshortArray?"
34+
case .array(.int): "CjintArray?"
35+
case .array(.long): "CjlongArray?"
36+
case .array(.float): "CjfloatArray?"
37+
case .array(.double): "CjdoubleArray?"
38+
case .array: "CjobjectArray?"
39+
case .class(package: "java.lang", name: "String", _): "Cjstring?"
40+
case .class(package: "java.lang", name: "Class", _): "Cjclass?"
41+
case .class(package: "java.lang", name: "Throwable", _): "Cjthrowable?"
42+
case .class: "Cjobject?"
4143
}
4244
}
4345

Sources/SwiftJava/JVM/JavaVirtualMachine.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import Foundation
1919
#endif
2020

2121
public typealias JavaVMPointer = UnsafeMutablePointer<JavaVM?>
22+
// Use CJNIEnv for C++ interoperability compatibility.
23+
// See: https://github.com/swiftlang/swift-java/issues/391
2224
#if canImport(Android)
23-
typealias JNIEnvPointer = UnsafeMutablePointer<JNIEnv?>
25+
typealias JNIEnvPointer = UnsafeMutablePointer<CJNIEnv?>
2426
#else
2527
typealias JNIEnvPointer = UnsafeMutableRawPointer
2628
#endif

Sources/SwiftJava/JavaEnvironment.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import CSwiftJavaJNI
1818
public typealias JNINativeInterface_ = JNINativeInterface
1919
#endif
2020

21-
extension UnsafeMutablePointer<JNIEnv?> {
21+
// Extension on CJNIEnv for C++ interoperability compatibility.
22+
// See: https://github.com/swiftlang/swift-java/issues/391
23+
extension UnsafeMutablePointer<CJNIEnv?> {
2224
public var interface: JNINativeInterface_ { self.pointee!.pointee }
2325
}

Sources/SwiftJava/JavaValue.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ public protocol JavaValue: ~Copyable {
9393
}
9494

9595
/// The JNI environment.
96-
public typealias JNIEnvironment = UnsafeMutablePointer<JNIEnv?>
96+
/// Uses CJNIEnv for C++ interoperability compatibility.
97+
/// See: https://github.com/swiftlang/swift-java/issues/391
98+
public typealias JNIEnvironment = UnsafeMutablePointer<CJNIEnv?>
9799

98100
/// Type of an operation that performs a JNI method call.
99101
public typealias JNIMethodCall<Result> = (JNIEnvironment, jobject, jmethodID, UnsafePointer<jvalue>?) -> Result

0 commit comments

Comments
 (0)