Usando C/C++ no iOS
Quando fui desenvolver uma aplicação móvel baseada em OpenGL ES, de maneira que fosse de fácil conversão para vários sistemas, as opções de linguagens caíram bastante. No caso do iOS, utilizar Objective-C poderia ser a melhor opção, mas daí estaria preso ao sistema da Apple. Ainda assim, grande parte dos APIs do sistema são voltados para o uso com Objective-C, então não dá para utilizar exclusivamente C++, que é a opção mais viável quando se pensa em compilar o mesmo programa para os vários sistemas disponíveis.
Felizmente é muito fácil fazer essa comunicação entre as 2 linguagens no iOS.
Projeto para iOS
Com um projeto em branco no XCode, e colocando um GLKit View no controlador inicial é o suficiente para termos o necessário para nosso código OpenGL poder desenhar na tela do dispositivo.
O que precisamos fazer é chamar nosso código C++ nos vários eventos que devem ser implementados do lado Objective-C. Como o ele é capaz de chamar código C diretamente, é só aproveitar disso e criar um arquivo com as declarações das funções que vão ser usadas pelo Objective-C para se comunicar com o código C++.
No meu caso, precisava cuidar dos seguintes eventos:
AppDelegate
:didFinishLauchWithOptions
: Evento para quando o aplicativo é iniciado pelo iOS;applicationWillResignActive
: Evento quando nosso aplicativo deixa de ser ativo;applicationDidEnterBackground
: Evento quando o sistema coloca nosso aplicativo para dormir, e devemos liberar recursos;applicationDidBecomeActive
: Evento quando nosso aplicativo volta a ser ativo.
ViewController
:viewDidLoad
: Evento quando nossoView
está sendo mostrado na tela pela primeira vez.update
edrawInRect
: Eventos do GLKit View chamados a cada frame para atualização.- eventos
Touch
: Eventos para tratar os toques na tela.
Para tratar estes eventos, criei o seguinte cabeçalho C:
#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
Lado do Objective-C
No lado do Objective-C, não poderia ser mais fácil: é só incluir nosso arquivo de cabeçalho e chamar as funções corretas.
AppDelegate
No arquivo que implementa nosso AppDelegate, não precisamos de quase nada além de chamar os métodos. Devo lembrar que neste momento, não temos necessariamente um contexto OpenGL, então devemos nos limitar a utilizar apenas as coisas que não dependem do sistema.
#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
Já nosso ViewController.m deve fazer algumas coisas antes de passar o controle para o C++, como tratar os eventos de maneira adequada, criar e ajustar o contexto OpenGL antes de qualquer código OpenGL, etc.
#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
Lado C++
Agora, é só criar um arquivo .cpp
que inclua e defina as funções declaradas no nosso cabeçalho,
e pronto!
Outra coisa que tive que fazer foi uma maneira de como ter o caminho correto para os arquivos dentro
de meu aplicativo, para ler imagens, texturas e outras coisas. Utilizando as funções mais básicas
do sistema iOS, disponíveis para C via CoreFoundation
, dá para fazer uma função auxiliar para ser utilizada em
conjunto com qualquer função C++, inclusive as funções de iostream
. Segue o código para quem
interessar.
Depois de getFilePath
ser declarado em algum arquivo de cabeçalho... é só definir:
#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};
}
Espero que tenha ajudado quem está começando a utilizar C++ em iOS!