Java中类型与C/C++中对应关系
Java中的类的对应
Sign签名, 用来识别对应各个方法。
JDK下的javap.exe能输出签名。用法javap -s -p 完整类名
下面是几个例子程序:
1、C++本地方法中获取Java中的变量以及调用Java中的方法
Java代码:
1 package com.test; 2 3 import java.util.Date; 4 5 public class TestNative { 6 public native void sayHello(); 7 private int a = 10; 8 public int function(int x,Date date,int[] y){ 9 System.out.println("function"); 10 return 0; 11 } 12 public double max(double a,double b){ 13 return a>b?a:b; 14 } 15 /** 16 * @param args 17 */ 18 public static void main(String[] args) { 19 System.loadLibrary("NativeCode"); 20 TestNative tNative = new TestNative(); 21 tNative.sayHello(); 22 } 23 24 }
C++本地代码:
com_test_TestNative.h代码省略了
1 #include<iostream.h> 2 #include"com_test_TestNative.h" 3 4 JNIEXPORT void JNICALL Java_com_test_TestNative_sayHello(JNIEnv *env, jobject obj) 5 { 6 //因为sayHello不是静态函数,所以传进来的obj就是调用这个函数的对象,否则就是传入native方法所在的类 7 jclass hello_clazz = env->GetObjectClass(obj); //得到的就是native方法所在的类 8 // jfieldID fieldId_a = env->GetFieldID(hello_clazz,"a","I"); 9 // jmethodID methodId_fun = env->GetMethodID(hello_clazz,"function","(ILjava/util/Date;[I)I"); 10 // env->CallIntMethod(obj,methodId_fun,0L,NULL,NULL); //方法调用 11 // cout<<"hello world!"<<endl; 12 // cout<<"successful"<<endl; 13 /* 14 jfieldID field_a = env->GetFieldID(hello_clazz,"a","I"); //得到字段a的ID 15 jint a = env->GetIntField(obj,field_a); //得到字段a的值 16 cout<<a<<endl; 17 env->SetIntField(obj,field_a,100L); 18 */ 19 jmethodID methodId_max = env->GetMethodID(hello_clazz,"max","(DD)D"); 20 jvalue *values = new jvalue[2]; //jvalue是用来向java函数中传参数的 21 values[0].d = 3.14; 22 values[1].d = 3.22; 23 //jdouble max = env->CallDoubleMethod(obj,methodId_max,3.18,3.15); //第一种方法调用 24 jdouble max = env->CallDoubleMethodA(obj,methodId_max,values); //第二种方法调用 25 delete [] values; 26 cout<<max<<endl; 27 28 }
2、C++本地方法中对Java中的字符串操作(实现用户输入一个字符串,在C++中对其反转)
Java代码:
1 package com.test; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 7 public class TestStr { 8 public native void cppCode(); 9 private String message; 10 public static void main(String[] args) throws IOException { 11 System.loadLibrary("CPP"); 12 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 13 String str = reader.readLine(); 14 TestStr obj = new TestStr(); 15 obj.message = str; 16 obj.cppCode(); 17 System.out.println("java :"+obj.message); 18 } 19 20 }
c++本地方法:
com_test_TestStr.h省略了
1 #include "com_test_TestStr.h" 2 #include<windows.h> 3 #include<string> 4 #include<algorithm> 5 using namespace std; 6 JNIEXPORT void JNICALL Java_com_test_TestStr_cppCode(JNIEnv *env, jobject obj) 7 { 8 jfieldID fid_msg = env->GetFieldID(env->GetObjectClass(obj),"message","Ljava/lang/String;"); 9 jstring j_msg = (jstring)env->GetObjectField(obj,fid_msg); 10 jsize len = env->GetStringLength(j_msg); //得到字符串的长度 11 jchar* jstr = new jchar[len+1]; //申请空间 12 jstr[len] = L'\0'; 13 env->GetStringRegion(j_msg,0,len,jstr); 14 //env->ReleaseStringChars(j_msg,jstr); 15 //MessageBoxW(NULL,(const wchar_t*)jstr,L"Title",MB_OK); 16 wstring wstr((const wchar_t*)jstr); 17 delete[] jstr; //释放空间 18 std::reverse(wstr.begin(),wstr.end()); //字符串反转 19 jstring j_new_str = env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size()); //创建一个新的字符串 20 env->SetObjectField(obj,fid_msg,j_new_str); //设置字符串给java对象 21 22 }
3、C++本地方法中获取Java中的数组(实现用C++本地方法将Java中的数组排序)
Java代码:
1 package com.test; 2 3 public class TestArray { 4 5 public int[] array = {5,7,3,4,1,9,2,8,6,0}; 6 7 public native void callCppFun(); 8 9 public static void main(String[] args) { 10 System.loadLibrary("TestArray"); 11 TestArray obj = new TestArray(); 12 obj.callCppFun(); 13 for(int a:obj.array){ 14 System.out.println(a); 15 } 16 } 17 18 }
C++本地代码:
com_test_TestArray.h省略了
1 #include"com_test_TestArray.h" 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 JNIEXPORT void JNICALL Java_com_test_TestArray_callCppFun (JNIEnv *env, jobject obj) 6 { 7 jfieldID fid_array = env->GetFieldID(env->GetObjectClass(obj),"array","[I"); //获取数组的id 8 jintArray jint_array = (jintArray)env->GetObjectField(obj,fid_array); 9 10 jint* int_arr = env->GetIntArrayElements(jint_array,NULL); //转化为jint数组 11 jsize len = env->GetArrayLength(jint_array); 12 std::sort(int_arr,int_arr+len); //对数组进行排序 13 env->ReleaseIntArrayElements(jint_array,int_arr,0); //0:释放C++数组,并且更新到Java 14 /* for(jsize i=0;i<len;i++){ 15 cout<<int_arr[i]<<endl; 16 } 17 */ 18 // env->ReleaseIntArrayElements(jint_array,int_arr,JNI_ABORT);//JNI_ABORT:释放C++数组,但是不更新到Java 19 }
使用Android Sutdio创建一个新的工程后,接下来记录创建NDK工程的基本步骤。
本文将达到:1. 创建NDK工程2. 在JNI中输出Log语句3. 指定编译的so库的abi版本4. 解决在创建NDK工程中的问题
Step: 1. 添加native接口注意写好native接口和System.loadLibrary()即可了,并无特别之处。P.S:onCreate()中对R.id.txt执行setText(),所以这里需要对xml布局文件按正常的开发步骤进行修改即可。
直接给出代码如下:
- public class MainActivity extends Activity{
- static {
- System.loadLibrary("JniTest");
- }
- public native String getStringFromNative();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- TextView txtView = (TextView) findViewById(R.id.txt);
- txtView.setText(getStringFromNative());
- }
- }
Step: 2.执行Build->Make Project
这一步骤执行一下,验证工程中并无其它错误,并对工程进行了编译,生成了.class文件..class文件的生成路径是在 app_path/build/intermediates/classes/debug下的.如下图:
Step: 3.javah生成c头文件
点击"View->Tool Windows->Terminal",即在Studio中进行终端命令行工具.执行如下命令生成c语言头文件。这里需要注意的是要进入 <Project>\app\src\main的目录下执行javah命令,为的是生成的 .h 文件同样是在<Project>\app\src\main路径下,可以在Studio的工程结构中直接看到。
操作命令:javah -d jni -classpath <SDK_android.jar>;<APP_classes> lab.sodino.jnitest.MainActivity具体操作图如下:
- javah -d jni -classpath c:\Users\sodinochen\AppData\Local\Android\sdk\platforms
- \android-16\android.jar;..\..\build\intermediates\classes\debug lab.sodino.jnitest.MainActivity
对于"主版本51比50新,此编译器支持最新的主版本"则是由于电脑上安装了两个版本的jdk引起的,而当前使用的是旧的jdk。把旧的jdk删除,并执行java version命令后显示当前jdk为最新的1.7时,则不会再有此提示了。如下图:最后的生成结果:
Step: 4.编辑c文件
在main.c文件中实现头文件中的方法,具体功能为直接return回一个String,并且使用android_log打印出相关日志。代码如下:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- #include <android/log.h>
- #ifndef LOG_TAG
- #define LOG_TAG "ANDROID_LAB"
- #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
- #endif
- /* Header for class lab_sodino_jnitest_MainActivity */
- #ifndef _Included_lab_sodino_jnitest_MainActivity
- #define _Included_lab_sodino_jnitest_MainActivity
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: lab_sodino_jnitest_MainActivity
- * Method: getStringFromNative
- * Signature: ()Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_lab_sodino_jnitest_MainActivity_getStringFromNative
- (JNIEnv * env, jobject jObj){
- LOGE("log string from ndk.");
- return (*env)->NewStringUTF(env,"Hello From JNI!");
- }
- #ifdef __cplusplus
- }
- #endif
- #endif
到这里后,我们再执行一个"Build->Make Project",发现"Messages Gradle Build"会给出提示如下:
- Error:Execution failed for task ':app:compileDebugNdk'.
- > NDK not configured.
- Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.
- (On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)
这里提示了NDK未配置,并且需要在工程中的local.properties文件中配置NDK路径。好了,提示很清楚了,那我们就进入下一步吧。
Step: 5.配置NDK这一步包括两个动作:1.指明ndk路径2. 修改build.gradle配置工程中共有两个build.gradle配置文件,我们要修改的是在<Project>\app\build.gradle这个文件。为其在defaultConfig分支中增加上
- ndk {
- moduleName "JniTest"
- ldLibs "log", "z", "m"
- abiFilters "armeabi", "armeabi-v7a", "x86"
- }
以上配置代码指定的so库名称为JniTest,链接时使用到的库,对应android.mk文件中的LOCAL_LDLIBS,及最终输出指定三种abi体系结构下的so库。添加后如下图:这时,再执行"Build->Rebuild Project",就可以编译出so文件了。但在Window平台上会出现一个问题:
- Error:Execution failed for task ':app:compileDebugNdk'.
- > com.android.ide.common.internal.LoggedErrorException: Failed to run command:
- D:\Mission\adt-bundle-windows\ndk-r10b\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\Android.mk APP_PLATFORM=android-21 NDK_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj NDK_LIBS_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a,x86
- Error Code:
- 2
- Output:
- make.exe: *** No rule to make target `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni', needed by `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni\main.o'. Stop.
编译出来的库文件被Studio输出到了下图的路径中出现这个错误很莫名其妙..几番折腾下,找到一个视频出来了大概原因及解决方式:出处见Youtube视频 02:50分开始: https://www.youtube.com/watch?v=okLKfxfbz40#t=362在Windows下NDK一个bug,当仅仅编译一个文件时出现会出现此问题,解决方法就是 再往jni文件夹加入一个空util.c文件即可。如下图:
Step: 6.安装运行
界面:
查看Log打印: