Using C/C++ in iOS
When I was developing a OpenGL ES based mobile application, in such a way that it would be portable for the other mobile systems, I had to choose what language to use. For iOS, using Objective-C would be the better option, most APIs are Objective-C exclusive, but then I would be tied to Apple's OS.
Fortunately, it's very easy to make Objective-C call C/C++ code, so I only needed to write Objective-C code to acess the iOS API.
iOS Project Setup
With a blank XCode iOS project, just making a GLKit ViewController is all that's needed to make the OpenGL code draw at the screen.
To call the C++ code where needed, to respond to system events, I had Objective-C code that would import and call C code, that was defined in a C++ file.
In my case, I needed to respond to the following events:
AppDelegate
:didFinishLauchWithOptions
: Called when iOS finishes loading the application;applicationWillResignActive
: Called when the application is not the active app;applicationDidEnterBackground
: Called when the application will be put in stand-by mode, so we need to free resources;applicationDidBecomeActive
: Called when the application is the active app again;
ViewController
:viewDidLoad
: Called when the GLKView will be displayed for the first time;update
edrawInRect
: Called once a frame, for screen/logic update;- eventos
Touch
: Called on touch events;
To call the C++ code when these events happen, just make a simple header file:
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TOUCH_START 0
#define TOUCH_MOVE 1
#define TOUCH_END 2
#define TOUCH_CANCEL 3
// didFinishLauchWithOptions
void GAME_startup(void);
// viewDidLoad
void GAME_init(int width, int height, float scale, bool tablet);
// applicationWillResignActive
void GAME_lostFocus(void);
// applicationDidBecomeActive
void GAME_restoredFocus(void);
// applicationDidEnterBackground
void GAME_saveState(void);
// drawInRect
void GAME_update(void);
// Touch
void GAME_processTouch(size_t id, unsigned action, unsigned x, unsigned y);
#ifdef __cplusplus
}
#endif
Objective-C side
At the Objective-C side, it could not be easier: just import the header file and call the correct functions.
AppDelegate
In the AppDelegate's implementation, we don't need to do much besides call the C++ functions. I must warn you that, at this moment, we may not have an active OpenGL context so we must only code for the basic functions of the system, like saving files or freeing memory.
#import "AppDelegate.h"
#import "header_cpp.h"
@interface IOS_AppDelegate ()
@end
@implementation IOS_AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
GAME_startup();
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
GAME_lostFocus();
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
GAME_saveState();
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
GAME_restoredFocus();
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
@end
ViewController
In our ViewController.m we have information about our viewport, touches that the user did, and many other information, so we have to prepare the data for the C++ functions, and make sure we have a valid OpenGL context.
#import "ViewController.h"
#import "header_cpp.h"
@interface IOS_ViewController () {}
@end
@implementation IOS_ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// setup OpenGL ES 2.0
EAGLContext* ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!ctx) {
NSLog(@"Context OpenGL error.");
exit(1);
}
GLKView* view = (GLKView*) self.view;
view.context = ctx;
[EAGLContext setCurrentContext: ctx];
// viewport setup
int h = view.bounds.size.height;
int w = view.bounds.size.width;
float s = view.contentScaleFactor;
bool tablet = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
GAME_init(w, h, s, tablet);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma MARK -- GLKView
- (void)update
{
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
if ( [EAGLContext currentContext] != view.context ) {
[EAGLContext setCurrentContext: view.context];
}
GAME_update();
}
#pragma MARK -- touch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
GLKView* view = (GLKView*) self.view;
for (UITouch * t in touches) {
CGPoint p = [t locationInView:self.view];
GAME_processTouch((size_t)t, TOUCH_START, p.x, view.bounds.size.height - p.y);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
GLKView* view = (GLKView*) self.view;
for (UITouch * t in touches) {
CGPoint p = [t locationInView:self.view];
GAME_processTouch((size_t)t, TOUCH_MOVE, p.x, view.bounds.size.height - p.y);
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
GLKView* view = (GLKView*) self.view;
for (UITouch * t in touches) {
CGPoint p = [t locationInView:self.view];
GAME_processTouch((size_t)t, TOUCH_END, p.x, view.bounds.size.height - p.y);
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
GLKView* view = (GLKView*) self.view;
for (UITouch * t in touches) {
CGPoint p = [t locationInView:self.view];
GAME_processTouch((size_t)t, TOUCH_CANCEL, p.x, view.bounds.size.height - p.y);
}
}
@end
Conclusion
Now we just need to implement our C++ functions in a .cpp
file and we're done!
Another thing I had to do was some way to get the proper filename of a file in my app, to load textures, sounds and other resource using STL streams.
Using CoreFoundation
, a set of system functions available for C, we can do it without much trouble:
#include <string>
#include <CoreFoundation/CoreFoundation.h>
#define BUF_SIZE 1024
std::string getFilePath(const std::string &filename) {
char buffer[BUF_SIZE];
auto bundle = CFBundleGetMainBundle();
auto str = CFStringCreateWithCString(NULL, filename.c_str(), kCFStringEncodingUTF8);
auto url = CFBundleCopyResourceURL(bundle,str, NULL, NULL);
CFRelease(str);
if (url == NULL) return {""};
CFURLGetFileSystemRepresentation(url, true, (UInt8*)&buffer, BUF_SIZE);
CFRelease(url);
return {buffer};
}