相比较托管调用非托管,回调函数方式稍微复杂一些。回调函数非常适合重复执行的任务、异步调用等情况下使用。
由上面的介绍可以知道CLR提供了C#程序运行的环境,与非托管代码的C/C++交互调用也由它来完成。CLR提供两种用于与非托管C/C++代码进行交互的机制:
平台调用依赖于元数据在运行时查找导出的函数并封送(Marshal)其参数。 下图显示了这一过程。
注意:1.除涉及回调函数时以外,平台调用方法调用从托管代码流向非托管代码,而绝不会以相反方向流动。 虽然平台调用的调用只能从托管代码流向非托管代码,但是数据仍然可以作为输入参数或输出参数在两个方向流动。2.图中DLL表示动态库,Windows平台指.dll文件、Linux/Android指.so文件、Mac OS X指.dylib/framework文件、iOS中只能使用.a。后文都使用DLL代指,并且DLL使用C/C++编写。
当"平台调用"调用非托管函数时,它将依次执行以下操作:
注意
只在第一次调用函数时,才会查找和加载 DLL 并查找函数在内存中的地址。iOS中使用的是.a已经静态打包到最终执行文件中。
2.2Android插件
Java同样提供了这样一个扩展机制JNI(Java Native Interface),能够与C/C++互相通信。
注:
C#/Java都可以和C/C++通信,那么通过编写一个C/C++模块作为桥接,就使得C#与Java通信成为了可能,如下图所示:
注:C/C++桥接器本身跟Unity3D没有直接关系,不属于Android和Unity3D,图中放在Unity3D中是为了代指libunity.so中实现的桥接器以表示真实的情况。
C/C++要访问的Java代码,必须要能访问到Java虚拟机,获取虚拟机有2中方法:
所以,我们只需要在编写C/C++桥接器so的时候定义JNI_OnLoad(JavaVM* jvm, void* reserved)方法即可,然后把JavaVM指针保存起来作为上下文使用。
获取到JavaVM之后,还不能直接拿到JNI函数去获取Java代码,必须通过线程关联的JNIENV指针去获取。所以,作为一个好的开发习惯在每次获取一个线程的JNI相关功能时,先调用AttachCurrentThread();又或者每次通过JavaVM指针获取当前的JNIENV:java_vm->GetEnv((void**)&jni_env, version),一定是已经附加到JavaVM的线程。通过JNIENV可以获取到Java的代码,例如你想在本地代码中访问一个对象的字段(field),你可以像下面这样做:
类似地,要调用一个方法,你step1.得获得一个类对象的引用obj,step2.是方法methodID。这些ID通常是指向运行时内部数据结构。查找到它们需要些字符串比较,但一旦你实际去执行它们获得字段或者做方法调用是非常快的。step3.调用jni_env->CallVoidMethodV(obj, methodID, args)。
从上面的示例代码,我们可以看出使用原始的JNI方式去与Android(Java)插件交互是多的繁琐,要自己做太多的事情,并且为了性能需要自己考虑缓存查询到的方法ID,字段ID等等。幸运的是,Unity3D已经为我们封装好了这些,并且考虑了性能优化。Unity3D主要提供了一下2个级别的封装来帮助高效编写代码: