资源大湿 发表于 2020-11-23 20:33

Unity/C++混合编程全攻略!——基础准备

在Unity游戏的开发当中,我们的游戏项目变得越来越复杂,以至于有些项目功能必须通过依赖库来进行实现。
比如,我们在手游开发中用到的toLua、FMOD或者是其他的插件,都是通过调用Native dll来实现一些复杂的功能。


那么我们应该如何使用C#来对C++进行调用呢。
了解C#的人都知道,C#是运行在CLR之上被托管的,而C++则并没有被托管。
这时候我们就需要通过一种机制来对C#的对象进行封送。
.Net互操作的三种方式

目前.Net平台中托管环境调用非托管环境有三种方法:
P/InvokeC++ InteropCOM Interop
这三种方法当中,C++ Interop是针对托管C++使用的方法(说实话C++/CLI感觉没啥前途),COM Interop则是针对Window软件开发而采用的方式。所以我们只剩下一种解决方案:也就是PInvoke来进行托管环境与非托管环境的互操作。
当然也可以使用C++/CLI作为中间层,但是这样就没太大必要了,而且Mono好像也不支持……
所以,接下来的一系列内容都将从PInvoke进行展开。
如何使用PInvoke是接下来内容的重点。
不过由于PInvoke本身内容也并不少,所以在这里我也就简单介绍一下其使用的方式,更详细的内容可以去查看官方文档,或者是下一个《精通.Net互操作》的pdf来阅读就可以了。在以后的文章中可能还会介绍Swig工具的使用,来帮助大家更轻松地在Unity中进行C#与C++的混编。
现在我们只在Windows平台下简单使用PInvoke可以更好开始进行学习。


现在我们建立一个简单的项目,设计一个加法的接口,每调用一次则加一并显示在界面上。






从C++中导出函数

首先我们建立一个简单的Win32工程。
建立一个Testlib.h与Testlib.cpp




工程目录


__declspec(dllexport)代表需要导出的函数,需要放在函数定义的前面。
extern “C”表示以C语言方式进行导出
__stdcall表示以标准方式调用。由于定义了extern “C”与__stdcall,编译器会对函数名进行整理,在库中会独立对应一个标识符,C#也会根据相同的规则去寻找符合条件的函数以进行调用。
在非托管dll导出的时候往往会用到不同的调用方式,所以相同的在C#中也可以通过调整DllImport中的CallingConvention进行指定以保证找到相应的函数。




TestLib.h


cpp文件中同样要如此定义




TestLib.cpp




因为是基础教程,这里也简单介绍下生成吧,如果要包含外部静态库的话大家就自行百度谷歌吧,也是比较容易的,因为我们目前只涉及简单的接口对接的入门就不进行赘述了。
右键点击工程,选择属性,选择生成动态库dll




设置项目属性
选择平台类型,




选择平台类型
我们右键工程进行生成,分别生成x86与x64的版本。






分别将生成的.dll与.pdb放入到对应的文件夹中。x86放入x86文件夹,x64放入x86_64文件夹下。
我在这里将导出的dll与pdb都重命名成了CppInterface。
在C#中编写接口

在C#中想要调用我们编写的C++函数也很简单:
在函数定义的时候要在前面加上定义extern,告诉编译器该函数在外部定义,不需要函数体,而且需要定义为静态函数,因为非托管函数在导出之后都可以进行直接调用无需实例化就可以进行使用了。最后就是要加上DllImport,Unity会首先搜索Plugin,如果在Windows平台会去搜索系统目录,如果仍未找到就会抛出DllNotFound异常。
注意这里的DllImport中的CppInterface是导入的dll名,并非类名。






在实际开发时往往会涉及到更多的细节,例如指定字符集、指定调用方式、指定调用入口等等,特别是对字符串、结构体等对象的封送也会有不同的选择,这里为了快速入门也就不再提及了。日常开发中也不需要特别进行记忆,可以在需要使用到复杂对象的时候再去查询相关的规则。《精通.Net互操作》这样的书也可以作为工具书来进行使用。
在C#中进行调用

就像普通调用C#函数一样就行了,下面是我编写的更新界面的代码。




用于更新UI的MonoBehavior


CppInterface便是我们编写的接口。






在Unity下调试Native Dll

我们刚刚知道我们将.pdb文件导入到了Unity下,那么这个文件就是记录着调试信息的文件。
首先我们打开Unity工程,右键工程属性,选择允许调试NativeDll。
最后就选择选项,附加进程:






这个时候我们就可以将我们的调试附加到Unity的进程上了。






点击附加之后我们就可以添加断点了。


总结

如果只是简单功能调用的话,大家应该已经能够在Unity中简单调用非托管代码了。本文没有涉及任何复杂封送操作与内存管理,纯粹帮大家迈出第一步!
在网上也有其他关于C#调用非托管代码的文章,相较我的文章也会详细一些,涉及到了指针封送以及函数指针封送,大家有兴趣也可以去读一读:
http://www.cnblogs.com/warensoft/archive/2011/12/09/warenosoft3d.html
不过我更推荐大家可以去下一本《精通.Net互操作》的pdf来读一读,时不时可以作为工具书用于参考,因为.Net互操作并非经常会使用到的编程技巧,所以也没有必要对其进行死记硬背。
我在之后的文章中会谈一谈
C++接口生成工具Swig以及与Unity的对接跨平台编译非托管库并且Unity中如何更好地进行协作,以及一些混合编程开发模式的思考,为什么要使用混合编程?混合编程在项目实践中的经历与分享。
实际项目当中绝对不推荐大家自己去手写互操作接口,能用工具就用工具,工具不能用就自己写生成工具,如果lua作为胶水语言无法自动生成栈操作代码的话大家肯定也很崩溃吧!
在项目中慢慢根据需求来使用各种互操作的封送以及内存管理,对互操作的掌握程度也会很快提升,比死记硬背要更有效。


大家有兴趣可以看看我的小站~ 原文地址:Unity/C++混合编程全攻略!--基础准备 | Unity之路
页: [1]
查看完整版本: Unity/C++混合编程全攻略!——基础准备