diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 66366a19..38860c06 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -19,6 +19,7 @@ // Import javakit/swiftkit support libraries import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; public class HelloJava2SwiftJNI { @@ -40,8 +41,10 @@ static void examples() { MySwiftClass.method(); - MySwiftClass myClass = MySwiftClass.init(10, 5); - MySwiftClass myClass2 = MySwiftClass.init(); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass myClass = MySwiftClass.init(10, 5, arena); + MySwiftClass myClass2 = MySwiftClass.init(arena); + } System.out.println("DONE."); } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 994240cb..ec0e31dd 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -14,24 +14,25 @@ package com.example.swift; -import com.example.swift.MySwiftLibrary; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { @Test void init_noParameters() { - MySwiftClass c = MySwiftClass.init(); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(arena); + assertNotNull(c); + } } @Test void init_withParameters() { - MySwiftClass c = MySwiftClass.init(1337, 42); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(1337, 42, arena); + assertNotNull(c); + } } - } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ac775638..6a1c549a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,19 @@ // //===----------------------------------------------------------------------===// + +// MARK: Defaults + +extension JNISwift2JavaGenerator { + /// Default set Java imports for every generated file + static let defaultJavaImports: Array = [ + "org.swift.swiftkit.core.*", + "org.swift.swiftkit.core.util.*", + ] +} + +// MARK: Printing + extension JNISwift2JavaGenerator { func writeExportedJavaSources() throws { var printer = CodePrinter() @@ -72,6 +85,7 @@ extension JNISwift2JavaGenerator { private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printNominal(&printer, decl) { printer in printer.print( @@ -87,14 +101,10 @@ extension JNISwift2JavaGenerator { """ ) - printer.println() - printer.print( """ - private long selfPointer; - - private \(decl.swiftNominal.name)(long selfPointer) { - this.selfPointer = selfPointer; + public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """ ) @@ -108,6 +118,8 @@ extension JNISwift2JavaGenerator { for method in decl.methods { printFunctionBinding(&printer, method) } + + printDestroyFunction(&printer, decl) } } @@ -130,10 +142,17 @@ extension JNISwift2JavaGenerator { ) } + private func printImports(_ printer: inout CodePrinter) { + for i in JNISwift2JavaGenerator.defaultJavaImports { + printer.print("import \(i);") + } + printer.print("") + } + private func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { - printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in body(&printer) } } @@ -160,7 +179,7 @@ extension JNISwift2JavaGenerator { printer.print( """ long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(selfPointer); + return new \(type.qualifiedName)(selfPointer, swiftArena$); """ ) } @@ -182,6 +201,26 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the destroy function for a `JNISwiftInstance` + private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.print("private static native void $destroy(long selfPointer);") + + printer.print("@Override") + printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in + printer.print( + """ + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + \(type.swiftNominal.name).$destroy($selfPointer); + } + }; + """ + ) + } + } + /// Renders a Java function signature /// /// `func method(x: Int, y: Int) -> Int` becomes @@ -189,7 +228,12 @@ extension JNISwift2JavaGenerator { private func renderFunctionSignature(_ decl: ImportedFunc) -> String { let translatedDecl = translatedDecl(for: decl) let resultType = translatedDecl.translatedFunctionSignature.resultType - let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + + if decl.isInitializer { + parameters.append("SwiftArena swiftArena$") + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index b4d73873..a14c2078 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,11 @@ //===----------------------------------------------------------------------===// import JavaTypes +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { @@ -96,6 +101,8 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, method) printer.println() } + + printDestroyFunctionThunk(&printer, type) } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { @@ -199,24 +206,18 @@ extension JNISwift2JavaGenerator { resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { - var jniSignature = parameters.reduce(into: "") { signature, parameter in + let jniSignature = parameters.reduce(into: "") { signature, parameter in signature += parameter.type.jniTypeSignature } - // Escape signature characters - jniSignature = jniSignature - .replacingOccurrences(of: "_", with: "_1") - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: ";", with: "_2") - .replacingOccurrences(of: "[", with: "_3") - let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName)_" - + javaMethodName + + "_\(parentName.escapedJNIIdentifier)_" + + javaMethodName.escapedJNIIdentifier + "__" - + jniSignature + + jniSignature.escapedJNIIdentifier + let translatedParameters = parameters.map { "\($0.name): \($0.type.jniTypeName)" } @@ -251,6 +252,30 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the implementation of the destroy function. + private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printCDecl( + &printer, + javaMethodName: "$destroy", + parentName: type.swiftNominal.name, + parameters: [ + JavaParameter(name: "selfPointer", type: .long) + ], + isStatic: true, + resultType: .void + ) { printer in + // Deinitialize the pointer allocated (which will call the VWT destroy method) + // then deallocate the memory. + printer.print( + """ + let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + """ + ) + } + } + /// Renders the arguments for making a downcall private func renderDowncallArguments( swiftFunctionSignature: SwiftFunctionSignature, @@ -266,3 +291,29 @@ extension JNISwift2JavaGenerator { .joined(separator: ", ") } } + +extension String { + /// Returns a version of the string correctly escaped for a JNI + var escapedJNIIdentifier: String { + self.map { + if $0 == "_" { + return "_1" + } else if $0 == "/" { + return "_" + } else if $0 == ";" { + return "_2" + } else if $0 == "[" { + return "_3" + } else if $0.isASCII && ($0.isLetter || $0.isNumber) { + return String($0) + } else if let utf16 = $0.utf16.first { + // Escape any non-alphanumeric to their UTF16 hex encoding + let utf16Hex = String(format: "%04x", utf16) + return "_0\(utf16Hex)" + } else { + fatalError("Invalid JNI character: \($0)") + } + } + .joined() + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 891745a2..f9966793 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -39,7 +39,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) { * * @return a function that is called when the value should be destroyed. */ - abstract Runnable $createDestroyFunction(); + protected abstract Runnable $createDestroyFunction(); @Override public SwiftInstanceCleanup createCleanupAction() { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index c39d5815..0c0f4f02 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -44,8 +44,11 @@ struct JNIClassTests { // Swift module: SwiftModule package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; - public final class MyClass { + public final class MyClass extends JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -55,12 +58,25 @@ struct JNIClassTests { return true; } - private long selfPointer; - - private MyClass(long selfPointer) { - this.selfPointer = selfPointer; + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyClass.$destroy($selfPointer); + } + }; + } + """ ]) } @@ -116,9 +132,9 @@ struct JNIClassTests { * public init(x: Int64, y: Int64) * } */ - public static MyClass init(long x, long y) { + public static MyClass init(long x, long y, SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(x, y); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -128,9 +144,9 @@ struct JNIClassTests { * public init() * } */ - public static MyClass init() { + public static MyClass init(SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -170,4 +186,24 @@ struct JNIClassTests { ] ) } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024destroy__J") + func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } }