Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9409523
  • 博文数量: 1748
  • 博客积分: 12961
  • 博客等级: 上将
  • 技术积分: 20070
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-09 11:25
个人简介

偷得浮生半桶水(半日闲), 好记性不如抄下来(烂笔头). 信息爆炸的时代, 学习是一项持续的工作.

文章分类

全部博文(1748)

文章存档

2024年(24)

2023年(26)

2022年(112)

2021年(217)

2020年(157)

2019年(192)

2018年(81)

2017年(78)

2016年(70)

2015年(52)

2014年(40)

2013年(51)

2012年(85)

2011年(45)

2010年(231)

2009年(287)

分类: Android平台

2016-04-14 18:52:09



前段时间开发Unity Android项目的时候需要在Unity C# Script、Native Code和Java三个模块之间相互调用,因此总结了一下这几种代码之间的调用方法,以防忘记。这篇先看看Unity C#与Native Code之间的相互调用。

 

一、Unity C#调用Native Code

最常见的应该是在游戏的C#脚本中调用Native Code中的方法,从层级来看可以视为游戏上层调用下层封装的函数,具体实现如下:

1、编写C、C++代码,导出相应函数或变量,下面代码是.h和.cpp中对应的代码:

//In NativeCode.h

#ifndef __NATIVE_CODE__

#define __NATIVE_CODE__

extern "C"

{

    float AddFun(float x, float y);

}

#endif

//In NativeCode.cpp

#include "NativeCode.h"

extern "C"

{

    float AddFun(float x, float y)

    {

        return x+y;

    }

}

 

2、编写Android.mk,将mk文件和c、cpp文件放到jni文件夹下,使用NDK(ndk-build命令)将Native Code编译成.so文件。开发过Android项目的同学应该都非常熟悉Android.mk和Application.mk,这里就不详细介绍了,以下是Android.mk中的内容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE     :=  NativeEngine

LOCAL_C_INCLUDES := $(LOCAL_PATH)

LOCAL_SRC_FILES  := NativeCode.cpp

LOCAL_LDLIBS     := -llog -landroid

include $(BUILD_SHARED_LIBRARY)

 

其中LOCAL_MODULE为模块的名字,生成文件的名字会自动带上lib前缀和.so后缀,比如上面使用ndk编译出的动态库的名字就是libNativeEngine.so;LOCAL_C_INCLUDES和LOCAL_SRC_FILES为头文件和源文件列表,注意路径;LOCAL_LDLIBS为编译模块要使用的附加连接器选项;最后BUILD_SHARED_LIBRARY表示编译成动态库(.so),相反使用LOCAL_STATIC_LIBRARY表示编译成静态库(.a)。

这应该是最简单的Android.mk了,实际项目中的Android.mk可能远比这个复杂,例如Native code中引用了其他库文件或者启用一些编译优化选项等等。

 

3、将编译好的libNativeEngine.so文件放入Unity工程Asset\Plugins\Android目录下,如果在Eclipse中调试则放入Android项目下的libs\armeabi-v7a目录下。

 

4、在Unity中编写C#脚本,引用so文件,声明导出的接口,代码如下:

//In Unity C# Script

using UnityEngine;

using System.Collections;

using System.Runtime.InteropServices;

public class CallNativeCode : MonoBehaviour

{

    [DllImport("NativeEngine")]

    public static extern floatAddFun(float x, float y);

    void Start()

    {

        float addResult =AddFun(2.5f, 8.8f);

        Debug.Log("Addresult is: " + addResult);

    }

    void Update()

    {

    }

}

通过DllImport引用Native模块的名字,注意需要去掉lib前缀和.so后缀,当然DllImport还包含一些缺省参数,这里就不一一介绍了,从字面上也很好理解:

 

 

5、通过Unity编译打包生成apk,安装并运行,使用adb logcat命令或者Eclipse 的LogCat查看结果,下图可以看到Unity C#调进了Native Code的函数:

 

 

方法很简单,但是也有一些需要注意的地方:

(1)如果程序提示找不到函数入口点,请查看导出的函数名字是否和声明的名字一致,如果不使用extern “C”,g++会对导出的函数进行签名,导出的接口除了函数名字还包含一些其他的字符。此时可以将DllImport的EntryPoint设置为包含其他字符的函数名字,或者在Native code导出时加上extern“C”,使导出的函数名和声明的函数名一致。Tips:extern “C”告诉链接器在链接的时候用C函数规范来链接,主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

(2)最常见的问题应该是函数参数的传递上了,传值的还好,native层不会去修改传入的参数,但如果需要native层修改C#传入的参数就应该设置为传引用:

//In NativeCode.cpp

#include "NativeCode.h"

 

extern "C"

{

    float AddRefFun(float* x, float* y)

    {

        *x = 3.2;

        *y = 2.4;

        return *x + *y;

    }

}

