【问题标题】:Mono.Cecil and Unity not playing nice togetherMono.Cecil 和 Unity 不能一起玩
【发布时间】:2018-03-29 07:44:54
【问题描述】:

我正在尝试使用 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 返回 1224

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

如果我改为尝试使用:

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

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

有没有人知道如何在 Unity 中修补用户的项目程序集,同时保持当前项目的可用性? 还是我应该只在构建期间进行修补或其他什么? 有什么建议吗?

谢谢

【问题讨论】:

  • 这段代码什么时候运行?如果您使用 [InitializeOnLoad] 执行此操作,则程序集显然已经加载,因此此时修改它们无济于事;您需要加载程序集一次来触发 Cecil,而不是每次通常只重新加载一次时重新加载以加载修改。尝试使用 UnityEditor.AssemblyReloadEvents.beforeAssemblyReload。上次我尝试使用 Cecil 时,我能够使用单个 var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); 来读取和写入文件而无需删除/复制。
  • 目前我正在通过菜单命令手动运行它。这些都是有趣的建议,我会尽快尝试并报告,谢谢~
  • 所以我现在正在这样做:使用 (Stream assemblyStream = new FileStream(assembly.Location, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { using (AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly (assemblyStream, new ReaderParameters() { ReadWrite = true })) { assemblyDefinition.Write(assemblyStream); } } 仍然得到 1224 IO 异常。
  • 即使我尝试这个也一样:使用 (FileStream assemblyStream = new FileStream(assembly.Location, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { byte[] bytes = new byte[装配流。长度]; assemblyStream.Write(字节,(int)assemblyStream.Position,(int)assemblyStream.Length); }
  • 您需要在读取和写入之间设置assemblyStream.Position = 0;,以便您实际上是从文件开头覆盖。不确定这是否是错误的原因。除此之外,它看起来与我的代码几乎相同(尽管我没有使用任何 ReaderParameters)。

标签: unity3d mono.cecil


【解决方案1】:

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

如果您使用[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;
    }

【讨论】:

  • 您在使用此方法进行构建方面是否取得了成功?构建时不使用修补程序集,因为正在构建新程序集。但是 OnPostprocessBuild 和 OnPreprocessBuild 似乎与此无关,在注入代码后立即使用 BuildPipeline.BuildPlayer 也不相关。我似乎找不到在构建中使用注入代码的方法。
  • 我已经有一段时间没有使用它了,我从来没有认真实现过任何东西,所以我不记得了,但我将我正在使用的代码添加到我的答案中。
猜你喜欢
  • 2010-10-04
  • 2010-10-16
  • 2018-12-07
  • 1970-01-01
  • 1970-01-01
  • 2013-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多