Can't link to method in native library - what's missing?

337 views
Skip to first unread message

Nigel McFarlane

unread,
Mar 26, 2018, 2:16:16 PM3/26/18
to android-ndk
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.  
I added the following to MainActivity:

    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());
}

The #include works, but now the compiler returns a link error:
Error:(12) undefined reference to 'nmcfMiscUseful::Round(double)'

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?






Alex Cohn

unread,
Mar 27, 2018, 12:44:45 PM3/27/18
to android-ndk
To build libnative-lib.so that depends on libnmcfMisc.so, you can add nmcfMisc to target_link_libraries(). The best way is to have two CMakeLists.txt files connected, e.g. the one for native-lib could include the one for nmcfMisc. Else, you must point to the prebuilt libnmcfMisc.so in the native-lib CMakeLists.txt, using an imported-target.

BR,
Alex

Nigel McFarlane

unread,
Apr 5, 2018, 12:25:24 PM4/5/18
to android-ndk
Thank you, Alex.  Adding the library as an imported target worked for me.  The hello-lib example at https://github.com/googlesamples/android-ndk is also very useful: it introduces the variable ${ANDROID_ABI} containing the path to the correct library flavour, and also shows how to use sourceSets{} to avoid copying the so libraries into the app distribution.  The relevant CMake section now looks like this:

 include_directories(${NMCFMISC_H})

add_library(lib_nmcfMisc SHARED IMPORTED)
set_property(TARGET lib_nmcfMisc PROPERTY IMPORTED_LOCATION ${NMCFMISC_SO}/${ANDROID_ABI}/libnmcfMisc.so)

target_link_libraries( 
                       native-lib
                       lib_nmcfMisc
                       ${log-lib} )
 
Reply all
Reply to author
Forward
0 new messages