Mono.Cecil 和 Unity 不能很好地配合

Mono.Cecil and Unity not playing nice together

我正在尝试使用 Mono.Cecil 在 Unity 中修补我的自定义用户脚本。

我有代码要注入到 Unity 中的自定义用户脚本中,以避免在项目的每个 MonoBehaviour 中编写相同的代码行。 但是,当我这样做时:

using (AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(assembly.Location, new ReaderParameters() { ReadWrite = true }))
{
    //Do some patching here
    assemblyDefinition.Write();
}

然后我得到一个异常

IOException: Win32 IO returned 1224

这显然意味着该文件已被锁定,无法写入。

如果我改为尝试使用:

File.Delete(sourceAssemblyPath);
File.Move(targetAssemblyPath, sourceAssemblyPath);

然后 dll 得到了正确的修补,但是当我尝试播放应用程序时,场景中的脚本失去了引用,就好像文件的替换导致他们认为场景对象上的脚本不再存在于项目(我想这是有道理的,因为我确实删除了他们所在的 dll 以用新的替换它)。

有没有人知道如何在保持当前项目可用性的同时在 Unity 中修补用户的项目程序集? 还是我应该只在构建过程中打补丁之类的? 建议?

谢谢

上次我尝试使用 Cecil 时,我能够使用单个 var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); 来读取和写入文件,而无需 delete/copy。

如果您使用 [InitializeOnLoad] 执行此操作,那么程序集显然已经加载,因此此时修改它们将无济于事;您需要加载程序集一次以触发 Cecil,而不是每次通常只重新加载一次时再次重新加载以加载修改。您需要改用 UnityEditor.AssemblyReloadEvents.beforeAssemblyReload

beforeAssemblyReload 在重新编译新程序集之后但在加载它们之前被调用。因此,您将使用 [InitializeOnLoad] 注册回调([DidReloadScripts] 在我尝试过的每种情况下似乎都相同),这应该确保所有新编译的程序集都得到处理。在某些情况下,这可能不会发生(例如,当您第一次打开编辑器时需要编译脚本,所以它还没有注册您的事件)所以您可能还需要立即 运行 您的处理代码如果使用 UnityEditorInternal.InternalEditorUtility.RequestScriptReload();UnityEditor.AssetDatabase.Refresh();.

发生任何更改,则也进行初始化并强制重新加载程序集

我发现将程序集标记为已处理的最佳方法是注入属性定义并将其实例添加到程序集中,然后按名称检查它并跳过程序集(如果存在)。如果没有办法做到这一点,您将在每次重新编译脚本时处理项目中的每个程序集,而不是只处理已修改的程序集。


编辑:要处理构建的程序集,试试这个:

    private static bool _HasProcessed;

    [PostProcessScene]
    private static void OnPostProcessScene()
    {
        if (_HasProcessed || !BuildPipeline.isBuildingPlayer)
            return;

        ProcessAssemblies(@"Library\PlayerDataCache");
        _HasProcessed = true;
    }

    [PostProcessBuild]
    private static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
    {
        _HasProcessed = false;
    }