enoent

Share code across multiple platforms

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 or 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.

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, 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.

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:

counter.hpp
1
2
3
4
5
6
7
8
9
10
class Counter {
public:
    Counter(int initialValue);

    void increment();
    int getValue() const;

private:
    int _value;
};
counter.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "counter.hpp"

Counter::Counter(int initialValue) :
  _value(initialValue) {
}

void Counter::increment() {
  ++_value;
}

int Counter::getValue() const {
  return _value;
}
counter.i
1
2
3
4
5
6
7
%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:
Counter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* ----------------------------------------------------------------------------
 * 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. 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 for more ideas.

Using the same Counter class as earlier, here’s how to call it from Objective-C++:

ViewController.mm
1
2
3
4
5
6
- (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 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.

Comments