Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 4 additions & 4 deletions CodenameOne/src/com/codename1/system/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// [support for making platform native API calls](https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html). Notice
/// that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
/// case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
/// message would be sent and so forth.
/// or Swift message would be sent and so forth.
///
/// Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
/// and PeerComponent values. Any other type of parameter/return type is prohibited. However, once in the native layer
Expand Down Expand Up @@ -59,9 +59,9 @@
/// These sources should be placed under the appropriate folder in the native directory and are sent to the
/// server for compilation.
///
/// For Objective-C, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. One would need to provide both a header and
/// an "m" file following this convention e.g.:
/// For iOS, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
/// (by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
///
/// ```java
/// @interface com_my_code_MyNative : NSObject {
Expand Down
10 changes: 5 additions & 5 deletions CodenameOne/src/com/codename1/system/package.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<a href="https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html">
support for making platform native API calls</a>. Notice
that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
message would be sent and so forth.
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
or Swift message would be sent and so forth.
</p>
<p>
Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
Expand Down Expand Up @@ -66,9 +66,9 @@
server for compilation.
</p>
<p>
For Objective-C, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. One would need to provide both a header and
an "m" file following this convention e.g.:
For iOS, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
(by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
</p>
<pre>
@interface com_my_code_MyNative : NSObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.*;
import java.lang.reflect.Method;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
Expand Down Expand Up @@ -3972,6 +3974,117 @@ static String xmlize(String s) {
}


@Override
protected String registerNativeImplementationsAndCreateStubs(ClassLoader parentClassLoader, File stubDir, File... classesDirectory) throws MalformedURLException, IOException {
Class[] discoveredNativeInterfaces = findNativeInterfaces(parentClassLoader, classesDirectory);
String registerNativeFunctions = "";
if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class n : discoveredNativeInterfaces) {
registerNativeFunctions += " NativeLookup.register(" + n.getName() + ".class, "
+ n.getName() + "Stub.class" + ");\n";
}
}

if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class currentNative : discoveredNativeInterfaces) {
File folder = new File(stubDir, currentNative.getPackage().getName().replace('.', File.separatorChar));
folder.mkdirs();
File javaFile = new File(folder, currentNative.getSimpleName() + "Stub.java");

String javaImplSourceFile = "package " + currentNative.getPackage().getName() + ";\n\n"
+ "import com.codename1.ui.PeerComponent;\n\n"
+ "public class " + currentNative.getSimpleName() + "Stub implements " + currentNative.getSimpleName() + "{\n"
+ " private final Object impl = createImpl();\n\n"
+ " private static Object createImpl() {\n"
+ " try {\n"
+ " return Class.forName(\"" + currentNative.getName() + getImplSuffix() + "\").newInstance();\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to instantiate native implementation for " + currentNative.getName() + "\", t);\n"
+ " }\n"
+ " }\n\n"
+ " private Object __cn1Invoke(String methodName, Object[] args) {\n"
+ " try {\n"
+ " java.lang.reflect.Method[] methods = impl.getClass().getMethods();\n"
+ " for (java.lang.reflect.Method method : methods) {\n"
+ " if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {\n"
+ " return method.invoke(impl, args);\n"
+ " }\n"
+ " }\n"
+ " throw new RuntimeException(methodName + \" with \" + args.length + \" args\");\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to invoke native method \" + methodName, t);\n"
+ " }\n"
+ " }\n\n";

for (Method m : currentNative.getMethods()) {
String name = m.getName();
if (name.equals("hashCode") || name.equals("equals") || name.equals("toString")) {
continue;
}

Class returnType = m.getReturnType();

javaImplSourceFile += " public " + returnType.getSimpleName() + " " + name + "(";
Class[] params = m.getParameterTypes();
String args = "";
if (params != null && params.length > 0) {
for (int iter = 0; iter < params.length; iter++) {
if (iter > 0) {
javaImplSourceFile += ", ";
args += ", ";
}
javaImplSourceFile += params[iter].getSimpleName() + " param" + iter;
if (params[iter].getName().equals("com.codename1.ui.PeerComponent")) {
args += convertPeerComponentToNative("param" + iter);
} else {
args += "param" + iter;
}
}
}
javaImplSourceFile += ") {\n";
String invocationExpression = "__cn1Invoke(\"" + name + "\", new Object[]{" + args + "})";
if (Void.class == returnType || Void.TYPE == returnType) {
javaImplSourceFile += " " + invocationExpression + ";\n }\n\n";
} else {
if (returnType.getName().equals("com.codename1.ui.PeerComponent")) {
javaImplSourceFile += " return " + generatePeerComponentCreationCode(invocationExpression) + ";\n }\n\n";
} else if (returnType.isPrimitive()) {
if (returnType == Boolean.TYPE) {
javaImplSourceFile += " return ((Boolean)" + invocationExpression + ").booleanValue();\n }\n\n";
} else if (returnType == Integer.TYPE) {
javaImplSourceFile += " return ((Integer)" + invocationExpression + ").intValue();\n }\n\n";
} else if (returnType == Long.TYPE) {
javaImplSourceFile += " return ((Long)" + invocationExpression + ").longValue();\n }\n\n";
} else if (returnType == Byte.TYPE) {
javaImplSourceFile += " return ((Byte)" + invocationExpression + ").byteValue();\n }\n\n";
} else if (returnType == Short.TYPE) {
javaImplSourceFile += " return ((Short)" + invocationExpression + ").shortValue();\n }\n\n";
} else if (returnType == Character.TYPE) {
javaImplSourceFile += " return ((Character)" + invocationExpression + ").charValue();\n }\n\n";
} else if (returnType == Float.TYPE) {
javaImplSourceFile += " return ((Float)" + invocationExpression + ").floatValue();\n }\n\n";
} else if (returnType == Double.TYPE) {
javaImplSourceFile += " return ((Double)" + invocationExpression + ").doubleValue();\n }\n\n";
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
}
}

javaImplSourceFile += "}\n";

try (FileOutputStream out = new FileOutputStream(javaFile)) {
out.write(javaImplSourceFile.getBytes(StandardCharsets.UTF_8));
}
}
}

return registerNativeFunctions;
}

