欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

程序员文章站 2022-07-04 16:23:55
一、关于NDKNDK,全称Native Development Kit,是Android的一种开发工具包。目前的Android开发,不再是纯粹的Java层开发,更多的会与C/C++结合,把一些重要的方和行为以及一些私密性质的东西放在C/C++中,通过NDK将其编译成.so动态库文件,放入工程中的libs目录。二、Unity在Android平台通过C#调用.so库接口如果是Java调用C/C++写的.so动态库的接口,需要用到JNI,如果是Unity中使用C#调用C/C++写的.so动态库的接口,则通...

零、GitHub工程

本文中的Unity工程,包括JavaC/C++以及编译脚本,我都上传到GitHub中了,感兴趣的同学可以下载下来学习。
GitHub地址:https://github.com/linxinfa/Unity-JNI-NDK-Demo
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

一、前言

Unity中,我们一般使用C#语言来开发,如果要发布Android平台,则可能会涉及到javaC/C++
C#javaC/C++三者是可以相互调用的,为此,我画了如下这个图,下文中我将详细介绍如何实现。
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
下文演示中,工程包名BundleIDcom.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命令行来编译,用到的命令为javacjar,相关教程可参见我这篇博客:https://linxinfa.blog.csdn.net/article/details/107174242

这里需要注意的是,我们依赖了Android SDKandroid.jarUnityclasses.jar,编译的时候,需要指明依赖它们。

2.1 Android SDK的android.jar所在目录
Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms\android-28(具体android版本看你下载的版本)
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
2.2、Unity的classes.jar所在目录
Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Release\Classes
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
2.3、UnityPlayerActivity.java所在目录
Unity2020UnityPlayerActivity.java没有在Unityclasses.jar中,而是单独以代码的方式提供给我们。
UnityPlayerActivity.java所在目录:
Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\playerUnity通过NDK、JNI实现C#、java、C/C++之间的相互调用
2.4、写个bat来执行编译命令
如下,为了方便,我把上文提到的依赖的jarjava文件放在同一个目录中,然后再创建一个makeJar.bat脚本,将编译的命令行放在makeJar.bat
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
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命令行,所以需要配置一下环境变量
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
双击运行makeJar.bat,生成了objsoutput两个文件夹
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
output文件夹中,game.jar就是我们生成出来的jar
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

3、拷贝jar包和AndroidManifest.xml

game.jar拷贝到Unity工程中的Assets/Plugins/Android/libs目录中
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
另外,还有一个UnityManifest.xml需要拷贝,UnityManifest.xml所在目录:
Unity安装目录\Editor\Data\PlaybackEngines\AndroidPlayer\Apk
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
UnityManifest.xml拷贝到Unity工程中的Assets/Plugins/Android目录中,并重命名为AndroidManifest.xml,将package改为包名com.linxinfa.game,将主Activity改为上文中写的javaMyActivity

<?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>

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

接下来,就是写C#代码来调用jar中的java接口了。

4、通过AndroidJavaClass调用java接口(简单明了)

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

5、通过AndroidJNI调用java接口(繁琐)

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
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
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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物体上。
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
其中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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

四、C#调用C/C++

Unity通过NDK、JNI实现C#、java、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的环境变量。
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

2、编写C/C++接口

封装C/C++接口,放在jni文件夹中。
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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.mkApplication.mk这两个文件。
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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文件和生成soCPU架构,如下会生成armeabi-v7ax86两个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++代码了
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
为了方便,我们也写个makeSo.bat脚本来执行
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
makeSo.bat脚本

cd jni
ndk-build

运行makeSo.bat脚本
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

5、生成so文件

生成了libsobj两个文件夹
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
其中,libs文件夹中,就是我们生成的armeabi-v7ax86两个CPU架构的so
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

6、拷贝so文件到Unity工程中

so拷贝到Unity工程中的Assets/Plugins/Android/libs目录中
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

五、C/C++调用C#

Unity通过NDK、JNI实现C#、java、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

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

4、拷贝so文件到Unity工程

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

六、java调用C/C++

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

1、关于JNI

java调用C/C++,需要通过JNI
JNI,全称为Java Native Interface,即Java本地接口,JNIJava调用Native语言的一种特性。通过JNI可以使得JavaC/C++交互。
由于JNIJVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++写的大量代码。

2、本文实现JNI的步骤

(1)、在Java中先声明一个native方法,

public native int TestJNIAddInt(int a, int b);  

通过java调用该native方法。
(2)、用C/C++实现Javanative方法,命名规范如下。

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是包名,MyActivityJava类名,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来创建这个对象。只要有jclassjmethodID,我们就可以在本地方法创建这个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的时候已经说过了,此处不再赘述。(GetFieldIDsig同理)。

还需要注意,后面调用函数的时候,一个是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

编译javac++过程见上文中的步骤,此处略过
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

7、将jar和so拷贝到Unity工程中

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果
Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

七、C/C++调用java

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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

编译javac++过程见上文中的步骤,此处略过

4、将jar和so拷贝到Unity工程中

Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

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运行效果

发布apkAndroid模拟器上运行效果Unity通过NDK、JNI实现C#、java、C/C++之间的相互调用

本文地址:https://blog.csdn.net/linxinfa/article/details/108642977