261 lines
8.8 KiB
Markdown
261 lines
8.8 KiB
Markdown
+++
|
|
template = "article.html"
|
|
title = "Share code across multiple platforms"
|
|
date = 2015-06-11T15:33:00+02:00
|
|
description = "Exploring strategies for sharing code between Android and iOS platforms while maintaining native performance and user experience."
|
|
|
|
[taxonomies]
|
|
tags = ["android", "ios"]
|
|
+++
|
|
|
|
When writing an application, you probably want it to run on most platforms
|
|
possible. Having a game on Android is great, but what about this weird friend
|
|
with his iPhone? It would be nice to be able to play with him. Of course there
|
|
are cross-platforms technologies like [Cordova](http://cordova.apache.org/) or
|
|
[Titanium](http://www.appcelerator.com/titanium/). But sadly, you can't achieve
|
|
both a perfect user experience and great performances with this kind of tools.
|
|
And even if you could: what about reusing code on the back-end? We need to share
|
|
some code.
|
|
|
|
<!--more-->
|
|
|
|
## Greatest common divisor
|
|
|
|
Every platform is different: iOS runs Objective-C, Android works with Java,
|
|
Windows Phone is using C#, desktop platforms can run a mix of everything and
|
|
even more... But all these languages share the same ability: call C code. We'll
|
|
take this ability a little farther and use C++.
|
|
|
|
While there is no point in writing all the application code in C++, as you need
|
|
a distinct UI for each platform (except for a game, where you'll probably write
|
|
a themed UI anyway), we can write the logic in C++.
|
|
|
|
## Android
|
|
|
|
Java can call C code trough
|
|
[JNI](http://en.wikipedia.org/wiki/Java_Native_Interface), which stands for Java
|
|
Native Interface. JNI is used for mainly two things: performance-critical
|
|
operations (depending on the case, native code can run faster than Java), or to
|
|
access platform-specific APIs. Some parts of the JDK uses JNI (e.g. to access
|
|
sound devices).
|
|
|
|
However, everything is not perfect with JNI. You lose the cross-platform aspect
|
|
of Java, as you're using native code which has to be rebuilt for every platform
|
|
you plan to run it on, and you need to take great care with memory management.
|
|
Every object acquired or allocated by native code must be released manually, the
|
|
garbage collector won't do anything to them.
|
|
|
|
JNI is pretty low-level, and a really complicated thing. I won't go into
|
|
details here. We will use it in combination with another tool:
|
|
[SWIG](http://www.swig.org/).
|
|
|
|
To use SWIG, you need two things: write your C++ classes like you would do for
|
|
any C++ program, and write a SWIG-specific interface declaration. Then, SWIG
|
|
will generate some more code:
|
|
|
|
- a C wrapper around your classes
|
|
- a Java class, reflecting your SWIG interface.
|
|
|
|
The SWIG-specific interface is mostly C++-compatible. In many case, you can just
|
|
include the C++ header, and you're done. Let's see how it works with a small
|
|
example:
|
|
|
|
{{ filename(body="counter.hpp") }}
|
|
|
|
```cpp
|
|
class Counter {
|
|
public:
|
|
Counter(int initialValue);
|
|
|
|
void increment();
|
|
int getValue() const;
|
|
|
|
private:
|
|
int _value;
|
|
};
|
|
```
|
|
|
|
{{ filename(body="counter.cpp") }}
|
|
|
|
```cpp
|
|
#include "counter.hpp"
|
|
|
|
Counter::Counter(int initialValue) :
|
|
_value(initialValue) {
|
|
}
|
|
|
|
void Counter::increment() {
|
|
++_value;
|
|
}
|
|
|
|
int Counter::getValue() const {
|
|
return _value;
|
|
}
|
|
```
|
|
|
|
{{ filename(body="counter.i") }}
|
|
|
|
```swig
|
|
%module Counter_module
|
|
|
|
%{
|
|
#include "counter.hpp"
|
|
%}
|
|
|
|
%include "counter.hpp"
|
|
```
|
|
|
|
Now, let's run SWIG and see what happens: `swig -c++ -java counter.i`. It
|
|
creates four files:
|
|
|
|
- `counter_wrap.cxx`, which exports our methods in C functions, and must be
|
|
compiled in a shared library (the one which will be loaded by Java),
|
|
- `Counter_module.java`, which contains module-level functions (you can define
|
|
multiple classes in a same module, and functions outside any class), we won't
|
|
use it in the example,
|
|
- `Counter_moduleJNI.java`, which contains all the raw JNI bindings, used
|
|
internaly by SWIG,
|
|
- `Counter.java`, which is the Java class you will actualy use. Let's see how
|
|
it looks:
|
|
|
|
```java
|
|
/* ----------------------------------------------------------------------------
|
|
* This file was automatically generated by SWIG (http://www.swig.org).
|
|
* Version 3.0.5
|
|
*
|
|
* Do not make changes to this file unless you know what you are doing--modify
|
|
* the SWIG interface file instead.
|
|
* ----------------------------------------------------------------------------- */
|
|
|
|
|
|
public class Counter {
|
|
private long swigCPtr;
|
|
protected boolean swigCMemOwn;
|
|
|
|
protected Counter(long cPtr, boolean cMemoryOwn) {
|
|
swigCMemOwn = cMemoryOwn;
|
|
swigCPtr = cPtr;
|
|
}
|
|
|
|
protected static long getCPtr(Counter obj) {
|
|
return (obj == null) ? 0 : obj.swigCPtr;
|
|
}
|
|
|
|
protected void finalize() {
|
|
delete();
|
|
}
|
|
|
|
public synchronized void delete() {
|
|
if (swigCPtr != 0) {
|
|
if (swigCMemOwn) {
|
|
swigCMemOwn = false;
|
|
Counter_moduleJNI.delete_Counter(swigCPtr);
|
|
}
|
|
swigCPtr = 0;
|
|
}
|
|
}
|
|
|
|
public Counter(int initialValue) {
|
|
this(Counter_moduleJNI.new_Counter(initialValue), true);
|
|
}
|
|
|
|
public void increment() {
|
|
Counter_moduleJNI.Counter_increment(swigCPtr, this);
|
|
}
|
|
|
|
public int getValue() {
|
|
return Counter_moduleJNI.Counter_getValue(swigCPtr, this);
|
|
}
|
|
}
|
|
```
|
|
|
|
You can see two things here. Until the line 37, we have SWIG boilerplate. It
|
|
will handle memory management and JNI matching for us. Then, our custom
|
|
constructor and two methods. As you can see, none of the functional C++ code is
|
|
replicated here, neither are the attributes. Calls are simply mapped to the C++
|
|
methods.
|
|
|
|
We have seen we could use (unmodified!) C++ code from Java, replicating the same
|
|
interface, with a small glue. You can use it on desktop as on mobile. You only
|
|
need to be able to compile the native code for the target platform.
|
|
|
|
_Android: done._
|
|
|
|
## iOS
|
|
|
|
Using C++ code from an iOS application is far easier than Android, thanks to the
|
|
origins of Objective-C. Both Objective-C and C++ are C supersets. Objective-C++
|
|
has then been created to allow incorporating C++ code in an Objective-C program.
|
|
|
|
Objective-C++ is _not_ a strict superset of Objective-C, as C++ is not a strict
|
|
surperset of C. You can write valid C code which doesn't compile with a C++
|
|
compiler, and you can also write valid Objective-C code which doesn't compile
|
|
with an Objective-C++ compiler. But these are very specific cases.
|
|
|
|
Two steps are needed here. The easy one: add C++ source files to your project.
|
|
Xcode will build them as C++ code without doing anything more. Then, the
|
|
easy-but-not-as-easy one. You can't use C++ from Objective-C, but you can from
|
|
Objective-C++. So you need to convert your Objective-C to Objective-C++. There
|
|
are two ways to do it: add the `-Obj-C++` flag to each file needed, or rename
|
|
them to `YourFile.mm` instead of `YourFile.m`.
|
|
|
|
While this seems pretty simple, there's a catch: you can't include a header
|
|
containing C++ code in an Objective-C file. You have multiple possibilities
|
|
here:
|
|
|
|
- Use Objective-C++ everywhere. It's the most straightforward solution, but can
|
|
be complicated to implement.
|
|
- Use the [PIMPL idiom](http://en.wikipedia.org/wiki/Opaque_pointer). C++ will
|
|
not go outside your `.mm` files.
|
|
- And many other solutions. I'm not really into Objective-C and am probably
|
|
missing the best solution, have a look at
|
|
[this article](http://philjordan.eu/article/mixing-objective-c-c++-and-objective-c++)
|
|
for more ideas.
|
|
|
|
Using the same `Counter` class as earlier, here's how to call it from
|
|
Objective-C++:
|
|
|
|
{{ filename(body="ViewController.mm") }}
|
|
|
|
```objective-c
|
|
- (IBAction)click:(id)sender {
|
|
Counter* counter = new Counter(41);
|
|
counter->increment();
|
|
[_button setTitle:[NSString stringWithFormat:@"%d", counter->getValue()] forState:UIControlStateNormal];
|
|
delete counter;
|
|
}
|
|
```
|
|
|
|
_iOS: done._
|
|
|
|
## Windows Phone
|
|
|
|
I never wrote a Windows Phone application, so I won't go into details here. But
|
|
using C++ in a C#/XAML application is totaly doable, and pretty simply so. Just
|
|
write your C++ code, and build it as a native module. To use it from managed C#,
|
|
you'll have to add a reference to the native module in your managed one. Then,
|
|
instantiate your native component from managed code and use it. Tim Laverty made
|
|
a [great speach](http://channel9.msdn.com/Events/Build/2013/2-211) at Build
|
|
2013 to explain just that.
|
|
|
|
_Windows Phone: done._
|
|
|
|
## BlackBerry 10
|
|
|
|
_BlackBerry 10: done._
|
|
|
|
Yep. That's that simple. BlackBerry 10 uses C++ as its first-class language.
|
|
You can use your C++ classes as any other ones.
|
|
|
|
## Conclusion
|
|
|
|
Using C++ code without modification is doable on any of the 4 major mobile
|
|
platforms. However, it's not easy on all of them. Integrating it on Android
|
|
needs some work, and using Objective-C++ on iOS can be problematic.
|
|
|
|
Keep in mind that excepted for BlackBerry 10, you don't have access to any part
|
|
of the platform SDK when writing C++ code. You're on your own. And you have to
|
|
handle memory management.
|
|
|
|
While it can reduce the workload of a multi-platform application, the decision
|
|
to use C++ code must not be taken lightly.
|