After a few long days, I've managed to compile the objective-c runtime on android as well an implementation of the Foundation framework. The motivation behind this effort is primarily to investigate the possibility of using cocos2d-iphone on android.
You may be familiar with our game Super Stickman Golf. For the iphone version we used cocos2d-iphone. When we made the decision to port it to android, we used cocos2d-x. Great that the api is pretty much the same, but not so great that it's c++... Most of the work was literally converting obj-c to c++... I do not want to do that again, and to be honest I prefer obj-c over c++ big time.
I wanted to share a code snippet I've managed to get running on android and how I accomplished it. As well as get some feedback from some of the core cocos2d developers (riq) on the possibility of continuing this effort to get a version of cocos2d-iphone running on android...
The following is a code snippet I have working and running on android NDK:
#import <jni.h>
#import <string.h>
#import <android/log.h>
#import <Foundation/Foundation.h>
#import <Foundation/NSString_placeholder.h>
#import <Foundation/TestObjClass3.h>
#import <TestLibrary/TestObjClass2.h>
#import <objc/NXConstStr.h>
#define DEBUG_TAG "NDKSetupActivity"
@interface TestNewObj : NSObject {
int intProp;
}
@property (assign) int intProp;
- (void)testPrintMethod:(const char*)string;
- (void)testPrintMethodNSString:(NSString*)string;
+ (void)testStaticPrintMethod:(const char*)string;
@end
@implementation TestNewObj
@synthesize intProp;
- (void)testPrintMethod:(const char*)string {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: %s", string);
}
+ (void)testStaticPrintMethod:(const char*)string {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: %s", string);
}
- (void)testPrintMethodNSString:(NSString*)string {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: %s", [string UTF8String]);
}
@end
extern "C"
{
void Java_com_noodlecake_NDKSetup_NDKSetupActivity_printLog(JNIEnv * env, jobject thiz, jstring logString) {
jboolean isCopy;
const char * szLogString = (env)->GetStringUTFChars(logString, &isCopy); // get a java string
// Lets print a string from java...
[TestNewObj testStaticPrintMethod:szLogString];
// Lets print a const char*
[TestNewObj testStaticPrintMethod:"Starting tests..."];
// Lets create some new objective-c objects
TestNewObj* test = [[TestNewObj alloc] init];\
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Creating object: %p", test);
TestNewObj* test2 = [[TestNewObj alloc] init];
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Creating object: %p", test2);
TestNewObj* test3 = [[TestNewObj alloc] init];
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Creating object: %p", test3);
//Lets test the object's property
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Setting object int property to: %i", 675);
test.intProp = 675;
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Object property is: %i", test.intProp);
// Lets create a mutable array and a mutable dictionary
NSMutableArray* arr = [[NSMutableArray arrayWithCapacity:20] retain];
NSMutableDictionary* dict = [[NSMutableDictionary dictionaryWithCapacity:20] retain];
// Lets add some objects to the array
[arr addObject:test];
[arr addObject:test2];
[arr addObject:test3];
// Lets iterate over the array (NOTE* gcc-4.6.1 can't use FastEnumeration in objetive-c++ files (.mm)
for(int i =0; i < [arr count]; i++) {
[TestNewObj testStaticPrintMethod:"Loop!"];
// Lets grab the object
TestNewObj* currentObj = (TestNewObj*)[arr objectAtIndex:i];
// Lets create a NSString key and add this object to the mutable dictionary
char tmp2[] = {'a', 'b', 'c', 'd'};
NSString_placeholder* nss = [[NSString_placeholder alloc] initWithBytes:tmp2 length:i+1 encoding:NSUTF8StringEncoding];
[dict setObject:currentObj forKey:nss];
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Inserting object: %p for key: %s", currentObj, [nss UTF8String]);
}
// Lets iterate over the dictionary and check the contents..
NSArray* keys = [dict allKeys];
for(int i =0; i < [keys count]; i++) {
NSString* keyString = (NSString*)[keys objectAtIndex:i];
TestNewObj* currentObj = (TestNewObj*)[dict objectForKey:keyString];
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK: Retrieved object: %p for key: %s", currentObj, [keyString UTF8String]);
}
[TestNewObj testStaticPrintMethod:"Ending Tests..."];
(env)->ReleaseStringUTFChars(logString, szLogString); // release the java string
}
}
The following is the output from the code snippet from android LogCat:
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Starting tests...
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Creating object: 0x339880
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Creating object: 0x3372b0
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Creating object: 0x336dc0
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Setting object int property to: 675
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Object property is: 675
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Loop!
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Inserting object: 0x339880 for key: a
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Loop!
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Inserting object: 0x3372b0 for key: ab
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Loop!
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Inserting object: 0x336dc0 for key: abc
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Retrieved object: 0x3372b0 for key: ab
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Retrieved object: 0x339880 for key: a
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Retrieved object: 0x336dc0 for key: abc
12-08 09:49:12.371: DEBUG/NDKSetupActivity(3166): NDK: Ending Tests...
First I'll explain how the above code was actually compiled:
* GCC 4.6+ has built in support for objective-c and objective-c++ (gnustep-runtime). I've updated and patched the default gcc that comes with the android ndk to gcc 4.6.1. (https://docs.google.com/present/view?id=dfj692w3_351gq89x8fv&pli=1)
* I've modified the android make system to recognize .m and .mm as valid source files, android's gcc can now compile them
* GCC 4.6+ gives us an objective-c runtime, but no Foundation framework. With a lot of hacking and patching, I managed to get the cocoatron Foundation implementation compiling and linking. (http://cocotron.org/)
Some notes about the above code snippet and output:
* The JNI function is called from the java-side.
* My custom objc class inherits from NSObject.
* NSMutableArray and NSDictionary seem to be working properly.
* GCC 4.6+ doesn't have support for FastEnumeration in objc++ files (.mm). Support is only in (.m) files.
* GCC 4.6+ doesn't have built-in support for blocks.
What I'm looking for:
* Thoughts and feedback.
* What internal parts of cocos2d-iphone depend on frameworks other than Foundation? I know there's some CoreGraphics and some UIKit dependancies, but I suspect we can potentially use the cocos2d-x implementations for some of those pieces... Example, I *believe* the cocos2d-iphone texture loading uses UIImage. We could potentially use the cocos2d-x version instead of relying on UIImage.
* I'm also pretty sure there are implementations of some of the CoreGraphics stuff. I think cocotron has implementations of all the matrix transformations, etc.
I think with some effort I can get cocos2d-iphone into a state where it can be running on android. I will already be using this system to keep our game logic in objective-c for android and iphone platforms.