Unity中的 原生插件/平台交互 原理

声明:  内容摘录自:  http://gad.qq.com/article/detail/20273

        总结下来,其实主要是C/C++这种操作系统级别的语言作为桥接, 而且几乎所有高级编程语言都支持C/C++扩展,交互。


原生插件/平台交互

虽然大多时候使用Unity3D进行游戏开发时,只需要使用C#进行逻辑编写。但有时候不可避免的需要使用和编写原生插件,例如一些第三方插件只提供C/C++原生插件、复用已有的C/C++模块等。有一些功能是Unity3D实现不了,必须要调用Android/iOS原生接口,比如获取手机的硬件信息(UnityEngine.SystemInfo没有提供的部分)、调用系统的原生弹窗、手机震动等等



1 、C/C++插件



编写和使用原生插件的几个关键点:

创建C/C++原生插件

 

  • 导出接口必须是C ABI-compatible函数
  • 函数调用约定
     


在C#中标识C/C++的函数并调用

 

  • 标识 DLL 中的函数。至少指定函数的名称和包含该函数的 DLL 的名称。
  • 创建用于容纳 DLL 函数的类。可以使用现有类,为每一非托管函数创建单独的类,或者创建包含一组相关的非托管函数的一个类。
  • 在托管代码中创建原型。使用 DllImportAttribute 标识 DLL 和函数。 用 static 和 extern 修饰符标记方法。  
  • 调用 DLL 函数。像处理其他任何托管方法一样调用托管类上的方法。
     


在C#中创建回调函数,C/C++调用C#回调函数

 

  • 创建托管回调函数。
  • 创建一个委托,并将其作为参数传递给 C/C++函数。平台调用会自动将委托转换为常见的回调格式。
  • 确保在回调函数完成其工作之前,垃圾回收器不会回收委托。
     


那么C#与原生插件之间是如何实现互相调用的呢?

  1.将源码编译为托管模块;

  2.将托管模块组合为程序集;

  3.加载公共语言运行时CLR;

  4.执行程序集代码。

注:CLR(公共语言运行时,Common LanguageRuntime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

为了提高平台的可靠性,以及为了达到面向事务的电子商务应用所要求的稳定性级别,CLR还要负责其他一些任务,比如监视程序的运行。按照.NET的说法,在CLR监视之下运行的程序属于"托管"(managed)代码,而不在CLR之下、直接在裸机上运行的应用或者组件属于"非托管"(unmanaged)的代码。

这几个过程我总结为下图:

 

                                                          图.NET上的程序运行


             回调函数是托管代码C#中的定义的函数,对回调函数的调用,实现从非托管C/C++代码中调用托管C#代码。那么C/C++是如何调用C#的呢?大致分为2步,可以用下图表示:

 


将回调函数指针注册到非托管C/C++代码中(C#中回调函数指委托delegate)

调用注册过的托管C#函数指针

相比较托管调用非托管,回调函数方式稍微复杂一些。回调函数非常适合重复执行的任务、异步调用等情况下使用。

由上面的介绍可以知道CLR提供了C#程序运行的环境,与非托管代码的C/C++交互调用也由它来完成。CLR提供两种用于与非托管C/C++代码进行交互的机制

 

  • 平台调用(Platform Invoke,简称PInvoke或者P/Invoke),它使托管代码能够调用从非托管DLL中导出的函数。
  • COM 互操作,它使托管代码能够通过接口与组件对象模型 (COM) 对象交互。考虑跨平台性,Unity3D不使用这种方式
     


平台调用依赖于元数据在运行时查找导出的函数并封送(Marshal)其参数。下图显示了这一过程。

 

         注意:

1. 除涉及回调函数时以外,平台调用方法调用从托管代码流向非托管代码,而绝不会以相反方向流动。虽然平台调用的调用只能从托管代码流向非托管代码,但是数据仍然可以作为输入参数或输出参数在两个方向流动。

2. 图中DLL表示动态库,Windows平台指.dll文件Linux/Android指.so文件、Mac OS X指.dylib/framework文件、iOS中只能使用.a后文都使用DLL代指,并且DLL使用C/C++编写

当"平台调用"调用非托管函数时,它将依次执行以下操作:

  • 查找包含该函数的DLL。
  • 将该DLL加载到内存中。
  • 查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据(参数)
  • 将控制权转移给非托管函数。
     

注意:  只在第一次调用函数时,才会查找和加载 DLL并查找函数在内存中的地址。iOS中使用的是.a已经静态打包到最终执行文件中。

 

 



2、 Android插件



Java同样提供了这样一个扩展机制JNI(Java NativeInterface),能够与C/C++互相通信。

注:

  • JNI wiki-https://en.wikipedia.org/wiki/Java_Native_Interface 这里不深入介绍JNI,有兴趣的可以自行去研究。如果你还不知道JNI也不用怕,就像Unity3D使用C/C++库一样,用起来还是比较简单的,只需要知道这个东西即可。并且Unity3D对C/C++桥接器这块做了封装,提供AndroidJNI/AndroidJNIHelper/AndroidJavaObject/AndroidJavaClass/AndroidJavaProxy方便使用等,具体使用后面在介绍。JNI提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互,保证本地代码能工作在任何Java虚拟机环境下。"

 

  • 作为知识扩展,提一下Android Java虚拟机。Android的Java虚拟机有2个,最开始是Dalvik,后面Google在Android 4.4系统新增一种应用运行模式ART。ART与Dalvik 之间的主要区别是其具有提前 (AOT) 编译模式。 根据 AOT 概念,设备安装应用时,DEX 字节代码转换仅进行一次。 相比于 Dalvik,这样可实现真正的优势 ,因为 Dalvik 的即时 (JIT) 编译方法需要在每次运行应用时都进行代码转换。下文中用Java虚拟机代指Dalvik/ART。
     


            C#/Java都可以和C/C++通信,那么通过编写一个C/C++模块作为桥接,就使得C#与Java通信成为了可能,如下图所示:

 

       :C/C++桥接器本身跟Unity3D没有直接关系,不属于Android和Unity3D,图中放在Unity3D中是为了代指libunity.so中实现的桥接器以表示真实的情况。


通过JNI既可以用于Java代码调用C/C++代码,也可用于C/C++代码与Java(Dalvik/ART虚拟机)的交互。JNI定义了2个关键概念/结构:JavaVM、JNIENV。JavaVM提供虚拟机创建、销毁等操作,Java中一个进程可以创建多个虚拟机,但是Android一个进程只能有一个虚拟机。JNIENV是线程相关的,对应的是JavaVM中的当前线程的JNI环境,只有附加(attach)到JavaVM的线程才有JNIENV指针,通过JNIEVN指针可以获取JNI功能,否则不能够调用JNI函数。



         C/C++要访问的Java代码,必须要能访问到Java虚拟机,获取虚拟机有2中方法:

  • 在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved),第一个参数会传入JavaVM指针。
  • 在C/C++中调用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)创建JavaVM指针 


             所以,我们只需要在编写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),你可以像下面这样做:

