找回密码
 立即注册
查看: 706|回复: 0

怎样在Unity中Reload原生插件

[复制链接]
发表于 2020-12-1 15:37 | 显示全部楼层 |阅读模式
原文来自
在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# 代码
[PluginAttr("my_cool_plugin")]
public static class FooPlugin
{
    [PluginFunctionAttr("sum")]
    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 {
    [DllImport("cpp_example_dll", EntryPoint = "sum")]
    extern static public float sum(float a, float span class="n">b);
}
但是使用 PInvoke 会有一个问题,就是Dll永远不会 unload。解决方案就是我们自己控制加载和卸载,卸载将使用下面的接口
static class SystemLibrary
{
    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    static public extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static public extern bool FreeLibrary(IntPtr hModule);

    [DllImport("kernel32")]
    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 现在对于这一块的支持还不够优雅。
我没有在网络上找到一个更好的方法去解决这个问题。所以我写了这个脚本,它帮我解决了一部分繁琐的事情,希望也能帮到其他人。
原代码:

欢迎关注微信公众号 萌一小栈

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-25 01:54 , Processed in 0.162435 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表