RuntimeInitializeOnLoad 不是 运行 条件编译
RuntimeInitializeOnLoad not running with conditional compilation
概览
我熟悉使用 Platform Dependent Compilation in Unity 更改代码在 editor/standalone/webgl/etc 之间的编译方式。
我 运行 遇到了一个我无法解释的看似微不足道的奇怪案例。
问题
给定一个空的 Unity 5.6.1f1(64 位 windows)项目,其中仅包含以下脚本:
using UnityEngine;
public class TestA
{
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] // Should be ignored for standalone
#else
[RuntimeInitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}
public class TestB
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod] // Should be ignored for standalone
#else
[RuntimeInitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}
public class TestC
{
[RuntimeInitializeOnLoadMethod]
static void OnInit()
{
#if UNITY_EDITOR
Debug.Log("TEST C - UNITY_EDITOR is defined");
#else
Debug.Log("TEST C - UNITY_EDITOR is NOT defined");
#endif
}
}
编译后 运行 在编辑器中我得到了我期望的输出:
- 注意
TestC
如何显示,当 运行 在编辑器中时,UNITY_EDITOR
指令按预期定义。
然而,当为 Window 独立编译时,TestA
无法打印。下面是 output_log.txt
在 运行 只包含上述脚本的独立构建之后的相关内容:
- 注意
TestA
如何打印失败。
- 注意
TestC
如何显示在为 Window 独立编译时,UNITY_EDITOR
未 定义(正如预期的那样),因此任何东西#if UNITY_EDITOR
部分中的编译器在为独立编译时应该被忽略。
那么为什么TestA
和TestB
有区别呢?为什么 TestA
不在独立编译中打印,但 TestB
打印?
唯一值得注意的区别是 TestA
中的 #if UNITY_EDITOR
定义有 editor-only runtime init attribute,它位于一个命名空间中,甚至不应该在编辑器外部进行编译(如果它不在 #if UNITY_EDITOR
).
之内,编译器确实会抱怨
换句话说,在单机编译时,编译时只需要#else
结束#endif
之间的部分,这两部分完全相同 在 TestA
和 TestB
在独立编译时,我假设上面的代码应该与这段代码的行为完全相同:
using UnityEngine;
public class TestA
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}
public class TestB
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}
public class TestC
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST C - UNITY_EDITOR is NOT defined"); }
}
但是在编译时我确实看到了 output_log.txt 中的所有三行:
所以一定有所不同。
这是怎么回事?为什么 TestA
不是独立的 运行ning,而 TestB
是?
暂时没有答案,但我想补充一点。我试过你的代码,得到了相同的结果。
此外,我想使用 ILSpy 查看独立构建的编译代码,奇怪的是 ILSpy 就是这样反编译的 TestA
class:
using System;
using UnityEngine;
public class TestA
{
[RuntimeInitializeOnLoadMethod]
private static void OnInit()
{
Debug.Log("TEST A - This does NOT print in Standalone");
}
}
所以,现在我比你还困惑:编译后的代码是正确的(预处理器工作正常),但它不应该 运行。
我会再考虑一下,但感觉像是一些非常奇怪的错误。
我已经提交了错误报告,我收到的回复暗示它可能会在未来的 Unity 版本中得到修复。
虽然导致问题的确切细节尚不清楚,但这是我们所知道的:
主要问题似乎是 RuntimeInitializeOnLoadMethodAttribute
(无论出于何种原因)在 #define 检查中编译时无法按预期运行(因此使用 #if
、#else
、 #if
, 等)当 UNITY_EDITOR
为 false/undefined.
时会计算
例如,即使这样也行不通:
#if !UNITY_EDITOR
public class SomeTest
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("Does not show up"); }
}
#endif
尽管 class 正在编译,并且该函数存在并且可以从其他 class 中调用(如果它是 public)。
为了使 [RuntimeInitializeOnLoadMethod]
工作,它似乎必须在 #define 检查之外,或者如果在内部,则在两种情况下都必须存在于相同的 fassion 中才能在 UNITY_EDITOR
计算为 false 时工作.
因此,要使原始示例中的 TestA
正常工作(使用 [InitializeOnLoadMethod]
在编辑器中使用 [RuntimeInitializeOnLoadMethod]
并独立使用 [RuntimeInitializeOnLoadMethod]
),您可以执行以下操作:
public class TestA
{
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] static void EditorInitWrapper() { OnInit(); }
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { /* Do Nothing */ }
#else
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { OnInit(); }
#endif
static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}
包装器在必要时调用 OnInit
。
需要注意的是[RuntimeInitializeOnLoadMethod]
配合/* Do Nothing */
命令其实是需要和必须有#if
和 #else
部分中的相同名称 才能正常工作。如果两者不匹配,那么 OnInit
将不会在独立构建中被调用。
或者,您可以通过将相同的函数定义移到 #define
检查之外并包装 OnInit()
调用本身来避免必须具有相同的函数定义两次,尽管可以说它的可读性较差:
public class TestA
{
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper()
{
#if !UNITY_EDITOR
OnInit();
#endif
}
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}
这两个都将实现原始示例脚本中 TestA
通常预期的确切行为。
概览
我熟悉使用 Platform Dependent Compilation in Unity 更改代码在 editor/standalone/webgl/etc 之间的编译方式。
我 运行 遇到了一个我无法解释的看似微不足道的奇怪案例。
问题
给定一个空的 Unity 5.6.1f1(64 位 windows)项目,其中仅包含以下脚本:
using UnityEngine;
public class TestA
{
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] // Should be ignored for standalone
#else
[RuntimeInitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}
public class TestB
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod] // Should be ignored for standalone
#else
[RuntimeInitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}
public class TestC
{
[RuntimeInitializeOnLoadMethod]
static void OnInit()
{
#if UNITY_EDITOR
Debug.Log("TEST C - UNITY_EDITOR is defined");
#else
Debug.Log("TEST C - UNITY_EDITOR is NOT defined");
#endif
}
}
编译后 运行 在编辑器中我得到了我期望的输出:
- 注意
TestC
如何显示,当 运行 在编辑器中时,UNITY_EDITOR
指令按预期定义。
然而,当为 Window 独立编译时,TestA
无法打印。下面是 output_log.txt
在 运行 只包含上述脚本的独立构建之后的相关内容:
- 注意
TestA
如何打印失败。 - 注意
TestC
如何显示在为 Window 独立编译时,UNITY_EDITOR
未 定义(正如预期的那样),因此任何东西#if UNITY_EDITOR
部分中的编译器在为独立编译时应该被忽略。
那么为什么TestA
和TestB
有区别呢?为什么 TestA
不在独立编译中打印,但 TestB
打印?
唯一值得注意的区别是 TestA
中的 #if UNITY_EDITOR
定义有 editor-only runtime init attribute,它位于一个命名空间中,甚至不应该在编辑器外部进行编译(如果它不在 #if UNITY_EDITOR
).
换句话说,在单机编译时,编译时只需要#else
结束#endif
之间的部分,这两部分完全相同 在 TestA
和 TestB
在独立编译时,我假设上面的代码应该与这段代码的行为完全相同:
using UnityEngine;
public class TestA
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}
public class TestB
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}
public class TestC
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("TEST C - UNITY_EDITOR is NOT defined"); }
}
但是在编译时我确实看到了 output_log.txt 中的所有三行:
所以一定有所不同。
这是怎么回事?为什么 TestA
不是独立的 运行ning,而 TestB
是?
暂时没有答案,但我想补充一点。我试过你的代码,得到了相同的结果。
此外,我想使用 ILSpy 查看独立构建的编译代码,奇怪的是 ILSpy 就是这样反编译的 TestA
class:
using System;
using UnityEngine;
public class TestA
{
[RuntimeInitializeOnLoadMethod]
private static void OnInit()
{
Debug.Log("TEST A - This does NOT print in Standalone");
}
}
所以,现在我比你还困惑:编译后的代码是正确的(预处理器工作正常),但它不应该 运行。
我会再考虑一下,但感觉像是一些非常奇怪的错误。
我已经提交了错误报告,我收到的回复暗示它可能会在未来的 Unity 版本中得到修复。
虽然导致问题的确切细节尚不清楚,但这是我们所知道的:
主要问题似乎是 RuntimeInitializeOnLoadMethodAttribute
(无论出于何种原因)在 #define 检查中编译时无法按预期运行(因此使用 #if
、#else
、 #if
, 等)当 UNITY_EDITOR
为 false/undefined.
例如,即使这样也行不通:
#if !UNITY_EDITOR
public class SomeTest
{
[RuntimeInitializeOnLoadMethod]
static void OnInit() { Debug.Log("Does not show up"); }
}
#endif
尽管 class 正在编译,并且该函数存在并且可以从其他 class 中调用(如果它是 public)。
为了使 [RuntimeInitializeOnLoadMethod]
工作,它似乎必须在 #define 检查之外,或者如果在内部,则在两种情况下都必须存在于相同的 fassion 中才能在 UNITY_EDITOR
计算为 false 时工作.
因此,要使原始示例中的 TestA
正常工作(使用 [InitializeOnLoadMethod]
在编辑器中使用 [RuntimeInitializeOnLoadMethod]
并独立使用 [RuntimeInitializeOnLoadMethod]
),您可以执行以下操作:
public class TestA
{
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] static void EditorInitWrapper() { OnInit(); }
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { /* Do Nothing */ }
#else
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { OnInit(); }
#endif
static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}
包装器在必要时调用 OnInit
。
需要注意的是[RuntimeInitializeOnLoadMethod]
配合/* Do Nothing */
命令其实是需要和必须有#if
和 #else
部分中的相同名称 才能正常工作。如果两者不匹配,那么 OnInit
将不会在独立构建中被调用。
或者,您可以通过将相同的函数定义移到 #define
检查之外并包装 OnInit()
调用本身来避免必须具有相同的函数定义两次,尽管可以说它的可读性较差:
public class TestA
{
[RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper()
{
#if !UNITY_EDITOR
OnInit();
#endif
}
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#endif
static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}
这两个都将实现原始示例脚本中 TestA
通常预期的确切行为。