Migrate from Bazel
This commit is contained in:
commit
016dbd0814
59 changed files with 7044 additions and 0 deletions
261
content/posts/share-code-across-multiple-platforms.md
Normal file
261
content/posts/share-code-across-multiple-platforms.md
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
+++
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue