Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework target: using Java classes as native ObjC #457

Merged
merged 6 commits into from
Feb 26, 2020
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.robovm.compiler.llvm.VariableRef;
import org.robovm.compiler.plugin.CompilerPlugin;
import org.robovm.compiler.plugin.debug.DebuggerDebugObjectFileInfo;
import org.robovm.compiler.plugin.objc.ObjCMemberPlugin;
import org.robovm.compiler.trampoline.Checkcast;
import org.robovm.compiler.trampoline.Instanceof;
import org.robovm.compiler.trampoline.Invoke;
Expand Down Expand Up @@ -225,7 +226,8 @@ public class ClassCompiler {
private final GlobalValueMethodCompiler globalValueMethodCompiler;
private final AttributesEncoder attributesEncoder;
private final TrampolineCompiler trampolineResolver;

private final ObjCMemberPlugin.MethodCompiler objcMethodCompiler;

private final ByteArrayOutputStream output = new ByteArrayOutputStream(256 * 1024);

public ClassCompiler(Config config) {
Expand All @@ -238,6 +240,7 @@ public ClassCompiler(Config config) {
this.globalValueMethodCompiler = new GlobalValueMethodCompiler(config);
this.attributesEncoder = new AttributesEncoder();
this.trampolineResolver = new TrampolineCompiler(config);
this.objcMethodCompiler = new ObjCMemberPlugin.MethodCompiler(config);
}

public boolean mustCompile(Clazz clazz) {
Expand Down Expand Up @@ -890,6 +893,8 @@ private void compile(Clazz clazz, OutputStream out) throws IOException {
function = structMember(method);
} else if (method.isNative()) {
function = nativeMethod(method);
} else if (objcMethodCompiler.willCompile(method)) {
function = objcPublishMethod(method);
} else if (!method.isAbstract()) {
function = method(method);
}
Expand Down Expand Up @@ -1587,6 +1592,10 @@ private Function globalValueMethod(SootMethod method) {
return compileMethod(globalValueMethodCompiler, method);
}

private Function objcPublishMethod(SootMethod method) {
return compileMethod(objcMethodCompiler, method);
}

private Function method(SootMethod method) {
return compileMethod(javaMethodCompiler, method);
}
Expand Down
16 changes: 16 additions & 0 deletions compiler/compiler/src/main/java/org/robovm/compiler/Linker.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public int compareTo(TypeInfo o) {

private final Config config;
private final Map<String, byte[]> runtimeData = new HashMap<>();
private final Map<String, byte[]> bcGlobalData = new HashMap<>();

public Linker(Config config) {
this.config = config;
Expand All @@ -155,6 +156,16 @@ public void addRuntimeData(String id, byte[] data) {
runtimeData.put(id, data);
}

/**
* adds global with data.
* allows plugins to register own data to be accesible in native/bc code
*/
public void addBcGlobalData(String name, byte[]data) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(data, "data");
bcGlobalData.put(name, data);
}

private ArrayConstant runtimeDataToBytes() throws UnsupportedEncodingException {
// The data consists of key value pairs prefixed with the number of
// pairs (int). In each pair the key is an UTF8 encoded string prefixed
Expand Down Expand Up @@ -199,6 +210,11 @@ public void link(Set<Clazz> classes) throws IOException {
mb.addInclude(getClass().getClassLoader().getResource("header.ll"));

mb.addGlobal(new Global("_bcRuntimeData", runtimeDataToBytes()));
// add globals from plugins
for (Entry<String, byte[]> e : bcGlobalData.entrySet()) {
ArrayConstant value = new ArrayConstantBuilder(I8).add(e.getValue()).build();
mb.addGlobal(new Global(e.getKey(), new ConstantGetelementptr(mb.newGlobal(value, true).ref(), 0, 0)));
}

ArrayConstantBuilder staticLibs = new ArrayConstantBuilder(I8_PTR);
for (Config.Lib lib : config.getLibs()) {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public String getInstallRelativeArchivePath(Path path) {

@Override
protected List<String> getTargetExportedSymbols() {
return Arrays.asList("JNI_*", "rvmInstantiateFramework");
return Arrays.asList("JNI_*", "rvmInstantiateFramework", "OBJC_CLASS_$_*");
}

private String getMinimumOSVersion() {
Expand Down
16 changes: 15 additions & 1 deletion compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,21 @@ private static String getCustomClassName(Class<? extends ObjCObject> type) {
name = name.replace('.', '_');
return name;
}


/**
* support for OBJC_CLASS_$_
* once data from handle is copied to OBJC_CLASS_$_ there is two class_t struct for same class:
* - one in OBJC_CLASS_$_
* - second created runtime and pointed by handle
* just to have inside RoboVM and external objc one lets replace handle with alias (pointer to OBJC_CLASS_$_)
* WARNING: Shall not be called directly
*/
@Deprecated
public void replaceHandle(long aliasHandle) {
ObjCObject.ObjectOwnershipHelper.replaceHandle(getHandle(), aliasHandle);
setHandle(aliasHandle);
}

@SuppressWarnings("unchecked")
private static ObjCClass register(Class<? extends ObjCObject> type, String name) {
ObjCClass superclass = getByType((Class<? extends ObjCObject>) type.getSuperclass());
Expand Down
11 changes: 11 additions & 0 deletions compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@ public static void registerClass(long cls) {
registerCallbackMethod(cls, dealloc, 0, deallocMethod);
}

public static void replaceHandle(long cls, long replacementCls) {
synchronized (customClassToNativeSuper) {
Long nativeSuper = customClassToNativeSuper.remove(cls);
if (nativeSuper == null) {
throw new Error(
"Failed to register alias as class for it is not registered");
}
customClassToNativeSuper.put(replacementCls, nativeSuper);
}
}

private static void registerCallbackMethod(long cls, long selector, long newSelector, Method method) {
long superMethod = ObjCRuntime.class_getInstanceMethod(cls, selector);
long typeEncoding = ObjCRuntime.method_getTypeEncoding(superMethod);
Expand Down
61 changes: 44 additions & 17 deletions compiler/vm/frameworksupport/src/frameworksupport.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,61 @@
#import <Foundation/Foundation.h>
#import "jni.h"

static dispatch_once_t once;
static id instance;
NSObject* rvmInstantiateFramework(const char *className) {
dispatch_once(&once, ^ {
static JNIEnv *env;
const char* __attribute__ ((weak)) _bcFrameworkPreloadClasses = NULL;

__attribute__((constructor))
static void initializer(int argc, char** argv, char** envp) {
static dispatch_once_t createVmOnce;
dispatch_once(&createVmOnce, ^ {
JavaVMInitArgs vm_args;
JavaVM *vm;
JNIEnv *env;
jint res;

/*
* Create the RoboVM "VM" using standard the JNI JNI_CreateJavaVM() function
* which is exported by our framework.
*/
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 0;
vm_args.options = NULL;
vm_args.nOptions = argc;
JavaVMOption options[argc];
if (argc) {
for (int idx = 0; idx < argc; idx++) {
options[idx].optionString = argv[idx];
options[idx].extraInfo = NULL;
}
vm_args.options = options;
} else {
vm_args.options = NULL;
}

res = JNI_CreateJavaVM(&vm, &env, &vm_args);
if (res != JNI_OK) {
[NSException raise:@"JNI_CreateJavaVM() failed" format:@"%d", res];
}

/*
* Lookup the root framework Java class that and call its initantiate() method to obtain Object instance
*/

// preload classes needed for framework
// _bcFrameworkPreloadClasses contains zero terminated strings of classes to preload
// it finishes with zero length string
if (_bcFrameworkPreloadClasses) {
const char* className = _bcFrameworkPreloadClasses;
while (*className) {
jclass cls = (*env)->FindClass(env, className);
if (!cls) {
[NSException raise:@"Failed to preload class for framework" format:@"classname = %s", className];
}
className += strlen(className) + 1;
}
}
});
}

// deprecated, for compatibility with old code
NSObject* rvmInstantiateFramework(const char *className) {
static dispatch_once_t createFrameworkInstanceOnce;
static id instance;
dispatch_once(&createFrameworkInstanceOnce, ^ {
// Lookup the root framework Java class that and call its instantiate() method to obtain Object instance
char *jvmClassName = strdup(className);
for (char *p = jvmClassName; *p; p++) {
if (*p == '.')
Expand All @@ -52,9 +83,7 @@
[NSException raise:@"Failed to find class" format:@"classname = %s", className];
}

/*
* Now lookup and call instantiate method to receive instance of framework class
*/
// lookup and call instantiate method to receive instance of framework class
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "instantiate", "()Lorg/robovm/apple/foundation/NSObject;");
if (!mid) {
[NSException raise:@"Failed to find method initialize()" format:@"classname = %s", className];
Expand All @@ -67,9 +96,7 @@
[NSException raise:@"Call to method initialize() returned NULL object" format:@"classname = %s", className];
}

/*
* Now fetch pointer to objc object from java, calling NSObject.getHandle() for this
*/
// fetch pointer to objc object from java, calling NSObject.getHandle() for this
cls = (*env)->FindClass(env, "org/robovm/apple/foundation/NSObject");
if (!cls) {
[NSException raise:@"Failed to find class NSObject" format:@""];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,16 @@ private void applyCustomValues(Map<String, String> customTitles) {
}

private void applyCustomTitle(String title, JLabel label) {
if (title != null)
if (title != null) {
label.setText(title);
label.setVisible(!title.isEmpty());
}
}

private void applyCustomValue(String value, JTextField field) {
if (value != null)
if (value != null) {
field.setText(value);
field.setVisible(!value.isEmpty());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="518df" class="javax.swing.JLabel">
<component id="518df" class="javax.swing.JLabel" binding="mainClassNameLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ public ProjectTemplate[] createTemplates(String group, WizardContext context) {
new RoboVmProjectTemplate("RoboVM iOS Framework", "A basic iOS framework template ", new RoboVmModuleBuilder("ios-framework",
new HashMap<String, String>(){{
put("appNameLabel", "Framework Name");
put("appIdLabel", "Framework Id");
put("mainClassName", "MyFramework");
put("packageName", "com.mycompany.myframework");
put("appName", "MyFramework");
put("appIdLabel", "Framework Id");
put("appId", "com.mycompany.myframework");
put("packageName", "com.mycompany.myframework");
put("mainClassNameLabel", ""); // hide as not used for framework target
put("mainClassName", ""); // hide as not used for framework target
}})),
new RoboVmProjectTemplate("RoboVM Console App", "A console app for Mac OS X or Linux", new RoboVmModuleBuilder("console")),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,24 @@
#import <Foundation/Foundation.h>

//
// Calculator API -- everything is exposed as protocols
// Calculator class
//
NS_SWIFT_NAME(Calculator)
@protocol Calculator
@interface Calculator
-(id)init;
-(id)initWithValue:(int)startValue;
-(int)reset;
-(int)add:(int) i;
-(int)sub:(int) i;
-(int)result;
@end

// and typedef NSObject<Calculator> to Calculator which makes to make code better readable
typedef NSObject<Calculator> Calculator;


//
// ${mainClass} of framework. it is entry point to framework.
// ${appName}Demo class with basic API demonstration
//
NS_SWIFT_NAME(${mainClass})
@protocol ${mainClass}
-(Calculator *) createCalculator;
-(void) sayHello;
-(NSString*) roboVmVersion;
@interface ${appName}Demo
-(id)init;
-(id)initWithText:(NSString*)text;
+(void)hello;
-(NSString*)roboVmVersion;
-(void)installSignals:(void(^)(void))installer;
@end
typedef NSObject<${mainClass}> ${mainClass};

//
// static function that returns instance of Framework's main class. On first access it also instantiate RoboVM
//
static ${mainClass}* ${appName}Instance() {
extern ${mainClass}* rvmInstantiateFramework(const char *className);
return rvmInstantiateFramework("${package}.${mainClass}Impl");
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<!-- We're building a framework. -->
<target>framework</target>

<!-- The framework name is defined in robovm.properties. -->
<imageName>${symbol_dollar}{framework.name}</imageName>
<!-- The framework image name (executable name) is defined in robovm.properties. -->
<imageName>${symbol_dollar}{framework.executable}</imageName>

<!-- The Info.plist.xml file will be copied into the framework. -->
<infoPList>Info.plist.xml</infoPList>
Expand Down

This file was deleted.

Loading