The Importance of being Namespaced

Hopefully some interesting thoughts on C++ coding and programming in general.

View on GitHub

Connecting C++ code to Android with Djinni

This video and the related rep proved usefull mainly because he kept parts separate so the configuration is easier to follow.

Djinni

First in the clone of the Djinni rep create a directory at the same level as their example.

Then in that directory create as script run_djinni.sh

#!/bin/bash

rm -r generated/*

../src/run \
    --java-out generated/java/com/example/djinni \
    --java-package com.example.djinni \
    \
    --cpp-out generated/cpp/ \
    --cpp-optional-template std::experimental::optional \
    --cpp-optional-header "<experimental/optional>" \
    \
    --jni-out generated/jni/ \
    --ident-jni-class JNIFooBar \
    --ident-jni-file jni_foo_bar \
    \
    --objc-out generated/objc/ \
    --objcpp-out generated/objcpp/ \
    --objc-type-prefix SJ \
    \
    --idl idl/example.djinni

Create a sub-directory idl and a definition file idl/example.djinni

example_service = interface +c {
    static create_service(display: example_display): example_service;
    # Call this in bg thread
    run(who: list<string>);
}

example_display = interface +j +o {
    display(example_record: example_record);
}

example_record = record {
    message: string;
    color: example_color;
}

example_color = enum {
    RED;
    BLUE;
}

Run the script to generate the djinni code in the generated sub-directory.

Android Studio

Create a C++ project supporting exceptions and RTTI

In the app/src directory create a new directory djinni.

In the djinni directory create symbolic links to the generated and idl directory in the djinni project. Also create a symbolic link to the support_libs directory in the djinni rep.

Add the following to the app/build.gradle file

    sourceSets {
        main.java.srcDirs += 'src/djinni/generated/java'
    }

so it looks something like this

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.project"
        minSdkVersion 23
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/djinni/generated/java'
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

Replace the app/CMakeList.txt file with this

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Path to the djinni support code
set(support_dir src/djinni/support-lib/jni)
# Path to the generated code and our own c++ implementations
set(include_dirs src/djinni/generated/cpp/ src/djinni/generated/jni/ src/main/cpp/)

# Djinni support code that needs to be compiled
file(
  GLOB_RECURSE support_srcs
  ${support_dir}/*.cpp)
# Generated code and c++ implementations that need to be compiled
file(
  GLOB_RECURSE lib_srcs
  src/djinni/generated/cpp/*.cpp
  src/djinni/generated/jni/*.cpp
  src/main/cpp/*.cpp)

# All the implementation files that make up our library
set(complete_srcs ${support_srcs} ${lib_srcs})

# Define library referring to the sources above
add_library(native-lib SHARED ${complete_srcs})

# Define INCLUDE_DIRECTORIES property for native-lib
target_include_directories(
  native-lib
  PUBLIC
  ${include_dirs}
  ${support_dir})

and if everything is correct it should compile to produce a library file (*.so) in the app/build/intermediates/cmake/debug/obj/armeabi-v7a/ directory.

Java level code can then be written to use the library similar to this

package com.example.project.name;

import android.graphics.Color;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;

import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;

import com.example.djinni.Greeting;
import com.example.djinni.GreetingColor;
import com.example.djinni.GreetingDisplay;
import com.example.djinni.GreetingService;

public class MainActivity extends AppCompatActivity {
    ExampleService exampleService;
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        final TextView tv = (TextView) findViewById(R.id.sample_text);
        // Example of a call to a native method
        exampleService = ExampleService.createService(new ExampleDisplay() {
            @Override
            public void display(Example example) {
                MainActivity.this.display(example, tv);
            }
        });
        new Thread(new Runnable() {
            public void run() {
                exampleService.run(new ArrayList<>(Arrays.asList("World", "Devoxx", "Djinni", "Android")));
            }
        }).start();
    }

    private void display(final Example example, final TextView textView) {
        runOnUiThread(new Runnable() {
            public void run() {
                String message = example.getMessage();
                textView.setText(message);
                ExampleColor color = example.getColor();
                switch (color) {
                    case RED:
                        textView.setTextColor(Color.RED);
                        break;
                    case BLUE:
                        textView.setTextColor(Color.BLUE);
                        break;
                }
                fadeOut(textView, ExampleService.COOLDOWN_MS / 2, ExampleService.COOLDOWN_MS / 4);
            }
        });
    }

    private void fadeOut(final TextView textView, long delay, long duration) {
        textView.setAlpha(1);
        AlphaAnimation animation = new AlphaAnimation(1, 0);
        animation.setStartOffset(delay);
        animation.setDuration(duration);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationEnd(Animation animation) {
                textView.setAlpha(0);
            }

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        textView.startAnimation(animation);

    }
}