@Override
protected String generatePeerComponentCreationCode(String methodCallString) {
return "PeerComponent.create(" + methodCallString + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1097,16 +1097,62 @@ public void usesClassMethod(String cls, String method) {
String classNameWithUnderscores = currentNative.getName().replace('.', '_');
String mSourceFile = "#include \"xmlvm.h\"\n"
+ "#include \"java_lang_String.h\"\n"
+ "#include <stdlib.h>\n"
+ "#import \"CodenameOne_GLViewController.h\"\n"
+ "#import <UIKit/UIKit.h>\n"
+ "#import \"" + classNameWithUnderscores + "Impl.h\"\n" + newVMInclude
+ "#import <objc/runtime.h>\n"
+ newVMInclude
+ "#include \"" + classNameWithUnderscores + "ImplCodenameOne.h\"\n\n"
+ "static id cn1_createNativeInterfacePeer(NSString* className) {\n"
+ " NSMutableArray* candidates = [NSMutableArray arrayWithObject:className];\n"
+ " NSString* executableName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"CFBundleExecutable\"];\n"
+ " NSString* bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"CFBundleName\"];\n"
+ " NSArray* moduleNames = @[executableName ?: @\"\", bundleName ?: @\"\"];\n"
+ " for(NSString* moduleName in moduleNames) {\n"
+ " if(moduleName.length == 0) {\n"
+ " continue;\n"
+ " }\n"
+ " NSString* sanitized = [[moduleName stringByReplacingOccurrencesOfString:@\"-\" withString:@\"_\"] stringByReplacingOccurrencesOfString:@\" \" withString:@\"_\"];\n"
+ " [candidates addObject:[sanitized stringByAppendingFormat:@\".%@\", className]];\n"
+ " if(![sanitized isEqualToString:moduleName]) {\n"
+ " [candidates addObject:[moduleName stringByAppendingFormat:@\".%@\", className]];\n"
+ " }\n"
+ " }\n"
+ " Class cls = Nil;\n"
+ " for(NSString* candidate in candidates) {\n"
+ " cls = NSClassFromString(candidate);\n"
+ " if(cls != Nil) {\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ " if(cls == Nil) {\n"
+ " unsigned int classCount = 0;\n"
+ " Class *classList = objc_copyClassList(&classCount);\n"
+ " NSString* dottedSuffix = [@\".\" stringByAppendingString:className];\n"
+ " for(unsigned int i = 0; i < classCount; i++) {\n"
+ " NSString* runtimeName = [NSString stringWithUTF8String:class_getName(classList[i])];\n"
+ " if([runtimeName isEqualToString:className] || [runtimeName hasSuffix:dottedSuffix] || [runtimeName hasSuffix:className]) {\n"
+ " cls = classList[i];\n"
+ " NSLog(@\"[CN1] Resolved native interface class %@ via runtime scan as %@\", className, runtimeName);\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ " if(classList != NULL) {\n"
+ " free(classList);\n"
+ " }\n"
+ " }\n"
+ " if(cls == Nil) {\n"
+ " NSLog(@\"[CN1] Failed to find native interface class %@. Tried: %@\", className, candidates);\n"
+ " return nil;\n"
+ " }\n"
+ " return [[cls alloc] init];\n"
+ "}\n\n"
+ "JAVA_LONG " + classNameWithUnderscores + "ImplCodenameOne_initializeNativePeer__" + postfixForNewVM + "(" + prefixForNewVM + ") {\n"
+ " " + classNameWithUnderscores + "Impl* i = [[" + classNameWithUnderscores + "Impl alloc] init];\n"
+ " id i = cn1_createNativeInterfacePeer(@\"" + classNameWithUnderscores + "Impl\");\n"
+ " return i;\n"
+ "}\n\n"
+ "void " + classNameWithUnderscores + "ImplCodenameOne_releaseNativePeerInstance___long(" + prefix2ForNewVM + "JAVA_LONG l) {\n"
+ " " + classNameWithUnderscores + "Impl* i = (" + classNameWithUnderscores + "Impl*)l;\n"
+ " id i = (id)l;\n"
+ " [i release];\n"
+ "}\n\n"
+ "extern NSData* arrayToData(JAVA_OBJECT arr);\n"
Expand Down Expand Up @@ -1135,8 +1181,7 @@ public void usesClassMethod(String cls, String method) {
String mFileBody;

mFileArgs = "(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me";
mFileBody = " " + classNameWithUnderscores + "Impl* ptr = (" + classNameWithUnderscores +
"Impl*)get_field_" + classNameWithUnderscores + "ImplCodenameOne_nativePeer(me);\n";
mFileBody = " id ptr = (id)get_field_" + classNameWithUnderscores + "ImplCodenameOne_nativePeer(me);\n";


if(!(returnType.equals(Void.class) || returnType.equals(Void.TYPE))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codenameone.examples.hellocodenameone

class SwiftKotlinNativeImpl {
fun implementationLanguage(): String {
return "kotlin"
}

fun diagnostics(): String {
return "android-kotlin-native-impl"
}

fun isSupported(): Boolean {
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.codenameone.examples.hellocodenameone;

import com.codename1.system.NativeLookup;
import com.codename1.ui.CN;

public final class NativeInterfaceLanguageValidator {
private static String lastStatus = "UNINITIALIZED";

private NativeInterfaceLanguageValidator() {
}

public static String getLastStatus() {
return lastStatus;
}

public static void validate() {
String platformName = CN.getPlatformName();
String normalizedPlatform = platformName == null ? "" : platformName.toLowerCase();
System.out.println("CN1SS:SWIFT_DIAG:START platform=" + platformName);
lastStatus = "START platform=" + platformName;
boolean isAndroid = normalizedPlatform.contains("android");
boolean isIos = normalizedPlatform.contains("ios") || normalizedPlatform.contains("iphone");
if (!isAndroid && !isIos) {
System.out.println("CN1SS:SWIFT_DIAG:SKIP platform=" + platformName);
lastStatus = "SKIP platform=" + platformName;
return;
}

SwiftKotlinNative nativeImpl = NativeLookup.create(SwiftKotlinNative.class);
System.out.println("CN1SS:SWIFT_DIAG:NATIVE_LOOKUP result=" + (nativeImpl == null ? "null" : nativeImpl.getClass().getName()));
if (nativeImpl == null) {
lastStatus = "LOOKUP_NULL platform=" + platformName;
throw new IllegalStateException("SwiftKotlinNative lookup returned null on " + platformName);
}
if (!nativeImpl.isSupported()) {
lastStatus = "NOT_SUPPORTED platform=" + platformName;
throw new IllegalStateException("SwiftKotlinNative is not available on " + platformName);
}

String expected = isAndroid ? "kotlin" : "swift";
String actual = nativeImpl.implementationLanguage();
String diagnostics = nativeImpl.diagnostics();
System.out.println("CN1SS:SWIFT_DIAG:RESULT expected=" + expected + " actual=" + actual + " diagnostics=" + diagnostics);
if (!expected.equalsIgnoreCase(actual)) {
lastStatus = "MISMATCH expected=" + expected + " actual=" + actual + " diagnostics=" + diagnostics;
throw new IllegalStateException("Expected " + expected + " implementation on " + platformName + " but got " + actual + ". diagnostics=" + diagnostics);
}
lastStatus = "OK expected=" + expected + " actual=" + actual + " diagnostics=" + diagnostics;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.codenameone.examples.hellocodenameone;

import com.codename1.system.NativeInterface;

public interface SwiftKotlinNative extends NativeInterface {
String implementationLanguage();
String diagnostics();
}
Loading
Loading