Boost + Android? CrystaX NDK!
01/20/2015 02:40 PM

Do you have code that uses Boost C++ libraries? Do you want to easily port this code to Android? Or maybe you just want start a new Android-targeted project and would like to use Boost from the beginning? With CrystaX NDK 10.1.0, you get both an Android native development kit and Boost C++ libraries out of the box!

Lets see how to do that.

First off, I assume you're using an NDK build system with its Application.mk and Android.mk files. Here is the initial layout of the simplest project, containing just the native part and producing a native executable:

.
└── jni
    ├── Android.mk
    ├── Application.mk
    └── test.cpp

Initial files

Here is the initial content of those files:

Application.mk
# Application.mk
APP_ABI := all
Android.mk
# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := test-boost
LOCAL_SRC_FILES := test.cpp
include $(BUILD_EXECUTABLE)
test.cpp
// test.cpp
#include <iostream>

int main()
{
    return 0;
}

Adding Boost

Now, having this initial skeleton, lets grow it to a simple Boost-enabled project. We'll use the Boost Serialization library in this example; however, exactly the same approach works with any other Boost library. The only change required is the name of the Boost library in Android.mk.

First, we add new source files containing the Boost serialization code (we've used the official Boost example here):

gps.hpp
// gps.hpp
#ifndef GPS_HPP_7D5AF29629F64210BE00F3AF697BA650
#define GPS_HPP_7D5AF29629F64210BE00F3AF697BA650

// include headers that implement a archive in simple text format
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

/////////////////////////////////////////////////////////////
// gps coordinate
//
// illustrates serialization for a simple type
//
class gps_position
{
private:
    friend class boost::serialization::access;
    friend std::ostream &operator<<(std::ostream &, gps_position const &);
    // When the class Archive corresponds to an output archive, the
    // & operator is defined similar to <<.  Likewise, when the class Archive
    // is a type of input archive the & operator is defined similar to >>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position(){}
    gps_position(int d, int m, float s) :
        degrees(d), minutes(m), seconds(s)
    {}

    bool operator==(gps_position const &g) const
    {
        return degrees == g.degrees &&
            minutes == g.minutes &&
            seconds == g.seconds;
    }

    bool operator!=(gps_position const &g) const
    {
        return !(*this == g);
    }
};

void save(gps_position const &g);
void load(gps_position &g);

#endif // GPS_HPP_7D5AF29629F64210BE00F3AF697BA650
gps.cpp
// gps.cpp
#include <fstream>

#include "gps.hpp"

const char *FILENAME = "gps.dat";

std::ostream &operator<<(std::ostream &s, gps_position const &g)
{
    s << "GPS(" << g.degrees << "/" << g.minutes << "/" << g.seconds << ")";
    return s;
}

void save(gps_position const &g)
{
    // create and open a character archive for output
    std::ofstream ofs(FILENAME);
    boost::archive::text_oarchive oa(ofs);
    // write class instance to archive
    oa << g;
    // archive and stream closed when destructors are called
}

void load(gps_position &g)
{
    // create and open an archive for input
    std::ifstream ifs(FILENAME);
    boost::archive::text_iarchive ia(ifs);
    // read class state from archive
    ia >> g;
    // archive and stream closed when destructors are called
}

Second, we modify our test.cpp in order to do real work with the help of functions defined in gps.hpp:

test.cpp
// test.cpp
#include <iostream>
#include <iomanip>
#include "gps.hpp"

int main()
{
    // create class instance
    const gps_position g(35, 59, 24.567f);
    std::cout << "Initial value: " << g << std::endl;
    save(g);

    // ... some time later restore the class instance to its orginal state
    gps_position newg;
    load(newg);
    std::cout << "After load: " << newg << std::endl;

    if (g != newg)
    {
        std::cerr << "ERROR: Loaded object differs from the saved one" << std::endl;
        return 1;
    }

    std::cout << "Congratulations! GPS object was successfully saved and then loaded" << std::endl;
    return 0;
}

Finally, we modify our Android.mk to include those source files and enable Boost Serialization:

Android.mk
# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE           := test-boost
LOCAL_SRC_FILES        := test.cpp gps.cpp
LOCAL_STATIC_LIBRARIES := boost_serialization_static
# Or, if you need to link with Boost Serialization library dynamically:
#LOCAL_SHARED_LIBRARIES := boost_serialization_shared
include $(BUILD_EXECUTABLE)

$(call import-module,boost/1.57.0)

And now the file tree of our project looks like the following:

.
└── jni
    ├── Android.mk
    ├── Application.mk
    ├── gps.cpp
    ├── gps.hpp
    └── test.cpp

Final result

Ok, we got it! Lets check how it works. Please note we've used the APP_ABI parameter for ndk-build; this was done just to limit output by armeabi-v7a-related lines only and to avoid cluttering it up with insignificant information. However, it works for all other ABIs as well as for armeabi-v7a.

$ ndk-build APP_ABI=armeabi-v7a
[armeabi-v7a] Compile++ thumb: test-boost <= test.cpp
[armeabi-v7a] Compile++ thumb: test-boost <= gps.cpp
[armeabi-v7a] Prebuilt       : libgnustl_shared.so <= <NDK>/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/thumb/
[armeabi-v7a] Executable     : test-boost
[armeabi-v7a] Prebuilt       : libboost_atomic.so <= <NDK>/sources/boost/1.57.0/libs/armeabi-v7a/
[armeabi-v7a] Install        : libcrystax.so => libs/armeabi-v7a/libcrystax.so
[armeabi-v7a] Install        : test-boost => libs/armeabi-v7a/test-boost
[armeabi-v7a] Install        : libgnustl_shared.so => libs/armeabi-v7a/libgnustl_shared.so

$ adb push libs/armeabi-v7a/libcrystax.so /data/local/tmp
1231 KB/s (537036 bytes in 0.425s)

$ adb push libs/armeabi-v7a/libgnustl_shared.so /data/local/tmp
1234 KB/s (718448 bytes in 0.568s)

$ adb push libs/armeabi-v7a/test-boost /data/local/tmp
840 KB/s (50536 bytes in 0.058s)

$ adb shell 'cd /data/local/tmp && LD_LIBRARY_PATH=/data/local/tmp ./test-boost'
Initial value: GPS(35/59/24.567)
After load: GPS(35/59/24.567)
Congratulations! GPS object was successfully saved and then loaded

As you can see, it just works, without any altering of code - i.e., the code is exactly the same as for other desktop and mobile platforms. Moreover, it works the same on all supported Android versions - i.e., on all versions starting from 2.3 (API level 9).

Impressed? Check out CrystaX NDK and try it right now!

Back
Home
Map
Back
Home
Map

Our contributors: