怎样在Unity中Reload原生插件
原文来自 在Unity编辑器使用原生插件,Dll之类的,经常会遇到一个问题,替换插件时,Unity会提示正在使用,无法替换,这是因为Unity一旦点了Play,加载了Dll,就不会去卸载。要解决这个问题也很简单,那就是先关掉Unity,然后替换Dll,然后再打开Unity。对于插件的使用者,倒不是什么大问题,但是如果你是插件的开发者,需要频繁的修改和测试插件,那就有点悲惨了。
这篇博客将介绍一个我认为不错的解决方案,有很多开发者已经实现了这个或者类似的解决方案,但是在Google或者Github上很难找到。
TLDR
我写了一个200行的代码,在OnAwake时,会加载所有的Dll,在OnDestroy时会卸载所有的Dll,我们自己去管理Dll的加载和卸载,就可以做到停止Play时,卸载掉所有的Dll,这样就可以在不关闭Unity的情况下,替换Dll。
要做到这个,就不能用 PInvoke 去调用,而是用类似的方式,达到相同的目的。
完整的工程代码在 Github。但是我们只需要一个文件就可以 NativePluginLoader.cs
如何使用:
将 NativePluginLoader.cs 放到你的工程中在场景中新建一个GameObject,然后挂载 NativePluginLoader.cs定义一个类,用于声明所有的插件方法,例如命名为 FooPlugin,然后给这个类赋予 PluginAttr 属性给 delegate 添加 PluginFunctionAttr 属性,示例代码如下
// C# 代码
public static class FooPlugin
{
public static Sum sum = null;
public delegate float Sum(float a, float b);// 原生方法的 delegate
}
void CoolFunc() {
float s = FooPlugin.sum(1.0, 2.0);
}
// 这里是原生C代码中的接口, 最后打成Dll给Unity调用
// my_cool_plugin.h
extern "C" {
__declspec(dllexport) float sum(float a, float b);
}
关于 PInvoke
调用原生插件的常规方法是通过 PInvoke
public static class FooPlugin_PInvoke {
extern static public float sum(float a, float span class="n">b);
}
但是使用 PInvoke 会有一个问题,就是Dll永远不会 unload。解决方案就是我们自己控制加载和卸载,卸载将使用下面的接口
static class SystemLibrary
{
static public extern IntPtr LoadLibrary(string lpFileName);
static public extern bool FreeLibrary(IntPtr hModule);
static public extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
}
自动完成加载卸载工作
NativePluginLoader.cs 是一个单例类,负责完成所有的加载和卸载工作。
主要的加载代码如下
// Loop over all assemblies
foreach (var assembly in assemblies) {
// Loop over all types
foreach (var type in assembly.GetTypes()) {
// Consider types with the attribute PluginAttr
var typeAttr = type.FindAttribute(typeof(PluginAttr));
if (!typeAttr)
continue;
// Load the Plugin (this is cached)
var plugin = LoadLibrary(typeAttr.pluginName);
// Loop over all fields for type
foreach (var field in type.GetFields()) {
// Find static, public fields with PluginFunctionAttr
var fieldAttr = field.FindAttribute(typeof(PluginFunctionAttr));
if (!fieldAttr)
continue;
// Get function pointer and store in static delegate field
var fnPtr = GetProcAddress(plugin, fieldAttr.functionName);
var fnDelegate = Marshal.GetDelegateForFunctionPointer(fnPtr, field.FieldType);
field.SetValue(null, fnDelegate);
}
}
}
这段代码在 OnAwake 时会加载所有的Dll,存到一个字典里,然后在 OnDestroy 时卸载所有的Dll。
其他注意的事情
从 Unity 2018.2 开始,设置里添加了新的特性。强烈推荐设置
Editor->Preferences->Script Changes While Playing = Recompile After Finished Playing
ScriptReload 会让所有的原生插件 unload 然后 reload。
当前的 NativePluginLoader.cs 只支持 Windows 平台结论
Unity 支持原生插件是很不错的,因为有一些模块,使用C/C++之类的语言实现,然后提供API给C#调用是更好的选择。但是 Unity 现在对于这一块的支持还不够优雅。
我没有在网络上找到一个更好的方法去解决这个问题。所以我写了这个脚本,它帮我解决了一部分繁琐的事情,希望也能帮到其他人。
原代码:
欢迎关注微信公众号 萌一小栈
页:
[1]