1.对于类,使用jni_env->FindClass获得类对象的引用

2.对于字段,使用jni_env->GetFieldId获得字段ID

3.使用对应的方法(例如jni_env->GetIntField)获取字段的值

          类似地,要调用一个方法,你

step1.得获得一个类对象的引用obj,

  • step2.是方法methodID。这些ID通常是指向运行时内部数据结构。查找到它们需要些字符串比较,但一旦你实际去执行它们获得字段或者做方法调用是非常快的。step3.调用jni_env->CallVoidMethodV(obj,methodID,args)。

               从上面的示例代码,我们可以看出使用原始的JNI方式去与Android(Java)插件交互是多的繁琐,要自己做太多的事情,并且为了性能需要自己考虑缓存查询到的方法ID,字段ID等等。幸运的是,Unity3D已经为我们封装好了这些,并且考虑了性能优化。Unity3D主要提供了一下2个级别的封装来帮助高效编写代码

     


 

:Unity3D中对应的C/C++桥接器包含在libunity.so中。

 

  • Level 1:AndroidJNI、AndroidJNIHelper,原始的封装相当于我们上面自己编写的C# Wrapper。AndroidJNIHelper和AndroidJNI自动完成了很多任务(指找到类定义,构造方法等),并且使用缓存使调用java速度更快。AndroidJavaObject和AndroidJavaClass基于AndroidJNIHelper和AndroidJNI创建,但在处理自动完成部分也有很多自己的逻辑,这些类也有静态的版本,用来访问java类的静态成员。http://docs.unity3d.com/ScriptReference/AndroidJNI.htmlhttp://docs.unity3d.com/ScriptReference/AndroidJNIHelper.html
  • Level 2:AndroidJavaObject、AndroidJavaClass、AndroidJavaProxy,这个3个类是基于Level1的封装提供了更高层级的封装使用起来更简单,这个在第三部分详细介绍。
     


3、iOS插件



         iOS编写插件比Android要简单很多,因为Objective-C也是C-compatible的,完全兼容标准C语言。这些就可以非常简单的包一层 extern"c"{},用C语言封装调用iOS功能,暴露给Unity3D调用。并且可以跟原生C/C++库一样编成.a插件。C#与iOS(Objective-C)通信的原理跟C/C++完全一样:

 


除此之外,UnityiOS支持插件自动集成方式。所有位于Asset/Plugings/iOS文件夹中后缀名为.m , .mm , .c ,.cpp的文件都将自动并入到已生成的Xcode项目中。然而,最终编进执行文件中。后缀为.h的文件不能被包含在Xcode的项目树中,但他们将出现在目标文件系统中,从而使.m/.mm/.c/.cpp文件编译。这样编写iOS插件,除了需要对iOSObjective-C有一定了解之外,与C/C++插件没有差异,反而更简单。
 









  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值