I recently came across a new problem: cross-platform development
of libraries on iOS, Android and Linux Embedded devices. What I wanted
was a cross-platform way of writing a library letting me perform as
much non-GUI work as possible.
The only reasonable solution I could think of was developing the
library entirely in
C/C++ language. This can be quite long, but
simple to recompile for all these platforms. Linux Embedded should
commonly support entire C/C++ toolchains (like Codesourcery
toolchains), iOS supports standard C/C++ quite well and can be used
very simply from Objective-C using Objective-C++ and Android can make
use of C/C++ libraries through a JNI interface (not as much quick as
the others, but possible).
Ok, then C/C++ is my choice. Anyway, would it possible to increase my
productivity by using some kind of general purpose C/C++ library
instead of relying only on the standard library? There are many
possible solutions to this, one of which is the
Neptune library.
During the last months anyway, I've come to be very close to the
Qt libraries. I started to love the
completeness, the flexibility and the comfortable API Qt provides.
I've been looking with interest for months to the new development of
the Qt porting to
Android
(thanks to BogDan Vatra) and
iOS but
I never had the chance to try. Anyway, those ports make use of the QPA
build of Qt, which started with the Lighthouse project to provide Qt
portability on the GUI level to other platforms. This is not exactly
what I needed: I'm not interested at the moment on the GUI development.
What I need is simpler. Anyway, I found this very useful to start
working.
The environment I'm currently working with allows me to create a common
Qt Makefile to be parsed by qmake to generate a Makefile for any
platform I need: for Android the toolchain provided by the NDK and for
iOS the toolchain provided by Apple.
After this introduction, I explain the steps I followed to setup the
environment.
Setting up for Android development
First thing to do is to download the
Android NDK
from the official website. Uncompress it somewhere.
Download and compile the Android port of Qt: it might be good to
compile a specification file for the build, it is quite simple, but the
Android's toolchain is not a complete toolchain. This implies that some
modifications to the Qt sources are needed. I started to do it, but for
the moment simply downloading from the git the last sources from
Necessitas is good enough.
Now you have both the Android toolchain and the Qt sources compiled for
Android. You can select the new Qt platform and compile directly from
Qt Creator. This is damn good, cause it makes it quite simple to
implement the JNI interface also, which I've never found a way to write
comfortably from Eclipse.
Once the library is compiled, you can place it into an Android project
as instructed in the manual of the Android NDK and all is done. You
have C/C++ sources with Qt support ready to be used in an Android
project. You can also implement the JNI interface and include the
sources only when building for android using the qmake scopes.
Setting up for iOS development
It might seem a paradox, but I found it more difficult to compile for
iOS than for Android... What you can do is use the QPA specification
for iOS that can be found in
<Qt
sources>/mkspecs/qpa/macx-iphone*. By using qmake from the
command line you can create a Makefile for iOS and then compile it on a
Mac:
qmake -spec
qpa/macx-iphonesimulator-g++
you can also directly include Objective-C and Objective-C++ sources
directly into the Qt project file by using the variable:
OBJECTIVE_SOURCES += source_list
Just remember that
only
static libraries are allowed on iOS, so include something like:
ios
{
CONFIG += static
}
to compile statically when on iOS.
When I tried this for the first time it didn't work. It seems that for
some reason there is a difference between my XCode setup and the one
used as a reference for the platform specification. What I had to do is
to fix the qmake.conf specification for the iOS platform so that I
compiles correctly. I found
this
article very useful to see what needed to be changed.
In particular, for recent versions of Xcode, I had to change the
variable QMAKE_IOS_DEV_PATH (in
mkspecs/qpa/macx-iphonesimulator-g++/qmake.conf) from:
QMAKE_IOS_DEV_PATH =
/Developer/Platforms/iPhoneSimulator.platform/Developer
to:
QMAKE_IOS_DEV_PATH
=
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer
and the QMAKE_IOS_SDK_VERSION (in
mkspecs/qpa/common/g++-base-macx-iphone.conf) to 5.1.
Using Qt libraries on Android and iOS
There is one other point when implementing libraries using Qt: many Qt
features require an event loop to be running and that an instance of
QCoreApplication
to be instantiated somehow. To do this, it might be sufficient to run
the event loop and instantiate QCoreApplication in a thread of the
library: in the init function, just spawn a new thread and exec the
event loop.
This is an example of what I tried: first I created a Java class
like
this:
package com.lukeshq.android.test.qt;
/**
* Author Luca Carlon
* Date: 05.10.2012
*/
public class QtTest {
// Static initializer.
static {
System.loadLibrary("QtCore");
System.loadLibrary("AndroidTest");
}
public QtTest() {
new
Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
mySlot();
}
}
}).start();
}
// Native interface.
public native boolean
mySlot();
}
then, in my library, I created a simple object like this:
class MyQObject : public QObject
{
Q_OBJECT
public:
explicit
MyQObject(QObject* parent = 0);
public slots:
void mySlot();
};
and this is the implementation of the bindings:
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include <QString>
#include <QtConcurrentRun>
#include <QCoreApplication>
#include <QTimer>
#include <jni.h>
#include <android/log.h>
#include "myqobject.h"
/*----------------------------------------------------------------------
| declarations
+---------------------------------------------------------------------*/
MyQObject* myQObject;
/*----------------------------------------------------------------------
|
runQCoreApplication
+---------------------------------------------------------------------*/
void runQCoreApplication()
{
// Instantiate
QCoreApplication.
int i = 1;
char* c[1] = {"MyLib"};
QCoreApplication a(i,
c);
// Start the QTimer.
__android_log_print(ANDROID_LOG_INFO, "LibTag", "Starting
QTimer!");
QTimer* t = new
QTimer();
myQObject = new
MyQObject();
QObject::connect(t,
SIGNAL(timeout()), myQObject,
SLOT(mySlot()));
t->setInterval(1000);
t->setSingleShot(false);
t->start();
// Start the event
loop.
a.exec();
}
/*----------------------------------------------------------------------
| JNI_OnLoad
+---------------------------------------------------------------------*/
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void*
reserved)
{
Q_UNUSED(vm);
Q_UNUSED(reserved);
// Test QString.
QString myString =
QString("My QString!");
__android_log_print(ANDROID_LOG_INFO, "LibTag", "%s",
qPrintable(myString));
// Start the
QCoreApplication event loop.
QtConcurrent::run(&runQCoreApplication);
return JNI_VERSION_1_6;
}
/*----------------------------------------------------------------------
| JNI_OnUnload
+---------------------------------------------------------------------*/
extern "C" JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM* vm, void*
reserved)
{
Q_UNUSED(vm);
Q_UNUSED(reserved);
__android_log_print(ANDROID_LOG_INFO, "LibTag",
"Unloading!");
}
/*----------------------------------------------------------------------
|
Java_com_lukeshq_android_test_qt_QtTest_myslot
+---------------------------------------------------------------------*/
extern "C" JNIEXPORT void JNICALL
Java_com_lukeshq_android_test_qt_QtTest_mySlot(JNIEnv*
env, jobject
thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QMetaObject::invokeMethod(myQObject, "mySlot");
}