使用 P/Invoke 从 Unity 高效地与非托管代码对话
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 P/Invoke 从 Unity 高效地与非托管代码对话相关的知识,希望对你有一定的参考价值。
参考技术A在上一篇关于 P/INVOKE 的文章中,我们学习了如何从 Unity 内部调用非托管方法,以及如何跨互操作屏障传递参数和返回值。
现在让我们开始DllImport在我们的代码库中到处撒播以获得乐趣和利润!对?好…
那么让我们来看看我们是如何在 Baracoda 的项目中应对这些挑战的!
假设您有一个针对静态库编译的 ios 代码的现有版本,libios_plugin.a. 因此,让我们添加插件的 android 版本libandroid_plugin.aar,其中包含内部libandroid_plugin.so.
然后在尝试执行本机代码时观察它失败:
我们的第一个问题是,在 iOS 上,我们使用的是静态链接库,它要求传递给的名称DllImport是__Internal.
但是,我们有一个名为 的 Android 版本的动态链接库libandroid_plugin.so,并且该名称需要在DllImport.
我们可以使用///指令和Unity的平台脚本符号来使用条件编译#if,如#elif或选择#else将基于当前平台编译的属性的版本。#endifUNITY_ANDROIDUNITY_IOS
万岁,它有效!但是,呃……如果我们需要更多方法,那就太冗长了,而且我们现在只支持 2 个平台。那么我们能做些什么来避免重复这个巨大的块呢?
好吧,库名称必须是一个常量字符串,所以const string也可以。让我们重构:
我们编写的内容适用于单个类,但随着 API 表面变大,我们可能希望将这些本地方法分组为对象——根据 单一 责任 原则—— 每个对象代表我们想要的不同服务访问。
但是因为这个LIBNAME变量现在是私有的,所以我们必须在每个类中复制/粘贴指令,这与 D on\'t R epeat Y ourself原则相矛盾。所以让我们创建另一个类来为我们保存它!
导入现在看起来像这样:
不太冗长,易于阅读,易于扩展。现在我们肯定完成了,对吧?
那么,你能为最终调用那些非托管方法的特性编写单元测试吗?
因此,也许您可 以针对实际实现编写测试,因为本机库只是提供一些业务逻辑,但也许您首先拥有它的原因是因为它使您可以访问外部资源?也许没有可用的桌面版本的库,测试甚至无法在编辑器中运行?
无论如何,这个非托管代码应该被考虑在被测单元 之外,但是您仍然需要访问它在真实代码中提供的服务。
在这种情况下,最好的解决方案通常是将服务的实现与其接口分离。
现在我们可以为测试实例化一个假的,但仍然将真实的实现用于生产!
这仍然是一个玩具示例,但是因为我们没有创建非托管对象的实例,而只是在讨论似乎是自由函数或静态方法的东西。
有时只有一个服务实例可以与之对话是有意义的,有时则不然,您需要能够动态地创建新实例。那么,我们如何从 C# 中与它们交互呢?
在 C 或 C++ 中动态创建对象时,程序将分配一些内存,在其中构造对象,并返回指向它的 指针 。
等等,别跑!没关系!
在编组指向 C# 的非托管指针时,运行时可以将其转换为IntPtr. 您可以将其视为非托管对象的不透明句柄,除了将其交还给非托管端外,您不能直接使用它做很多事情。
所以现在创建 C# 类的新实例也会创建非托管对象的新实例,然后我们可以对其进行方法调用。甜的!
我们只是忘记了一个细节:我们创建了一个 非托管 对象,这意味着 GC 不知道如何回收它,甚至默认 都不尝试!
因此,当 C# 类被垃圾回收时,让我们停止 泄漏该非托管对象。
在 C# 中,类发出需要清理步骤的信号的首选方式是实现IDisposable接口。
假设有一个函数用于销毁我们的非托管对象,它的 API 如下所示:
现在实现Dispose()非常简单:
随着这一变化,我们现在可以很好地管理我们的资源并执行必要的清理工作。
然而,既然我们已经引入了手动资源管理,我们就会冒着尝试引用已被释放的非托管对象的风险,所以让我们让它更安全!
幸运的是,C# 标准库正是我们所需要的:SafeHandle!它本来是用来保持 Win32 句柄的,但它的 API 和终结保证使它非常适合我们的目的。
从 继承时SafeHandle,需要做 3 件事。
因此,这就是SafeHandle我们示例中自定义的样子:
现在我们只需要在任何地方都替换IntPtr为 with CameraServiceHandle,除了在销毁方法中仍然需要一个IntPtr.
我们的 C# 端服务现在在内部使用句柄:
我们已经做到了!
我们现在已经从到处添加临时[DllImport] static extern方法(冗长、难以测试且不一定资源安全)转变为专门设计的方法。
我们有一些小包装;它们封装良好,不会阻止对依赖它们的代码进行测试,易于添加跨平台支持,并且我们现在有系统的方法来保证与非托管对象交互时的资源和类型安全!
这就是我们在 Baracoda 如何使用 P/Invoke 的导览!
利用 P/Invoke 使我们能够编写跨平台库并与之交互,从而将我们研发团队在机器学习和计算机视觉方面的内部知识带到我们的 Unity 游戏 中!
我们计划发布更多 Unity 开发者内容,敬请期待!
Unity中的Invoke
MonoBehaviour.Invoke 延迟调用
方法签名: void Invoke(string methodName, float time);
在time秒后,延迟调用方法methodName。
Invoke() 方法是 Unity3D 的一种委托机制
如: Invoke(“SendMsg”, 5); 它的意思是:5 秒之后调用 SendMsg() 方法;
使用 Invoke() 方法需要注意 3点:
1 :它应该在 脚本的生命周期里的(Start、Update、OnGUI、FixedUpdate、LateUpdate)中被调用;
2:Invoke(); 不能接受含有 参数的方法;
3:在 Time.ScaleTime = 0; 时, Invoke() 无效,因为它不会被调用到
Invoke() 也支持重复调用:InvokeRepeating(“SendMsg”, 2 , 3);
这个方法的意思是指:2 秒后调用 SendMsg() 方法,并且之后每隔 3 秒调用一次 SendMsg () 方法
以上是关于使用 P/Invoke 从 Unity 高效地与非托管代码对话的主要内容,如果未能解决你的问题,请参考以下文章