2

The Point:

I want to call a C/C++ function from Java.

What I do:

  • I am able to generate a .h file using the "javah" program. It correctly spits out my header file. Perfect. I added two .c files that include this header file, where only one of which implements the function. The second one is a completely empty .c file due to a bug in the Android NDK build system. All fine and dandy so far.

  • I use the Android NDK "ndk-build" script to generate my .so files for multiple different platforms. Again, works perfectly.

  • I added these library files (in their respective folders) to the jniLibs folder in my Android Studio Project. Gradle sees them, and correctly adds them to my .apk file at build-time.

  • In my app source code, I use static { System.loadLibrary("testLib"); }. (Yes that's the correct filename). And it loads it successfully no issue.

The Problem:

As soon as I try to call the native function (defined in java and implemented in C++), I get the following exception:

java.lang.UnsatisfiedLinkError: Native method not found:    
com.example.nativeapp.MainActivity.testNativeMethod:()Ljava/lang/String;"

This is where things get weird :/: I compile successfully with NDK, build successfully with Gradle, and "loadLibrary" successfully with Java in my application code. But when simply calling the function "testNativeMethod();", that's when I get the runtime exception and the application Crashes.

Here is my "javah" generated header file:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_nativeapp_MainActivity */

#ifndef _Included_com_example_nativeapp_MainActivity
#define _Included_com_example_nativeapp_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_nativeapp_MainActivity_MODE_PRIVATE
#define com_example_nativeapp_MainActivity_MODE_PRIVATE 0L
#undef com_example_nativeapp_MainActivity_MODE_WORLD_READABLE
#define com_example_nativeapp_MainActivity_MODE_WORLD_READABLE 1L
#undef com_example_nativeapp_MainActivity_MODE_WORLD_WRITEABLE
#define com_example_nativeapp_MainActivity_MODE_WORLD_WRITEABLE 2L
#undef com_example_nativeapp_MainActivity_MODE_APPEND
#define com_example_nativeapp_MainActivity_MODE_APPEND 32768L
#undef com_example_nativeapp_MainActivity_MODE_MULTI_PROCESS
#define com_example_nativeapp_MainActivity_MODE_MULTI_PROCESS 4L
#undef com_example_nativeapp_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING
#define com_example_nativeapp_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING 8L
#undef com_example_nativeapp_MainActivity_BIND_AUTO_CREATE
#define com_example_nativeapp_MainActivity_BIND_AUTO_CREATE 1L
#undef com_example_nativeapp_MainActivity_BIND_DEBUG_UNBIND
#define com_example_nativeapp_MainActivity_BIND_DEBUG_UNBIND 2L
#undef com_example_nativeapp_MainActivity_BIND_NOT_FOREGROUND
#define com_example_nativeapp_MainActivity_BIND_NOT_FOREGROUND 4L
#undef com_example_nativeapp_MainActivity_BIND_ABOVE_CLIENT
#define com_example_nativeapp_MainActivity_BIND_ABOVE_CLIENT 8L
#undef com_example_nativeapp_MainActivity_BIND_ALLOW_OOM_MANAGEMENT
#define com_example_nativeapp_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef com_example_nativeapp_MainActivity_BIND_WAIVE_PRIORITY
#define com_example_nativeapp_MainActivity_BIND_WAIVE_PRIORITY 32L
#undef com_example_nativeapp_MainActivity_BIND_IMPORTANT
#define com_example_nativeapp_MainActivity_BIND_IMPORTANT 64L
#undef com_example_nativeapp_MainActivity_BIND_ADJUST_WITH_ACTIVITY
#define com_example_nativeapp_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L
#undef com_example_nativeapp_MainActivity_CONTEXT_INCLUDE_CODE
#define com_example_nativeapp_MainActivity_CONTEXT_INCLUDE_CODE 1L
#undef com_example_nativeapp_MainActivity_CONTEXT_IGNORE_SECURITY
#define com_example_nativeapp_MainActivity_CONTEXT_IGNORE_SECURITY 2L
#undef com_example_nativeapp_MainActivity_CONTEXT_RESTRICTED
#define com_example_nativeapp_MainActivity_CONTEXT_RESTRICTED 4L
#undef com_example_nativeapp_MainActivity_RESULT_CANCELED
#define com_example_nativeapp_MainActivity_RESULT_CANCELED 0L
#undef com_example_nativeapp_MainActivity_RESULT_OK
#define com_example_nativeapp_MainActivity_RESULT_OK -1L
#undef com_example_nativeapp_MainActivity_RESULT_FIRST_USER
#define com_example_nativeapp_MainActivity_RESULT_FIRST_USER 1L
#undef com_example_nativeapp_MainActivity_DEFAULT_KEYS_DISABLE
#define com_example_nativeapp_MainActivity_DEFAULT_KEYS_DISABLE 0L
#undef com_example_nativeapp_MainActivity_DEFAULT_KEYS_DIALER
#define com_example_nativeapp_MainActivity_DEFAULT_KEYS_DIALER 1L
#undef com_example_nativeapp_MainActivity_DEFAULT_KEYS_SHORTCUT
#define com_example_nativeapp_MainActivity_DEFAULT_KEYS_SHORTCUT 2L
#undef com_example_nativeapp_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL
#define com_example_nativeapp_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef com_example_nativeapp_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL
#define com_example_nativeapp_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L
/*
 * Class:     com_example_nativeapp_MainActivity
 * Method:    testNativeMethod
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_nativeapp_MainActivity_testNativeMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Here is my .c implementation file:

#include "com_example_nativeapp_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_example_nativeapp_MainActivity_testNativeMethod(JNIEnv* environment, jobject obj)
{
    return ( *env )->NewStringUTF(env, "HELLO FROM C Native!");
}

And last but not least, here is my .java Application file:

package com.example.nativeapp;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class MainActivity extends Activity
{

    static { System.loadLibrary("testLib"); }

    private TextView mDebugText;

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

        mDebugText = (TextView)findViewById(R.id.debug_text);

        // Here is where my native function call takes place. Without this, the app does (NOT) crash.
        mDebugText.setText(testNativeMethod());
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public native String testNativeMethod();
}

Thanks in advance. This is definitely one of those punch the monitor types of problems.

Just a quick heads up:

I've tried this: https://www.youtube.com/watch?v=okLKfxfbz40 three times in a row following the steps perfectly and still resulted in failure.

Also, I've been reading around here on Stackoverflow and none of the problems match my case. They are all using opencv etc, I just want to make my own "Hello World" Android NDK library. And once again, thanks in advance.

Edited to fix formating issues for readability.

JonasVautherin
  • 7,297
  • 6
  • 49
  • 95
pBlack
  • 102
  • 2
  • 10
  • `UnsatisfiedLinkError` clearly means that the function is not loaded. If you are certain that the shared library `libtestLib.so` is loaded at runtime, then it might be the case that the symbol is not in the library (i.e. the function has not been compiled in the library). Are you certain that you added the sources in your `Android.mk` file? You can look for `testNativeMethod` in your shared library by listing the symbols (have a look [here](http://stackoverflow.com/questions/1237575/how-do-i-find-out-what-all-symbols-are-exported-from-a-shared-object)). – JonasVautherin Jun 05 '14 at 05:43
  • I did run a readelf on the .so. In indeed, the function is not in the symbol table. So maybe the Android NDK compilation stage is not working as I thought it was? I'll look into this right now... – pBlack Jun 05 '14 at 17:21
  • Thanks to your comment, I was put on the right track, and posted a solution below. Just in case anyone else has this issue. – pBlack Jun 05 '14 at 19:20
  • That's how it works ;-). Validate your own answer as soon as possible then, to close the question. – JonasVautherin Jun 06 '14 at 05:57
  • Will do, 7 more hours from now it will allow me to do so. – pBlack Jun 06 '14 at 17:39

1 Answers1

0

Fixed it.

The Fix:

I changed the main.c to a main.cpp file so I can use "extern "C"". That way I knew I was invoking the C compiler correctly.

Here's my new main.c file (now called main.cpp)(compare with OP source above^^^^^):

extern "C"
{
    #include "com_example_nativeapp_MainActivity.h"

    JNIEXPORT jstring JNICALL Java_com_example_nativeapp_MainActivity_testNativeMethod(JNIEnv* env, jobject obj)
    {
        return env->NewStringUTF("HELLO FROM C Native!");
    }
}
pBlack
  • 102
  • 2
  • 10
  • What if you simply remove the `extern "C"` from your header, instead of changing to C++? – JonasVautherin Jun 06 '14 at 06:00
  • Without extern "C" it won't mangle the names correctly. C++ uses another scheme, while C has it's own. So I believe when the NDK was compiling this code w/o extern "C" (I.E. using C++ Compiler) it wasn't adding the symbols correctly for the linker due to it being interpreted as C++ function instead of normal C function. – pBlack Jun 06 '14 at 17:38