前段时间开发UnityAndroid项目的时候需要在UnityC#Script、NativeCode和Java三个模块之间相互调用,因此总结了一下这几种代码之间的调用方法,以防忘记。这篇先看看UnityC#与NativeCode之间的相互调用。 一、UnityC#调用NativeCode 最常见的应该是在游戏的C#脚本中调用NativeCode中的方法,从层级来看可以视为游戏上层调用下层封装的函数,具体实现如下: 1、编写C、C++代码,导出相应函数或变量,下面代码是.h和.cpp中对应的代码: //InNativeCode.h #ifndef__NATIVE_CODE__ #define__NATIVE_CODE__ externC { floatAddFun(floatx,floaty); } #endif //InNativeCode.cpp #includeNativeCode.h externC { floatAddFun(floatx,floaty) { returnx+y; } } 2、编写Android.mk,将mk文件和c、cpp文件放到jni文件夹下,使用NDK(ndk-build命令)将NativeCode编译成.so文件。开发过Android项目的同学应该都非常熟悉Android.mk和Application.mk,这里就不详细介绍了,以下是Android.mk中的内容: LOCAL_PATH:=$(callmy-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可能远比这个复杂,例如Nativecode中引用了其他库文件或者启用一些编译优化选项等等。 3、将编译好的libNativeEngine.so文件放入Unity工程Asset\Plugins\Android目录下,如果在Eclipse中调试则放入Android项目下的libs\armeabi-v7a目录下。 4、在Unity中编写C#脚本,引用so文件,声明导出的接口,代码如下: //InUnityC#Script usingUnityEngine; usingSystem.Collections; usingSystem.Runtime.InteropServices; publicclassCallNativeCode:MonoBehaviour { [DllImport(NativeEngine)] publicstaticexternfloatAddFun(floatx,floaty); voidStart() { floataddResult=AddFun(2.5f,8.8f); Debug.Log(Addresultis:+addResult); } voidUpdate() { } } 通过DllImport引用Native模块的名字,注意需要去掉lib前缀和.so后缀,当然DllImport还包含一些缺省参数,这里就不一一介绍了,从字面上也很好理解: 5、通过Unity编译打包生成apk,安装并运行,使用adblogcat命令或者Eclipse的LogCat查看结果,下图可以看到UnityC#调进了NativeCode的函数: 方法很简单,但是也有一些需要注意的地方: (1)如果程序提示找不到函数入口点,请查看导出的函数名字是否和声明的名字一致,如果不使用extern“C”,g++会对导出的函数进行签名,导出的接口除了函数名字还包含一些其他的字符。此时可以将DllImport的EntryPoint设置为包含其他字符的函数名字,或者在Nativecode导出时加上extern“C”,使导出的函数名和声明的函数名一致。Tips:extern“C”告诉链接器在链接的时候用C函数规范来链接,主要原因是C++和C程序编译完成后在目标代码中命名规则不同。 (2)最常见的问题应该是函数参数的传递上了,传值的还好,native层不会去修改传入的参数,但如果需要native层修改C#传入的参数就应该设置为传引用: //InNativeCode.cpp #includeNativeCode.h externC { floatAddRefFun(float*x,float*y) { *x=3.2; *y=2.4; return*x+*y; } } //InUnityC#Script usingUnityEngine; usingSystem.Collections; usingSystem.Runtime.InteropServices publicclassCallNativeCode:MonoBehaviour { [DllImport(NativeEngine)] publicstaticexternfloatAddRefFun(reffloatx,reffloaty); voidStart() { floatx=2.5f; floaty=8.8f; floataddResult=AddRefFun(refx,refy); Debug.Log(Xis:+x); Debug.Log(Yis:+y); Debug.Log(Addresultis:+addResult); } voidUpdate() { } } 结果可以看出,Nativecode修改了C#传入的参数: 最后总结了一下C#和Nativecode之间常见不同参数类型的调用方法: a、参数为基本类型,如int,float,char等: //InNativeCode voidFun(intvalue); voidFun(floatvalue); voidFun(charch); //InUnityC#Script [DllImport(xxx)] publicstaticexternvoidFun(Int32value); [DllImport(xxx)] publicstaticexternvoidFun(floatvalue); [DllImport(xxx)] publicstaticexternvoidFun(charch); b、参数为基本指针类型,如int*,float*,char*等: //InNativeCode voidFun(int*value); voidFun(float*value); voidFun(char*ch); //InUnityC#Script [DllImport(xxx)] publicstaticexternvoidFun(refInt32value); [DllImport(xxx)] publicstaticexternvoidFun(reffloatvalue); Nativecode参数为char*,C#有多种声明方式: [DllImport(xxx)] publicstaticexternvoidFun(stringch);//ch的内容不会被更改 [DllImport(xxx)] publicstaticexternvoidFun(StringBuilderch);//ch的内容可以被更改 c、参数为结构体,C#中一般使用MarshalAs属性来指示如何在托管代码和非托管代码之间传递数据(注意:定义结构体时应该考虑字节对其的问题): //InNativeCode structstEvent { intvalue;//基本类型 charch;//基本类型 intnumber[];//数组 charbuffer[];//字符串数组 }; voidFun(stEventtEvent); //InUnityC#Script [StructLayout(LayoutKind.Sequential)] publicstructstEvent { publicInt32value; publiccharch; [MarshalAs(UnmanagedType.ByValArray,SizeConst=)] publicInt32[]number; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=)] publicchar[]buffer; } [DllImport(xxx)] publicstaticexternvoidFun(stEventtEvent); d、参数为结构体指针 //InNativeCode voidFun(stEvent*ptEvent); //InUnityC#Script [DllImport(xxx)] publicstaticexternvoidFun(refstEventptEvent); 二、NativeCode调用UnityC# 通常在NativeCode完成了某个任务后需要及时通知给游戏,从层级来看可以视为下层封装的函数同步回调至游戏上层,实现思路如下: 我们知道C++中有函数指针回调的概念,通过函数指针可以实现函数回调,C#是托管语言,没有指针的概念,但是有个和函数指针很类似的委托(delegate),我们可以在C#中定义一个委托,并将这个委托设置进NativeCode,这样调用Native的函数指针就相当于直接调用上层C#的函数了,直接看代码: //InNativeCode.h #ifndef__NATIVE_CODE__ #define__NATIVE_CODE__ externC { typedefvoid(*CALLBACK)(constchar*); staticCALLBACKcallback;//Native层的全局回调指针 voidSetCallBackAndRun(CALLBACKcb); } #endif //InNativeCode.cpp #includeNativeCode.h externC { voidSetCallBackAndRun(CALLBACKcb) { callback=cb;//cb相当于C#中的函数地址 callback(HelloWorld!); } } //InUnityC#Script usingUnityEngine; usingSystem.Collections; usingSystem.Runtime.InteropServices; publicclassCallNativeCode:MonoBehaviour { publicdelegatevoidcallbackDelegate(stringstr); [DllImport(NativeEngine)] publicstaticexternvoidSetCallBackAndRun(callbackDelegatecb); voidStart() { SetCallBackAndRun(newcallbackDelegate(PrintString)); } voidPrintString(stringstr) { Debug.Log(CallPrintString); Debug.Log(str); } voidUpdate() { } } 从LogCat看到,NativeCode通过callback成功回调到了C#中的PrintString方法中: 先写到这,下次再来总结UnityC#和Java之间的调用吧。 Gad给大家拜年啦! Gad-GameDev∣腾讯游戏开发者平台 长按,识别北京都有哪些白癜风医院北京那个医院治白癜风最好
|