Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
零、GitHub工程
本文中的Unity
工程,包括Java
、C/C++
以及编译脚本,我都上传到GitHub
中了,感兴趣的同学可以下载下来学习。GitHub
地址:https://github.com/linxinfa/Unity-JNI-NDK-Demo
一、前言
Unity
中,我们一般使用C#
语言来开发,如果要发布Android
平台,则可能会涉及到java
和C/C++
。C#
、java
、C/C++
三者是可以相互调用的,为此,我画了如下这个图,下文中我将详细介绍如何实现。
下文演示中,工程包名BundleID
为com.linxinfa.game
。
二、C#调用java
Unity
提供了两种方式来支持C#
调用java
。
方式一,通过AndroidJavaClass
类,方式二,通过AndroidJNI
类。
首先,我们写java
接口,可以是非静态接口,也可以是静态接口。
非静态的接口,我们需要先获取到java
对象,然后通过java
对象调用public
接口;
静态接口,我们直接获取java
类,然后直接通过java
类调用public static
接口。
1、编写java接口
为了演示,我这里写一个静态的和一个非静态接口。
package com.linxinfa.game;
//注意包名,要和Unity工程的包名一致
import android.os.Bundle;
import com.unity3d.player.UnityPlayerActivity;
//自己写的主Activity,必须继承UnityPlayerActivity
public class MyActivity extends UnityPlayerActivity
{
private String m_hi;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
m_hi = "hi, 我是java";
}
//非静态接口
public String HelloMethod(String msg)
{
return m_hi + ", 我是非静态接口,你传给我的参数是: " + msg;
}
//静态接口
public static String HelloStaticMethod(int a, int b)
{
return "你好,我是java静态接口,a + b = " + (a+b);
}
}
2、将java代码编译为jar包
可以使用Eclipse
或者Android Studio
来编译java
,相关的教程,可参见我之前的博客:https://linxinfa.blog.csdn.net/article/details/72852155
这里呢,我就直接用jdk
命令行来编译,用到的命令为javac
和jar
,相关教程可参见我这篇博客:https://linxinfa.blog.csdn.net/article/details/107174242
这里需要注意的是,我们依赖了Android SDK
的android.jar
和Unity
的classes.jar
,编译的时候,需要指明依赖它们。
2.1 Android SDK的android.jar所在目录Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms\android-28
(具体android
版本看你下载的版本)
2.2、Unity的classes.jar所在目录Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Release\Classes
2.3、UnityPlayerActivity.java所在目录Unity2020
的UnityPlayerActivity.java
没有在Unity
的classes.jar
中,而是单独以代码的方式提供给我们。UnityPlayerActivity.java
所在目录:Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player
2.4、写个bat来执行编译命令
如下,为了方便,我把上文提到的依赖的jar
和java
文件放在同一个目录中,然后再创建一个makeJar.bat
脚本,将编译的命令行放在makeJar.bat
中makeJar.bat
内容如下:
rem 创建objs目录,用来存放生成的.class文件
@if not exist objs mkdir objs
rem 创建output目录,用来存放最后编译成的.jar文件
@if not exist output mkdir output
rem javac命令,生成.class文件到objs目录中
javac -source 1.6 -target 1.6 -nowarn -encoding utf8 -cp "./android_sdk.jar;./unity_classes.jar" -d "./objs" UnityPlayerActivity.java MyActivity.java
rem 进入objs目录
cd objs
rem 讲objs/com/linxinfa/game/目录中所有的.class文件打包成.jar文件
jar cvf ../output/game.jar ./com/linxinfa/game/*
注意,由于我们用到了jdk
命令行,所以需要配置一下环境变量
双击运行makeJar.bat
,生成了objs
和output
两个文件夹output
文件夹中,game.jar
就是我们生成出来的jar
包
3、拷贝jar包和AndroidManifest.xml
将game.jar
拷贝到Unity
工程中的Assets/Plugins/Android/libs
目录中
另外,还有一个UnityManifest.xml
需要拷贝,UnityManifest.xml
所在目录:Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Apk
将UnityManifest.xml
拷贝到Unity
工程中的Assets/Plugins/Android
目录中,并重命名为AndroidManifest.xml
,将package
改为包名com.linxinfa.game
,将主Activity
改为上文中写的java
的MyActivity
<?xml version="1.0" encoding="utf-8"?>
<!-- GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.linxinfa.game"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity android:name="com.linxinfa.game.MyActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>
接下来,就是写C#
代码来调用jar
中的java
接口了。
4、通过AndroidJavaClass调用java接口(简单明了)
4.1、编写C#调用接口
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CsCallJava_AndroidJavaClass : MonoBehaviour
{
public Text txt;
public Button btn1;
public Button btn2;
void Start()
{
btn1.onClick.AddListener(() =>
{
//通过对象调用非静态接口
var jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
txt.text = jo.Call<string>("HelloMethod", "I am Unity") + "\n";
});
btn2.onClick.AddListener(() =>
{
//通过类调用静态接口
var jc = new AndroidJavaClass("com.linxinfa.game.MyActivity");
txt.text = jc.CallStatic<string>("HelloStaticMethod", 10, 15);
});
}
}
4.2、发布APP运行效果
发布apk
到Android模拟器
上运行效果
5、通过AndroidJNI调用java接口(繁琐)
5.1、编写C#调用接口
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CsCallJava_AndroidJNI : MonoBehaviour
{
public Text txt;
public Button btn1;
public Button btn2;
void Start()
{
btn1.onClick.AddListener(() =>
{
//通过对象调用非静态接口---------------------------------------
//获得类
IntPtr clz = AndroidJNI.FindClass("com/unity3d/player/UnityPlayer");
IntPtr fid = AndroidJNI.GetStaticFieldID(clz, "currentActivity", "Landroid/app/Activity;");
//获得静态属性
IntPtr obj = AndroidJNI.GetStaticObjectField(clz, fid);
//获得类
IntPtr clz_OurAppActitvityClass = AndroidJNI.FindClass("com/linxinfa/game/MyActivity");
//获得方法
IntPtr methodId = AndroidJNI.GetMethodID(clz_OurAppActitvityClass, "HelloMethod", "(Ljava/lang/String;)Ljava/lang/String;");
//参数
jvalue v = new jvalue();
v.l = AndroidJNI.NewStringUTF("I am Unity");
var result = AndroidJNI.CallStringMethod(obj, methodId, new jvalue[] { v });
txt.text =result;
AndroidJNI.DeleteLocalRef(clz);
AndroidJNI.DeleteLocalRef(fid);
AndroidJNI.DeleteLocalRef(obj);
AndroidJNI.DeleteLocalRef(clz_OurAppActitvityClass);
AndroidJNI.DeleteLocalRef(methodId);
});
btn2.onClick.AddListener(() =>
{
//通过类调用静态接口--------------------------------------
//获得类
IntPtr clz = AndroidJNI.FindClass("com/linxinfa/game/MyActivity");
//调用静态方法
IntPtr methodId = AndroidJNI.GetStaticMethodID(clz, "HelloStaticMethod", "(II)Ljava/lang/String;");
//参数
jvalue v1 = new jvalue();
v1.i = 10;
jvalue v2 = new jvalue();
v2.i = 15;
var result = AndroidJNI.CallStaticStringMethod(clz, methodId, new jvalue[] { v1, v2 });
txt.text = result;
AndroidJNI.DeleteLocalRef(clz);
AndroidJNI.DeleteLocalRef(methodId);
});
}
}
5.2、发布APP运行效果
发布apk
到Android模拟器
上运行效果GetMethodID
第三个参数是sig
,它是对函数的签名,也可以说标识,具体的格式为
Java类型 | 符号 |
---|---|
Boolean | Z |
Byte | B |
Char | C |
Short | S |
Integer | I |
Long | L |
Float | F |
Double | D |
Void | V |
Object对象 | L开头,包名/类名,”;”结尾,$标识嵌套类 |
数组 | [签名 |
例子:
//sig: {}V
public void demo1() {}
//sig: ()I
public int demo2() {}
//sig: (I)V
public void demo3(int a) {}
//sig: (II)V
public void demo4(int a, int b) {}
//sig: (Ljava/lang/String;)V
public void demo5(String a) {}
//sig: (Ljava/lang/String;Ljava/lang/String;)V
public void demo6(String a, String b) {}
//sig: ([Ljava/lang/String;)V
public void demo7(String [] arr) {}
//sig: (Ljava/lang/String;)Ljava/lang/String;
public String demo8(String a) { return ""; }
//sig: ([java/lang/String;)Ljava/lang/String;
public String demo6(String [] a) { return ""; }
//sig: ([Ljava/lang/String;[Ljava/lang/String;)V
public void demo8(String[] a, String[] b) {}
//sig: ([Ljava/lang/String;I)V
public void demo8(String[] a,int b) {}
//sig: ()Z
public boolean demo9() { return false; }
//内部类
// (Ljava/lang/String;com/linxinfa/game/Demo$DemoInnter;)Z
如果是普通类型的数组不需要加;
后缀,如果是Object
类型的数组则需要添加;
三、java调用C#
java
调用C#
一般是通过UnityPlayer.UnitySendMessage
的方式发送消息给Unity
。
1、编写java接口
首先,封装一个java
接口,里面通过UnityPlayer.UnitySendMessage
发消息给Unity
。
package com.linxinfa.game;
import android.os.Bundle;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
public class MyActivity extends UnityPlayerActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public static void JavaCallCSharp(String msg)
{
String returnMsg = "hello,我是java, 我通过UnityPlayer.UnitySendMessage返回消息给你: " + msg;
//第一个参数是GameObject名字
//第二个参数是GameObject上脚本的public方法
//第三个参数是发送给Unity的参数
UnityPlayer.UnitySendMessage("JavaMsgRecver", "OnJavaMsg", returnMsg);
}
}
2、Unity创建物体和C#脚本响应java消息
然后,在Unity
场景中创建一个物体,取名为JavaMsgRecver
,在创建一个脚本JavaCallCs_UnitySendMessage.cs
挂到JavaMsgRecver
物体上。
其中HandleJavaMsg.cs
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class JavaCallCs_UnitySendMessage : MonoBehaviour
{
public Text txt;
public Button btn;
void Start()
{
btn.onClick.AddListener(() =>
{
var jc = new AndroidJavaClass("com.linxinfa.game.MyActivity");
jc.CallStatic("JavaCallCSharp", "I am Unity");
});
}
public void OnJavaMsg(string msg)
{
txt.text = msg;
}
}
3、发布APP运行效果
发布apk
到Android模拟器
上运行效果
四、C#调用C/C++
1、关于NDK
在Android
平台,C/C++
需通过NDK
编译成动态链接库.so文件
,然后C#
中通过[DllImport("soname")]
声明后即可调用。
NDK
,全称Native Development Kit
,是Android
的一种开发工具包。
目前的Android
开发,不再是纯粹的Java
层开发,更多的会与C/C++
结合,把一些重要的方和行为以及一些私密性质的东西放在C/C++
中,通过NDK
将其编译成.so
动态库文件,放入工程中的libs
目录。
下文中我们会使用ndk
命令行,所以需要配置一下NDK\build
的环境变量。
2、编写C/C++接口
封装C/C++
接口,放在jni
文件夹中。
cs_call_c.c
脚本
int AddInt(int a, int b)
{
return a + b;
}
cs_call_cpp.cpp
脚本
//声明一个类
class MyClass
{
public:
static float AddFloat(float a, float b)
{
return a + b;
}
};
extern "C"
{
float AddFloat(float a,float b)
{
return MyClass::AddFloat(a,b);
}
}
3、Android.mk与Application.mk
接着,我们需要创建Android.mk
和Application.mk
这两个文件。
Android.mk
文件,主要指明参与编译的C/C++
文件和生成的so
名字,如下,名字为linxinfa_so
,最后生成出来的so
文件就是liblinxinfa_so.so
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := linxinfa_so
LOCAL_SRC_FILES := \
cs_call_c.c \
cs_call_cpp.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
文件,主要是配置引用的Android.mk
文件和生成so
的CPU
架构,如下会生成armeabi-v7a
和x86
两个CPU
架构的so
。
APP_STL := c++_static
APP_CPPFLAGS := -frtti -std=c++11
APP_PLATFORM := android-19
APP_CFLAGS += -Wno-error=format-security
APP_BUILD_SCRIPT := Android.mk
APP_ABI := armeabi-v7a x86
4、通过ndk-build.cmd编译C/C++
现在就可以通过ndk-build.cmd
来编译C/C++
代码了
为了方便,我们也写个makeSo.bat
脚本来执行makeSo.bat
脚本
cd jni
ndk-build
运行makeSo.bat
脚本
5、生成so文件
生成了libs
和obj
两个文件夹
其中,libs
文件夹中,就是我们生成的armeabi-v7a
和x86
两个CPU
架构的so
6、拷贝so文件到Unity工程中
将so
拷贝到Unity
工程中的Assets/Plugins/Android/libs
目录中
7、编写C#调用接口
接着,编写C#
代码调用C/C++
接口
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
public class CsCallCCPP_DllImport_SO : MonoBehaviour
{
public Text txt;
public Button btn1;
public Button btn2;
void Start()
{
btn1.onClick.AddListener(() =>
{
//C#调用C
txt.text = "C#调用C接口: AddInt(2, 6) = " + AddInt(2, 6);
});
btn2.onClick.AddListener(() =>
{
//C#调用C++
txt.text = "C#调用C++接口: AddFloat(2.7, 4.2) = " + AddFloat(2.7f, 4.2f);
});
}
//C接口
[DllImport("linxinfa_so")]
public static extern int AddInt(int a, int b);
//C++接口
[DllImport("linxinfa_so")]
public static extern float AddFloat(float a, float b);
}
8、发布APP运行效果
发布apk
到Android模拟器
上运行效果
五、C/C++调用C#
1、编写C++接口
cpp_call_cs.h
#pragma once
#include<string.h>
#include<iostream>
extern "C"
{
class Debug
{
public:
//声明函数指针
static void (*Log)(char* message,int iSize);
};
// 注册C#的委托
void InitCSharpDelegate(void (*Log)(char* message, int iSize));
// 给C#调用的C++接口,里面再通过函数指针调用C#的委托
void HelloCppToCs(char* message);
}
cpp_call_cs.cpp
#include "cpp_call_cs.h"
// 定义函数指针,用来接受C#的委托
void(*Debug::Log)(char* message, int iSize);
void HelloCppToCs(char* message)
{
char temp[512]="你好,我是c++, 我收到了你传过来的参数: ";
//字符串拼接
strcat(temp, message);
// 调用C#的委托
Debug::Log(temp, strlen(temp));
}
// 注册C#的委托
void InitCSharpDelegate(void(*Log)(char* message, int iSize))
{
Debug::Log = Log;
}
2、Android.mk文件和Application.mk文件
Android.mk
文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := linxinfa_so
LOCAL_SRC_FILES := \
cpp_call_cs.h \
cpp_call_cs.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
文件
APP_STL := c++_static
APP_CPPFLAGS := -frtti -std=c++11
APP_PLATFORM := android-19
APP_CFLAGS += -Wno-error=format-security
APP_BUILD_SCRIPT := Android.mk
APP_ABI := armeabi-v7a
3、通过ndk-build编译成so
4、拷贝so文件到Unity工程
5、编写C#调用接口
using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
public class CPPCallCs_MonoPInvokeCallback : MonoBehaviour
{
public Text txt;
public Button btn;
public static string s_msg = "";
void Start()
{
InitCSharpDelegate(LogMessageFromCpp);
btn.onClick.AddListener(() =>
{
//C#调用C
HelloCppToCs("I am Unity");
});
}
void Update()
{
if(!string.IsNullOrEmpty(s_msg))
txt.text = s_msg;
}
[MonoPInvokeCallback(typeof(LogDelegate))]
public static void LogMessageFromCpp(IntPtr message, int iSize)
{
s_msg = "C#被C++调用\n";
s_msg += Marshal.PtrToStringAnsi(message, iSize);
Debug.Log(s_msg);
}
public delegate void LogDelegate(IntPtr message, int iSize);
[DllImport("linxinfa_so")]
public static extern void InitCSharpDelegate(LogDelegate log);
[DllImport("linxinfa_so")]
public static extern void HelloCppToCs(string msg);
}
6、发布APP运行效果
发布apk
到Android模拟器
上运行效果
六、java调用C/C++
1、关于JNI
java
调用C/C++
,需要通过JNI
。JNI
,全称为Java Native Interface
,即Java
本地接口,JNI
是Java
调用Native
语言的一种特性。通过JNI
可以使得Java
与C/C++
交互。
由于JNI
是JVM
规范的一部分,因此可以将我们写的JNI
的程序在任何实现了JNI
规范的Java
虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++
写的大量代码。
2、本文实现JNI的步骤
(1)、在Java
中先声明一个native
方法,
public native int TestJNIAddInt(int a, int b);
通过java
调用该native
方法。
(2)、用C/C++
实现Java
的native
方法,命名规范如下。
jint Java_com_linxinfa_game_MyActivity_TestJNIAddInt(JNIEnv* env,
jobject thiz, jint a, jint b)
jint
表示此方法的返回值为整形,其他数据类型还有jlong 、jfloat、jdouble、 jobject、jboolean、jbyte、jchar、jshort
。
函数名固定以Java
开头,com_linxinfa_game
是包名,MyActivity
是Java
类名,TestJNIAddInt
就是Java
中声明的native
方法名。参数里面,前面两个参数固定,后面的参数自定义。JNIEnv
是一个指针,指向一个线程相关的结构,线程相关结构,线程相关结构指向JNI
函数指针数组,这个数组中存放了大量的JNI
函数指针,这些指针指向了详细的JNI
函数。
3、JNIEnv常用的函数
3.1、创建Java中的对象
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
第一个参数jclass clazz
代表的你要创建的类的对象,第二个参数jmethodID methodID
代表你要使用那个构造方法ID
来创建这个对象。只要有jclass
和jmethodID
,我们就可以在本地方法创建这个Java
类的对象。
例
//获取Java类
jclass clazz = env->FindClass("com/linxinfa/game/MyJavaClass");
//拿到构造方法的methodID,构造函数固定是<init>
jmethodID method_init_id = env->GetMethodID(clazz,"<init>", "()V");
//普通方法的methodID
jmethodID method_set_id = env->GetMethodID(clazz,"setAge", "(I)V");
jmethodID method_get_id = env->GetMethodID(clazz,"getAge", "()I");
//创建了MyJavaClass对象
jobject obj = env->NewObject(clazz, method_init_id);
//调用setAge方法
env->CallVoidMethod(obj, method_set_id, 21);
//调用getAge方法
int age = env->CallIntMethod(obj, method_get_id);
GetMethodID
第三个参数是函数签名sig
,文章前面讲C#
通过JNI
调用Java
的时候已经说过了,此处不再赘述。(GetFieldID
的sig
同理)。
还需要注意,后面调用函数的时候,一个是CallVoidMethod
,一个是CallIntMethod
,还有其他的Call
接口,根据返回值调用对应的方法
方法名 | 本地返回类型 |
---|---|
CallVoidMethod、CallVoidMethodA、CallVoidMethodV | void |
CallObjectMethod、CallObjectMethodA、CallObjectMethodV | jobject |
CallBooleanMethod、CallBooleanMethodA、CallBooleanMethodV | jboolean |
CallByteMethod、CallByteMethodA、CallByteMethodV | jbyte |
CallCharMethod、CallCharMethodA、CallCharMethodV | jchar |
CallShortMethod、CallShortMethodA、CallShortMethodV | jshort |
CallIntMethod、CallIntMethodA、CallIntMethodV | jint |
CallLongMethod、CallLongMethodA、CallLongMethodV | jlong |
CallFloatMethod、CallFloatMethodA、CallFloatMethodV | jfloat |
CallDoubleMethod、CallDoubleMethodA、CallDoubleMethodV | jdouble |
3.2、创建Java类中的String对象
jstring NewStringUTF(JNIEnv *env, const jchar *unicodeChars);
例
jstring s = env->NewString("Hello world");
3.3、其他常用方法
//加载Java定义的类,类名需要是全路径,如"com/linxinfa/game/MyJavaClass"
jclass FindClass(JNIEnv *env, const char *name);
//返回Java字符串的长度(Unicode字符数)
jsize GetStringLength(JNIEnv *env, jstring string);
//以字节为单位返回字符串的 UTF-8 长度。
jsize GetStringUTFLength(JNIEnv *env, jstring string);
//测试对象是否为某个类的实例
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
//返回类或接口实例(非静态)方法 ID
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//返回类的实例(非静态)属性 ID
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//返回对象的实例(非静态)属性的值。要访问的属性由通过调用GetFieldID() 而得到的属性ID指定
NativeType GetField(JNIEnv*env, jobject obj, jfieldID fieldID);
//设置对象的实例(非静态)属性的值。要访问的属性由通过调用GetFieldID() 而得到的属性ID指定
void SetField(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
//返回数组中的元素数
jsize GetArrayLength(JNIEnv *env, jarray array);
//返回 Object 数组的元素
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
//设置 Object 数组的元素
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
好了,开始写Demo
。
4、编写java接口
package com.linxinfa.game;
import android.os.Bundle;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
import android.widget.Toast;
public class MyActivity extends UnityPlayerActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public void JavaCallCpp()
{
//调用c++接口
int result = TestJNIAddInt(4, 5);
//展示运算结果
Toast.makeText(this, "java调用c++, 4f + 5f = " + result, Toast.LENGTH_LONG).show();
}
//声明.cpp中的TestJNIAddInt方法
public native int TestJNIAddInt(int a, int b);
static
{
//加载.so文件
System.loadLibrary("linxinfa_so");
}
}
5、编写c++接口
#include <jni.h>
//声明一个类
class MyMathClass
{
public:
static int AddInt(int a, int b)
{
return a + b;
}
};
extern "C"
{
jint
Java_com_linxinfa_game_MyActivity_TestJNIAddInt(JNIEnv* env, jobject thiz, jint a, jint b)
{
return MyMathClass::AddInt(a,b);
}
}
6、编译java为jar,编译C++为so
编译java
和c++
过程见上文中的步骤,此处略过
7、将jar和so拷贝到Unity工程中
8、编写C#代码
using UnityEngine;
using UnityEngine.UI;
public class JavaCallCPP_JNI : MonoBehaviour
{
public Button btn;
void Start()
{
btn.onClick.AddListener(() =>
{
var jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
//调用java接口,java中会通过JNI去调用C++接口
jo.Call("JavaCallCpp");
});
}
}
9、发布APP运行效果
发布apk
到Android模拟器
上运行效果
七、C/C++调用java
C/C++
调用java
也是通过JNI
1、编写java接口
package com.linxinfa.game;
import android.os.Bundle;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
import android.widget.Toast;
public class MyActivity extends UnityPlayerActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public void CsCallJavaThenJavaCallCppThenCppCallJava()
{
JNICppCallJava();
}
public void CppCallJavaMethod(String msg)
{
Toast.makeText(this, "c++调用java:" + msg, Toast.LENGTH_LONG).show();
}
//声明.cpp中的TestJNIAddInt方法
public native void JNICppCallJava();
static
{
//加载.so文件
System.loadLibrary("linxinfa_so");
}
}
2、编写c++接口
#include <jni.h>
extern "C"
{
void
Java_com_linxinfa_game_MyActivity_JNICppCallJava(JNIEnv* env, jobject thiz)
{
jclass clz = env->FindClass("com/unity3d/player/UnityPlayer");
jfieldID fid = env->GetStaticFieldID(clz, "currentActivity", "Landroid/app/Activity;");
jobject obj = env->GetStaticObjectField(clz, fid);
jclass clzMyActivity = env->FindClass("com/linxinfa/game/MyActivity");
jmethodID methodId = env->GetMethodID(clzMyActivity, "CppCallJavaMethod","(Ljava/lang/String;)V");
jstring msg = env->NewStringUTF("I am c++");
env->CallVoidMethod(obj, methodId, msg);
}
}
3、编译java为jar,编译C++为so
编译java
和c++
过程见上文中的步骤,此处略过
4、将jar和so拷贝到Unity工程中
5、编写C#代码
using UnityEngine;
using UnityEngine.UI;
public class CPPCallJava_JNI : MonoBehaviour
{
public Button btn;
void Start()
{
btn.onClick.AddListener(() =>
{
var jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
//C#调用java接口,然后java中会通过JNI去调用C++接口,再然后C++接口中会通过JNI调用java接口
jo.Call("CsCallJavaThenJavaCallCppThenCppCallJava");
});
}
}
6、发布APP运行效果
发布apk
到Android模拟器
上运行效果
本文地址:https://blog.csdn.net/linxinfa/article/details/108642977
上一篇: 原来你没有啊
下一篇: 朱元璋为什么没有封刘伯温为丞相呢?