//In Unity C# Script

using UnityEngine;

using System.Collections;

using System.Runtime.InteropServices

 

public class CallNativeCode : MonoBehaviour 

{

    [DllImport("NativeEngine")]

    public static extern floatAddRefFun(ref float x, ref float y);

 

    void Start () 

    {

        float x = 2.5f;

        float y = 8.8f;

        float addResult =AddRefFun(ref x, ref y);

        Debug.Log("X is:" + x);

        Debug.Log("Y is:" + y);

        Debug.Log("Addresult is: " + addResult);          

    }

    void Update () 

    {

    }

}

结果可以看出,Native code修改了C#传入的参数:

 

 

最后总结了一下C#和Native code之间常见不同参数类型的调用方法:

a、参数为基本类型,如int, float, char等:

//In Native Code

void Fun(int value);

void Fun(float value);

void Fun(char ch);

//In Unity C# Script

[DllImport("xxx")]

public static extern void Fun(Int32 value);

[DllImport("xxx")]

public static extern void Fun(float value);

[DllImport("xxx")]

public static extern void Fun(char ch);

 

b、参数为基本指针类型,如int*, float*, char*等:

//In Native Code

void Fun(int* value);

void Fun(float* value);

void Fun(char* ch);

//In Unity C# Script

[DllImport("xxx")]

public static extern void Fun(ref Int32 value);

[DllImport("xxx")]

public static extern void Fun(ref float value);

Native code参数为char*,C#有多种声明方式:

[DllImport("xxx")]

public static extern void Fun(string ch);//ch的内容不会被更改

[DllImport("xxx")]

public static extern void Fun(StringBuilder ch);//ch的内容可以被更改

 

 c、参数为结构体,C#中一般使用MarshalAs属性来指示如何在托管代码和非托管代码之间传递数据(注意:定义结构体时应该考虑字节对其的问题):

//In Native Code

struct stEvent

{

    int value; //基本类型

    char ch; //基本类型

    int number[100]; //数组

    char buffer[100]; //字符串数组

};

void Fun(stEvent tEvent);

//In Unity C# Script

[StructLayout(LayoutKind.Sequential)]

public struct stEvent

{

    public Int32 value;

    public char ch;

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]

    public Int32[] number;

   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]

    public char[] buffer;

}

[DllImport("xxx")]

public static extern void Fun(stEvent tEvent);

 

d、参数为结构体指针

//In Native Code

void Fun(stEvent* ptEvent);

//In Unity C# Script

[DllImport("xxx")]

public static extern void Fun(ref stEvent ptEvent);

 

二、Native Code调用Unity C#

通常在Native Code完成了某个任务后需要及时通知给游戏,从层级来看可以视为下层封装的函数同步回调至游戏上层,实现思路如下:

我们知道C++中有函数指针回调的概念,通过函数指针可以实现函数回调,C#是托管语言,没有指针的概念,但是有个和函数指针很类似的委托(delegate),我们可以在C#中定义一个委托,并将这个委托设置进Native Code,这样调用Native的函数指针就相当于直接调用上层C#的函数了,直接看代码:

//In NativeCode.h

#ifndef __NATIVE_CODE__

#define __NATIVE_CODE__

extern "C"

{

 typedef void(*CALLBACK)(const char*);

 staticCALLBACK callback;    //Native层的全局回调指针

 voidSetCallBackAndRun(CALLBACK cb);

}

#endif

//In NativeCode.cpp

#include "NativeCode.h"

extern "C"

{

 voidSetCallBackAndRun(CALLBACK cb)

 {

    callback = cb;    //cb相当于C#中的函数地址

    callback("Hello World!");

 }

}

//In Unity C# Script

using UnityEngine;

using System.Collections;

using System.Runtime.InteropServices;

public class CallNativeCode : MonoBehaviour 

{

    public delegate voidcallbackDelegate(string str);

    [DllImport("NativeEngine")]

    public static extern voidSetCallBackAndRun(callbackDelegate cb);

    void Start () 

    {

        SetCallBackAndRun(newcallbackDelegate(PrintString)); 

    }

    void PrintString(string str)

    {

        Debug.Log("CallPrintString");

        Debug.Log(str);

    }

    void Update () 

    {

    }

}

从LogCat看到,Native Code通过callback成功回调到了C#中的PrintString方法中:


先写到这,下次再来总结UnityC#和Java之间的调用吧。

阅读(3696) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

hagendashao2016-05-02 11:57:44

楼主你好~!请问Android.mk的几个问题:
LOCAL_LDLIBS     := -llog -landroid 编译选项是必备的吗,我用该选项提示没有找到android的lib。

如果不是必备的,我去掉这个选项后,编译出了armeabi版本的so库。在Android调用运行提示!error: No JNI_OnLoad() found.

如果是必备的,那android的lib应该是在哪里呢?