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:

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};
}