I'm trying to execute a method from a native C++ library but can't get the link step to work. Please could someone point me to whatever vital step I'm missing in the process?
My OS is Windows 10. I'm using Android Studio 3.0.1, Gradle 4.1 and NDK 16.1. I should also note that I currently have very little experience with these tools.
The ultimate task is to demonstrate to a client that a C++ library we wrote on top of OpenCV will indeed compile on Android. For now, however, I'm using a simple standalone C++ library, nmcfMisc, written by myself, as an example. There are no macro annotations or externs in the code. The method in question, nmcfMiscUseful::Round(), is trivial and looks this this:
// nmcfMiscUseful.h:
#ifndef __nmcfMiscUseful_H__
#define __nmcfMiscUseful_H__
#include <string>
#include <vector>
#include <ostream>
#include <cmath>
//------------------------------------------------------------------------------
/// Useful misc maths and other methods.
//------------------------------------------------------------------------------
namespace nmcfMiscUseful {
/// Round function which works with negative and positive numbers
int Round(double x);
// nmcfMiscUseful.cpp:
namespace nmcfMiscUseful {
//------------------------------------------------------------------------------
// Round function which works with negative and positive numbers
//------------------------------------------------------------------------------
int Round(double x) {
if (x >= 0.0)
return (int) (x + 0.5);
else
return (int) (x - 0.5);
}
I would normally compile this for Visual Studio using the following CMakeLists.txt:
PROJECT (nmcfMisc)
# makefile to create standalone library.
# set minimum cmake version
cmake_minimum_required(VERSION 3.4)
# Options
Option(BUILD_SHARED "Build shared library" OFF)
# set top level paths
# find all .cpp and .h files and add to project
file (GLOB CPPFILES *.cpp)
file (GLOB CXXFILES *.cxx)
file (GLOB HFILES *.h)
set (SOURCEFILES ${CPPFILES} ${CXXFILES} ${HFILES})
# create library project
if (${BUILD_SHARED})
set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
add_library (${PROJECT_NAME} SHARED ${SOURCEFILES})
else()
add_library (${PROJECT_NAME} STATIC ${SOURCEFILES})
endif()
To create the library I compiled this in Android Studio using the default "C++ support" project. The only changes I made to the project were in the default gradle file, replacing the default CMake file with the one above and adding a -D argument to switch to the shared library build. build.gradle looks like this:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "uk.ac.beds.nmcfmisc"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
arguments "-DBUILD_SHARED=ON"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path 'C:/Users/nmcfarlane/SoftwareProjects/VisualStudio/MyLibraries/nmcfMisc/CMakeLists.txt'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
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'
}
The library compiles with no errors and produces libnmcfMisc.so. Actually it produces 4 flavours of the library and repeats them in different build locations. It might be significant that the build/intermediates/jniLibs directory is empty.
To use the library I created another default C++ support project called MyCppApp. I created the folder app/src/main/jniLibs and copied the four flavour directories into it from app/build/intermeidates/cmake/debug/obj.
static {
try {
System.loadLibrary("native-lib");
System.loadLibrary("nmcfMisc");
}
catch (UnsatisfiedLinkError ule) {
android.util.Log.e("LoadJniLib", "ERROR: Could not load native library: " + ule.getMessage());
}
}
The program runs and appears to find the library.
I added the include path to the default CMakeLists and added the method to native-lib.cpp:
#include <jni.h>
#include <string>
#include "nmcfMiscUseful.h"
extern "C"
JNIEXPORT jstring JNICALL
Java_uk_ac_beds_mycppapp_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
int x = nmcfMiscUseful::Round(17.9);
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
I've tried adding the windows nmcfMisc.lib to the CMake target_link_libraries() (which is what I would do if I were compiling native-lib for Visual Studio) but it makes no difference. I also tried adding the .so library to target_link_libraries() but this crashes the build.
How do I complete the link step? Do I need to prepare the C++ code for the native build? Did I build the .so library correctly, and did I choose the correct .so outputs to copy to jniLibs? Do I need to write an NDK makefile to complete the linkage? Or is there something else I've missed?