I have compiled static libcurl for android but continuously receiving the CurlRes code 6 i.e. CURLE_COULDNT_RESOLVE_HOST.
Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := Curl
LOCAL_SRC_FILES := prebuild/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ccSharedLib
LOCAL_SRC_FILES := main-jni.cpp
LOCAL_STATIC_LIBRARIES := Curl
include $(BUILD_SHARED_LIBRARY)
main-jni.cpp
extern "C" {
size_t write_data(void *ptr, size_t size, size_t count, FILE *stream)
{
size_t written;
written = fwrite(ptr, size, count, stream);
printf("data sent, size = %lu",written);
return written;
}
jint
Java_com_example_testlibcurl_MainActivity_test1( JNIEnv* env,
jobject thiz, jstring downloadDirectoryPath)
{
CURLcode res;
res = curl_global_init(CURL_GLOBAL_ALL);
jint temp = 3;
printf("Method called");
const char *nativeDownloadDirPath = env->GetStringUTFChars(downloadDirectoryPath,0);
// Test code for calling methods of libCURL
CURL *curl;
FILE *fp;
std::string s = "http://travel.paintedstork.com/blog/wp-content/uploads/2012/10/2013-calendar-images-1.jpg";
curl = curl_easy_init();
if(curl)
{
fp = fopen(nativeDownloadDirPath, "wb");
res = curl_easy_setopt(curl, CURLOPT_URL, s.c_str());
res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if(fp)
fclose(fp);
}
return res;
}
}
This code is downloading the image from a web source, but every time when "curl_easy_perform" method is called it gives the error code 6. I have checked this with different URL but still unsuccessful :( ...
"android.permission.INTERNET" and "android.permission.WRITE_EXTERNAL_STORAGE" permissions already given in Manifest file.
Any pointer to solve this will be a great help.
Make sure that you pass a valid file name and writable directory path to fopen.
I was getting
Fatal signal 11 (SIGSEGV) at 0x00000010 (code=1) ...
because fopen was trying to open a directory instead of a file.
Check the logs in LogCat from Eclipse or using adb logcat for other possible errors.
I've tested your code with the following modifications and it works.
MainActivity.java:
public class MainActivity
{
private static final String TAG = MainActivity.class.getName();
static
{
try
{
System.loadLibrary("mynativelib");
}
catch (UnsatisfiedLinkError ex)
{
Log.e(TAG, "WARNING: Could not load native library: " + ex.getMessage());
}
}
public static native int DownloadFile(String downloadDirectoryPath);
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int res = DownloadFile(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + Environment.DIRECTORY_DOWNLOADS + File.separator
+ "test.jpg");
Log.d(TAG, "Result Code: " + res);
}
}
main-jni.cpp
#include <jni.h>
#include <android/log.h>
#include <string>
#include <curl/curl.h>
#define LOG_TAG "native"
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOG_DEBUG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#ifdef __cplusplus
extern "C"
{
#endif
// [FIX for Android 4.2.x]
// "WARNING: Could not load native library:
// Cannot load library: soinfo_relocate(linker.cpp:975): cannot locate symbol "__exidx_end" referenced by"
// http://stackoverflow.com/a/14501998/313113
void __exidx_start()
{
}
void __exidx_end()
{
}
size_t write_data(void *ptr, size_t size, size_t count, FILE *stream)
{
size_t written;
written = fwrite(ptr, size, count, stream);
LOG_DEBUG("Writing data to file stream %u", written);
return written;
}
jint Java_com_company_awesomeapp_MainActivity_DownloadFile(JNIEnv* env, jobject thiz,
jstring downloadDirectoryPath)
{
CURLcode res;
res = curl_global_init(CURL_GLOBAL_ALL);
jint temp = 3;
LOG_DEBUG("Downloading file");
const char *nativeDownloadDirPath = env->GetStringUTFChars(downloadDirectoryPath, 0);
LOG_DEBUG(nativeDownloadDirPath);
CURL *curl;
FILE *fp;
std::string url = "http://travel.paintedstork.com/blog/wp-content/uploads/2012/10/2013-calendar-images-1.jpg";
curl = curl_easy_init();
if (curl)
{
fp = fopen(nativeDownloadDirPath, "wb");
res = curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
LOG_DEBUG("Before write function");
res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
LOG_DEBUG("After write function");
LOG_DEBUG("Before write data");
res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
LOG_DEBUG("After write data");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (fp) fclose(fp);
}
env->ReleaseStringUTFChars(downloadDirectoryPath, nativeDownloadDirPath);
return res;
}
/**
* The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary).
* JNI_OnLoad must return the JNI version needed by the native library.
*
* #see http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#JNI_OnLoad
*/JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
// [*] Get jclass with env->FindClass.
// [*] Register methods with env->RegisterNatives.
//jniRegisterNativeMethods(env, "dev/android/sample/AndroidNDKSampleActivity", sMethods, NELEM(sMethods));
return JNI_VERSION_1_6;
}
/**
* The VM calls JNI_OnUnload when the class loader containing the native library is garbage collected.
* This function can be used to perform cleanup operations.
*
* Because this function is called in an unknown context (such as from a finalizer),
* the programmer should be conservative on using Java VM services, and refrain from arbitrary
* Java call-backs.
* #see http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#JNI_OnUnload
*/JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved)
{
}
#ifdef __cplusplus
}
#endif
The file will be saved at: /storage/emulated/0/Download/test.jpg
This can also happen if you compile libcurl without threaded resolver (CMake option ENABLE_THREADED_RESOLVER) while targeting Android API level less than 23.
The problem is because implementation of CMake/CurlTests.c contains this line:
#ifndef gethostbyaddr_r
(void)gethostbyaddr_r;
#endif
However, if you check the netdb.h header in Android NDK, you will see
#if __ANDROID_API__ >= 23
int gethostbyaddr_r(const void* __addr, socklen_t __length, int __type, struct hostent* __ret, char* __buf, size_t __buf_size, struct hostent** __result, int* __h_errno_ptr) __INTRODUCED_IN(23);
#endif /* __ANDROID_API__ >= 23 */
This means that this function is not available when targeting Android older than Marshmallow. This is problematic, as testing for HAVE_GETHOSTBYNAME_R succeeds, while testing for HAVE_GETHOSTBYNAME_R_6 fails (but should succeed) with following error (observable in CMakeError.log):
/path/to/curl/CMake/CurlTests.c:128:9: error: use of undeclared identifier 'gethostbyaddr_r'
(void)gethostbyaddr_r;
^
1 error generated.
This then results with no-op behaviour in function Curl_ipv4_resolve_r in file hostip4.c, as it expects that after HAVE_GETHOSTBYNAME_R is defined, that at least one of HAVE_GETHOSTBYNAME_R_5, HAVE_GETHOSTBYNAME_R_6 or HAVE_GETHOSTBYNAME_R_3. Therefore, curl doesn't even try to resolve the host.
To fix this problem, simply remove those problematic lines from CMake/CurlTest.c.
I didn't try building curl with configure script, as in official documentation, but even if you do, it clearly states that you should target Android M or above if you plan on supporting SSL via OpenSSL, but AFAIK for simple HTTP support it should work.
The problem is gone if you use threaded resolver, as it uses different system API for performing the DNS resolution (getaddrinfo in function Curl_getaddrinfo_ex in curl_addrinfo.c). getaddrinfo is more modern API, but may not be available on all platform curl targets, such as embedded systems, which may also lack thread support. Therefore, curl uses the traditional approach in DNS resolution when threads are disabled.
P.S. Sorry for resurrecting this question after more than 7 years, but after facing the exact same problem as you, this StackOverflow question was the only similar Google search result 😛
Related
I have a.so which defines void a() and b.so which defines void b(). They are both put in the .apk so they are available to the Android application.
Now suppose that I'm calling a() through JNI. Is it possible to call b() from a() while completely bypassing JNI?
Can I do it this way in Android (the code is only for illustration, so it might have some errors)?
void a() {
void *handle = dlopen("b.so", RTLD_LAZY);
void (*b)() = dlsym(handle, "b");
b();
}
Would I need to add the fully qualified path, or is b.so already in LD_LIBRARY_PATH for the app?
You can do it this way on Android, though take care of where the shared library has been put in Android folders. It can change from a version to another.
On api 17 for example, it remains in /data/app-lib/. You can hardwrite it, but the best is to make calls to Java (through JNI) to know where the libraries should be.
We're doing something like this in our project :
JNIEnv* env;
const char* temp;
jobject oActivity = state->activity->clazz;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
strcpy(libpath, temp);
strcat(libpath, "/");
Then you push your dlopen + dlsym combo in the fight and it should work.
As mentioned here : How do I load a shared object in C++?
There are two ways of loading shared objects in C++
For either of these methods you would always need the header file for the object you want to use. The header will contain the definitions of the classes or objects you want to use in your code.
#include "blah.h"
int main()
{
ClassFromBlah a;
a.DoSomething();
}
gcc yourfile.cpp -lblah
Dynamically (In Linux):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("libm.so", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
exit(1);
}
dlerror(); /* Clear any existing error */
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
exit(1);
}
printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}
PS : for the dynamic approach, it depends on platform : on Linux, you use dlopen, on windows, you use LoadLibrary.
Device: Google Nexus 7 (2013).
logcat contains
I/DEBUG ( 176): Abort message: '### ABORTING: invalid address or address of corrupt block 0x5c6b0 passed to dlfree'.
I think there is a problem with static libgnustl. Am I right? How can I force to use ibgnustl_shared using GNU Build System? I uses android-ndk-r9b installed into $HOME/android-ndk-r9b. Firstly I made standalone toolchain
$HOME/android-ndk-r9b/build/tools/make-standalone-toolchain.sh --platform=android-9 --install-dir=$HOME/android-toolchain
I wrote simple test that contains shared library and console program that uses it. It crashes on device.
// libtest.h
#ifndef TEST_H
#define TEST_H
#include <stddef.h>
#include <string>
namespace test
{
enum { MSG_LEN_BYTES = 3, MSG_LEN_MAX = 0xFFF };
int encodeMsgLength(std::string &encoded_length, size_t length);
}
#endif
// libtest.cpp
#include "libtest.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
int test::encodeMsgLength(std::string &encoded_length, size_t length)
{
encoded_length.clear();
if (length > MSG_LEN_MAX) {
return E2BIG;
}
char buf[MSG_LEN_BYTES + 1] = {0};
int cnt = snprintf(buf, sizeof(buf), "%03zX", length);
if (cnt == MSG_LEN_BYTES && static_cast<size_t>(cnt) < sizeof(buf)) {
encoded_length.assign(buf, cnt);
return 0;
}
assert(!"encodeMsgLength");
return EBADF;
}
// test.cpp
#include <iostream>
#include <stdlib.h>
#include "libtest.h"
int main(int argc, char* argv[])
{
if (argc != 2) {
std::cout << "Usage: test <number>\n";
return 1;
}
size_t len = atoi(argv[1]);
std::cout << "encodeMsgLength(" << len << ") => ";
std::string str;
int err = test::encodeMsgLength(str, len);
std::cout << str << ", error " << err << std::endl;
return 0;
}
Makefile.am
lib_LTLIBRARIES = libtest.la
libtest_la_LDFLAGS = -shared -avoid-version
libtest_la_SOURCES = libtest.cpp
bin_PROGRAMS = test
test_LDADD = libtest.la
test_SOURCES = test.cpp
Build your app using the default : ndk-build --TARGET_PLATFORM android-9
To make sure that only shared library is taken edit your Android.mk and put appropriate library like LOCAL_SHARED_LIBS := libgnustl
There are various other tools that you can use to debug your issue:
1. gdb [search for remote debugging android native app]
2. strace [ if not directly available use busybox strace ]
3. valgrind
Also in many cases the memory stack during crash are stores in /data/tombstones. Locate your errror file, pull it and show the details here of the same.
Instead of linking to libgnustl-static.a, use libgnustl-shared.so. Your observation makes sense: when both libtest.so and test are linked with the static version, then the std::string class will have two different vtables, and the program may crash on shutdown. libstlport uses internally system stl (always shared) for new/delete, and thus behaves better in your test case. This, naturally, does not protect from other clashes, which may also be lethal. That's why you should use shared version of stl if you use it in two or more dynamically linked components.
In CPLUSPLUS-SUPPORT.html
"II.3. Static runtimes:
Please keep in mind that the static library variant of a given C++ runtime SHALL ONLY BE LINKED INTO A SINGLE BINARY for optimal conditions."
I had an issue with this where malloc was crashing.
I'm using Eclipse ADT with: CDT. Along with NDK interfacing with JNI, to c/c++, compiling c/c++ with Cygwin through Eclipse. Everything should be running the latest versions as this was just setup over the last two weeks.
When Building I get the following.
jni/testSocketClass.hpp:33:1: error: unknown type name 'class'
jni/testSocketClass.hpp:33:17: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
jni/ndkfoo.c:13:1: error: unknown type name 'mYNewClass'
JNI C file
#include <string.h>
#include <jni.h>
#include "testSocketClassWrapper.hpp"
void *m_GBLpmyCSocket;
ndkfoo.c
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object){
m_GBLpmyCSocket = MyClass_create();
MyClass_sendCommandToSerialDevice(m_GBLpmyCSocket, 0, 0, 0);
return (*env)->NewStringUTF(env, "started");
}
Class Wrapper .hpp
//file testSocketClassWrapper.hpp
#ifndef _MY_SOCKETCLASS_WRAPPER_H
#define _MY_SOCKETCLASS_WRAPPER_H
#include"testSocketClass.hpp"//<<<<<<<<<<<<<<<<<<<<<Wrong inclusion
#ifdef __cplusplus
extern "C" void* MyClass_create() {
return new mYNewClass;
}
extern "C" void MyClass_release(void* myclass) {
delete static_cast<mYNewClass*>(myclass);
}
extern "C" void MyClass_sendCommandToSerialDevice(void* myclass, int cmd, int params, int id) {
static_cast<mYNewClass*>(myclass)->sendCommandToSerialDevice(cmd,params,id);
}
#endif
#endif /* _MY_SOCKETCLASS_WRAPPER_H_INCLUDED */
Class wrapper .cpp
//file testSocketClassWrapper.cpp
#include "testSocketClassWrapper.hpp"
Class .h
// file testSocketClass.hpp
class mYNewClass{///////////////////////ERROR HERE////////////////////////////////
//public:
void sendCommandToSerialDevice(int Command, int Parameters, int DeviceID);
//int sockfd;
};
Class .cpp
// file testSocketClass.cpp
#include "testSocketClass.hpp"
void mYNewClass::sendCommandToSerialDevice(int Command, int Parameters, int DeviceID){
char testc[100];
sprintf(testc, "%d, %d, %d", Command, Parameters, DeviceID);
}
I've read countless questions on this topic and have had quite a few issues getting this far with Eclipse configurations too. But from what I've pulled together from other questions on this topic, I have hit a wall and do not know how to proceed at this point. Suggestions?
EDIT BELOW - ANSWER
After review with a colleague(sometimes a second pair of eyes helps bring things out), we located my error. See the line labeled improper inclusion in the Class wrapper .hpp. That header should be relocated as follows in the Class wrapper .cpp NOTE: the functions where also moved to the .cpp and the .hpp is now empty.
//file testSocketClassWrapper.cpp
#include "testSocketClassWrapper.hpp"
#include "testSocketClass.hpp"
extern "C" void* MyClass_create() {
return new mYNewClass;
}
extern "C" void MyClass_release(void* myclass) {
delete static_cast<mYNewClass*>(myclass);
}
extern "C" void MyClass_sendCommandToSerialDevice(void* myclass, int cmd, int params, int id) {
static_cast<mYNewClass*>(myclass)->sendCommandToSerialDevice(cmd,params,id);
}
Also for completeness as this has been no walk in the park, are the MK files.
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Here we give our module name and source file(s)
LOCAL_MODULE := ndkfoo
LOCAL_SRC_FILES := ndkfoo.c testSocketClassWrapper.cpp testSocketClass.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_STL:=stlport_static
Thank you for the prompt reply krsteeve. And yet another way to do this. Will keep it in mind.
testSockedClass.hpp is being included from a .c file, so it's being compiled with a C-compiler. class doesn't exist in C. Change ndkfoo.c to a .cpp file.
You will have to format things a bit differently in C++:
extern "C"
{
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object);
}
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object){
m_GBLpmyCSocket = MyClass_create();
MyClass_sendCommandToSerialDevice(m_GBLpmyCSocket, 0, 0, 0);
return env->NewStringUTF("started");
}
Note the form of the NewStringUTF call in C++ vs C.
i am using NDK to join java with C++ this my code :
MainActivity.java
public class MainActivity extends Activity {
/*Don't forget to load the library!!*/
static {
System.loadLibrary("NDK1");
}
public native String exec(String cmd);
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Here we give our module name and source file(s)
LOCAL_MODULE := NDK1
LOCAL_SRC_FILES := NDK1.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := armeabi armeabi-v7a
APP_STL := gnustl_static
NDK1.cpp
#include <string>
#include <iostream>
#include <stdio.h>
#include <jni.h>
std::string exec(char* cmd) {
FILE* pipe = popen(cmd, "r");
if (!pipe) return "ERROR";
char buffer[128];
std::string result = "";
while(!feof(pipe)) {
if(fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
pclose(pipe);
return result;
}
jstring Java_com_example_ndk1_MainActivity_exec(JNIEnv* env, jobject javaThis , jstring cmd) {
char * res;
res = env->GetStringUTFChars(cmd ) ;
std::string result = exec(res);
return env->NewStringUTF(result);
}
but have this error !!!
$ /cygdrive/c/android-ndk-r4/ndk-build
make: Warning: File `/cygdrive/c/android-ndk-r4/build/core/import-locals.mk' has modification time 140680622 s in the future
Compile++ thumb : NDK1 <= NDK1.cpp
jni/NDK1.cpp: In function '_jstring* Java_com_example_ndk1_MainActivity_exec(JNIEnv*, jobject, jstring)':
jni/NDK1.cpp:22:39: error: no matching function for call to '_JNIEnv::GetStringUTFChars(_jstring*&)'
jni/NDK1.cpp:22:39: note: candidate is:
C:/android-ndk-r4/platforms/android-14/arch-arm/usr/include/jni.h:860:17: note: char const* _JNIEnv::GetStringUTFChars(jstring, jboolean*)
C:/android-ndk-r4/platforms/android-14/arch-arm/usr/include/jni.h:860:17: note: candidate expects 2 arguments, 1 provided
jni/NDK1.cpp:25:36: error: no matching function for call to '_JNIEnv::NewStringUTF(std::string&)'
jni/NDK1.cpp:25:36: note: candidate is:
C:/android-ndk-r4/platforms/android-14/arch-arm/usr/include/jni.h:854:13: note: _jstring* _JNIEnv::NewStringUTF(char const*)
C:/android-ndk-r4/platforms/android-14/arch-arm/usr/include/jni.h:854:13: note: no known conversion for argument 1 from 'std::string {aka std::basic_string<char>}' to 'char const*'
/cygdrive/c/android-ndk-r4/build/core/build-binary.mk:269: recipe for target `obj/local/armeabi/objs/NDK1/NDK1.o' failed
make: *** [obj/local/armeabi/objs/NDK1/NDK1.o] Error 1
after many tries to catch the problem this is the solution without causing any error :
jstring Java_com_example_ndk1_MainActivity_exec(JNIEnv* env, jobject javaThis , jstring cmd) {
const char * res;
jboolean isCopy;
res = env->GetStringUTFChars(cmd, &isCopy);
if (isCopy == JNI_TRUE) {
(env)->ReleaseStringUTFChars(cmd, res);
}
std::string result = exec(res);
return (env)->NewStringUTF((const char* )result.c_str());
}
[edited to make the answer more generic - there are several errors in the code]
For every error of the type no matching function for call to, look up the definition of the function in the JNI reference
Make sure that you are:
Passing all required parameters
The parameters are of the required type
For example, the call to GetStringUTFChars is missing parameters and the call to NewStringUTF doesn't take a std::string as parameter. It wants a const char *.
For the NewStringUTF, try:
return (*env)->NewStringUTF(env, (const char*)result.c_str());
This JNI tutorial is also nice: http://www.steveolyo.com/JNI/JNI.html
I'm new to jni, and I was going over a tutorial to implement a simple native method, but I'm getting an unsatisfiedlinkerror. As far as I know, I followed the steps in the tutorial exactly. Please help me.
Here is the java wrapper code:
package com.cookbook.jni;
public class SquaredWrapper {
// Declare native method (and make it public to expose it directly)
public static native int squared(int base);
// Provide additional functionality, that "extends" the native method
public static int to4(int base)
{
int sq = squared(base);
return squared(sq);
}
// Load library
static {
System.loadLibrary("squared");
}
}
Here's what my Android.mk file looks like:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := squared
LOCAL_SRC_FILES := squared.c
include $(BUILD_SHARED_LIBRARY)
Here's what my .c file looks like:
#include "squared.h"
#include <jni.h>
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
(JNIEnv * je, jclass jc, jint base)
{
return (base*base);
}
And here is what my .h file looks like:
enter code here/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_cookbook_jni_SquaredWrapper */
#ifndef _Included_com_cookbook_jni_SquaredWrapper
#define _Included_com_cookbook_jni_SquaredWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_cookbook_jni_SquaredWrapper
* Method: squared
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Your JNI signature doesn't match. In your .c file, change:
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
to
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
Generally there are two ways to "glue" native C through JNI to a Java function. The first is what you're attempting to do here, that is use a predetermined signature that JNI will recognize and associate with your appropriate Java code. The second is to pass function pointers, signatures, and Java class names into JNI when you include the library.
Here's the second method that would bind the native function to the appropriate Java code (this would be your .c file):
#include "squared.h"
#include <jni.h>
static const char* SquaredWrapper = "com/cookbook/jni/SquaredWrapper";
jint squared(JNIEnv * env, jobject this, jint base) {
return (base*base);
}
// Methods to register for SquaredWrapper
static JNINativeMethod SquareWrapperMethods[] = {
{"squared", "(I)I", squared}
};
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ( (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->RegisterNatives(env, class, SquaredWrapperMethods, sizeof(SquaredWrapperMethods)/sizeof(SquaredWrapperMethods[0]));
return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
return;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->UnregisterNatives(env, class);
return;
}
It's a good deal longer but it gives you a lot more flexibility when binding native code. The definition for squared and the includes are as you would expect. on the 4th line, the static const char* SquaredWrapper is an escaped string with the fully qualified package name of the class you want to bind squared to. Near the bottom are the JNI_OnLoad and JNI_OnUnLoad functions that take care of binding and unbinding the functions on library load and unload. The last piece is the JNINativeMethod array. This array contains as each entry an array of size 3 whose components are the Java name of the method as a const char*, the JNI signature of the Java method, and the native C function pointer to bind to that method. The JNI function signature tells the environment the argument list format and return value of the Java function. The format is "(Arg1Arg2Arg3...)Ret", so a function that takes an int and double and returns a float would have a signature of "(ID)F", and a function that takes no arguments and returns void would be "()V". I use this handy cheat sheet to remember most of the shorthand:
http://dev.kanngard.net/Permalinks/ID_20050509144235.html
Good luck :)
Edit: Oh, BTW, you'll likely want to add the signatures for JNI_OnLoad and JNI_UnOnLoad to your header, and change the name of your native function prototype to reflect the new .c file.
This is kind of an obscure case, but if you get an access violation in your native code, Android will cover up the thrown exception and throw the error you got. In my case, the native code threw an access violation but Java kept running. It then tried to call a JNI method on the crashed NDK.
To find the access violation, I ended up moving the offending JNI method to another IDE to debug.
I hope this saves someone the amount of time it took me to figure this out.