继续研究 NDK
jni.h provides a thin C++ wrapper around the C API; the underlying calls are language-agnostic.基础调用是语言无关的。
wiki:
readelf 查看 ELF 文件格式:
ELF 文件格式
ProgramHeaders:
ELF 函数头
ABI (application binary interface ) :
来自 < http://baike.baidu.com/link?url=49-mYyGuqI687pINMMP-e2dU-vYp2wcQYdr_rhnoydZy881DUdRVgcVz2AM3cr5e5zCh58dbhV46jIkWwvW5AJPnFo0JvqMuqI2TJtGmd73 >
Jni:ndk-build hello-jni.c
android-arch
android.mk
Generate libhello-jni.so
Application.mk
Target all platform such as arm-v7/x86/ ...
Add a mudule:LOCAL_LDLIBS += -lsth
JNI 函数 声明:
JNIEXPORT <return> JNICALL Java_<package>_<class>_<function>(JNIEnv* env,jobject,<Args>)
例如:
JNIEXPORT void JNICALL Java_com_Cartoonifier_CartoonifierView_ShowPreview(
JNIEnv* env,jobject ,jint width,jint height,jbyteArray yuv,jintArray bgra){ }
JNIEnv:
native程序中频繁使用JNIEnv*和JavaVM*。而C和C++代码使用JNIEnv*和JavaVM*这两个指针的做法是有区别的,网上大部分代码都使用C++,基本上找不到关于C和C++在这个问题上的详细叙述。
在C中:
使用JNIEnv* env要这样 (*env)->方法名(env,参数列表)
使用JavaVM* vm要这样 (*vm)->方法名(vm,参数列表)
在C++中:
使用JNIEnv* env要这样 env->方法名(参数列表)
使用JavaVM* vm要这样 vm->方法名(参数列表)
JNIEnv struct:
本地方法不能将 JNIEnv 从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的 JNIEnv 是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv 。
Java 通过 JNI 机制调用 c/c++ 写的 native 程序。 c/c++ 开发的 native 程序需要遵循一定的 JNI 规范,下面的例子就是一个 JNI 函数声明:
JNIEXPORT jint JNICALL Java_jnitest_MyTest_test
(JNIEnv * env, jobject obj, jint arg0);
JVM 负责从 Java Stack 转入 C/C++ Native Stack 。当 Java 进入 JNI 调用,除了函数本身的参数 (arg0), 会多出两个参数: JNIEnv 指针和 jobject 指针。
JNIEnv 指针是 JVM 创建的 ,用于 Native 的 c/c++ 方法操纵 Java 执行栈中的数据,比如 Java Class, Java Method 等。
首先, JNI 对于 JNIEnv 的使用 , 提供了两种语法 : c 语法以及 c++ 语法,如下:
c语法:
jsize len = (*env)->GetArrayLength(env,array);
c++语法:
jsize len =env->GetArrayLength(array);
( 注:由于 C 语言并不支持对象的概念,所以 C 语法中需要把 env 作为第一个参数传入,类似于 C++ 的隐式参数 this 指针 ).
另外: JNIEnv 有几个设计的原则:
第一、 JNIEnv 指针被设计成了 Thread Local Storage(TLS) 变量,也就是说每一个 Thread, JNIEnv 变量都有独立的 Copy 。你不能把 Thead#1 使用的 JNIEnv 传给 Thread#2 使用。
第二、 JNIEnv 中定义了一组函数指针, c/c++ Native 程序是通过这些函数指针操纵 Java 数据。这样设计的好处是:你的 c/c++ 程序不需要依赖任何函数库,或者 DLL 。由于 JVM 可能由不同的厂商实现,不同厂商有自己不同的 JNI 实现,如果要求这些厂商暴露约定好的一些头文件和库,这不是灵活的设计。
而且使用函数指针表的另外一个好处是: JVM 可以根据启动参数动态替换 JNI 实现。
需要强调的是 JNIEnv 是跟线程相关的。
调用JavaVM接口:
第一种方式,在加载动态链接库的时候, JVM 会调用 JNI_OnLoad(JavaVM* jvm, void* reserved) (如果定义了该函数)。第一个参数会传入 JavaVM 指针。
第二种方式,在 native code 中调用 JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args) 可以得到 JavaVM 指针。 两种情况下,都可以用全局变量,比如 JavaVM* g_jvm 来保存获得的指针以便在任意上下文中使用。 Android 系统是利用第二种方式 Invocation interface 来创建 JVM 的。
/*
JavaVM *jvm=0;
JNIEnv *env=(JNIEnv *)p;
env->GetJavaVM(&jvm);
*/
调用 J NIEnv 接口:
在 native method 中, JNIEnv 作为第一个参数传入 。那么在 JNIEnv 不作为参数传入的时候,该如何获得它
JNI提供了两个函数:
( *jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)
(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)
两个函数都利用 JavaVM 接口获得 JNIEnv 接口,上面已经讲到如何获得 JavaVM 接口。 JNI 规范也说明,可以将获得 JNIEnv 封装成一个函数。
JNIEnv* JNU_GetEnv()
{
JNIEnv* env;
(*g_jvm) ->GetEnv(g_jvm, ( void **)&env, JNI_VERSION_1_2); //C 风格
//jint result = g_jvm->GetEnv((void **) &env,JNI_VERSION_1_2);
return env;
}
JNI_VERSION
下面是来自 stackoverflow 的一段代码:
#include <jni.h>
#include <iostream>
#include <pthread.h>
using namespace std;
JNIEnv* getEnv(JavaVM *jvm)
{
JNIEnv *env = 0;
jint result = jvm-> GetEnv((void **) &env, JNI_VERSION_1_6); // C++
if (result != JNI_OK)
{
JavaVM
result = jvm->AttachCurrentThread((void **) &env, NULL); | //struct JNIInvokeInterface |
if (result != JNI_OK)
{
cout << "Failed to attach current thread " << pthread_self() << endl;
}
else
{
cout << "Successfully attached native thread " << pthread_self() << endl;
}
// ...and register for detach when thread exits
int result = pthread_setspecific(key, (void *) env);
if (result != 0)
{
cout << "Problem registering for detach" << endl;
}
else
{
cout << "Successfully registered for detach" << endl;
}
}
return env;
}
static pthread_key_t key;
s tatic pthread_once_t key_once;
NDK platform/android-19/*
NDK.inlcude/c/c++
JNIEnv 使用方法:
参数声明 ...jintArray bgra,jbyteArray yuv...
/*......*/
jbyte* _yuv=env->GetByteArrayElements( yuv ,0);// 在 JVM 中获取图像数据
jint* _bgra=env->GetIntArrayElements(bgra,0);
/*......*/
env->ReleaseIntArrayElement(bgra,_bgra,0);
env->ReleaseByteArrayElements(yuv,_yuv,0);
问题是:从这里看到指向的是像素缓冲区,也就是 java 的对象数据,那么就不是 copy, 那么多线程调用 TLS 是怎么实现的?指针发生了竞争怎么办?还是就是拷贝,如果是内存拷贝又是怎么改变原实例的数据的。
引用:
SWIG (Simplified Wrapper and Interface Generator)
Tagline: SWIG is a compiler that integrates C and C++ with languages
including Perl, Python, Tcl, Ruby, PHP, Java, C#, D, Go, Lua,
Octave, R, Scheme (Guile, MzScheme/Racket, CHICKEN), Scilab,
Ocaml, Modula-3, Common Lisp (CLISP, Allegro CL, CFFI, UFFI)
and Pike. SWIG can also export its parse tree into XML and
Lisp s-expressions.
来自 < https://github.com/swig/swig >