InjectFix icon indicating copy to clipboard operation
InjectFix copied to clipboard

热更成功,但有个方法Patch会报错(报错的方法里面调用了一个带有ref参数的event的注册回调的操作)

Open MrNerverDie opened this issue 4 years ago • 13 comments

报错信息: ExecutionEngineException: Attempting to call method 'System.Threading.Interlocked::CompareExchange<MoleMole.LevelBorder+BorderChange>' for which no ahead of time (AOT) code was generated. 06-12 19:34:17.195 10092 10156 E Unity : at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <00000000000000000000000000000000>:0 06-12 19:34:17.195 10092 10156 E Unity : at IFix.Core.ReflectionMethodInvoker.Invoke (IFix.Core.VirtualMachine virtualMachine, IFix.Core.Call& call, System.Boolean isInstantiate) [0x00000] in <00000000000000000000000000000000>:0 06-12 19:34:17.195 10092 10156 E Unity : at IFix.Core.VirtualMachine.Execute (IFix.Core.Instruction* pc, IFix.Core.Value* argumentBase, System.Object[] managedStack, IFix.Core.Value* evaluationStackBase, System.Int32 argsCount, System.Int32 methodIndex, System.Int32 refCount, IFix.Core.Value** topWriteBack) [0x00000] in <00000000000000000000000000000000>:0 06-12 19:34:17.195 10092 10156 E Unity : at IFix.Core.VirtualMachine.Execute (IFix.Core.Instruction* pc,

相应的报错位置 MonoBasicLevel.levelManager.GetLevelBorder().OnGetLevelBorder -= OnGetLevelBorder 其中OnGetLevelBorder是一个定义有ref参数的event `

	public delegate void BorderChange(ref Vector2 border);

	public event BorderChange OnGetLevelBorder;

`

MrNerverDie avatar Jun 12 '20 11:06 MrNerverDie

需要提前把BorderChange放到CustomBridge里头。

chexiongsheng avatar Jul 14 '20 01:07 chexiongsheng

@chexiongsheng 需要提前把BorderChange放到CustomBridge里头的话,是说再打基础包的时候这些event就需要加上custombridge吗

MrNerverDie avatar Sep 10 '20 02:09 MrNerverDie

打包的时候,就要加到custombridge列表

chexiongsheng avatar Sep 15 '20 06:09 chexiongsheng

遇到一样的问题。il2cpp下,将delegate加到CustomBridge中也不起作用。一般的delegate加进去可以正常,一但做为event来使用,就会报System.Threading.Interlocked::CompareExchange这个的no AOT generated的错误。即使我在打基础包时,强行加了代码System.Threading.Interlocked.CompareExchange<对应的Delegate类>,补丁运行到这个event的时候还是会报错。有什么办法能修复吗 @chexiongsheng

Light0457 avatar Dec 26 '20 13:12 Light0457

CompareExchange

android版本的async多了个泛型调用,你用Patch,而不是Patch(Android)试试

chexiongsheng avatar Jan 03 '21 09:01 chexiongsheng

CompareExchange

android版本的async多了个泛型调用,你用Patch,而不是Patch(Android)试试

我这边没有用到ascyn。也蛮试了一下用Patch,在Android(il2cpp)下仍然是会报这个错误:

01-12 09:42:04.718 E/Unity (14434): Exception: Attempting to call method 'System.Threading.Interlocked::CompareExchange<System.Action`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]>' for which no ahead of time (AOT) code was generated. 01-12 09:42:04.718 E/Unity (14434): at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <00000000000000000000000000000000>:0 01-12 09:42:04.718 E/Unity (14434): at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 01-12 09:42:04.718 E/Unity (14434): at IFix.Core.ReflectionMethodInvoker.Invoke (IFix.Core.VirtualMachine virtualMachine, IFix.Core.Call& call, System.Boolean isInstantiate) [0x00000] in <00000000000000000000000000000000>:0 01-12 09:42:04.718 E/Unity (14434): at IFix.Core.ExternInvoker.Invoke (IFix.Core.VirtualMachine vm, IFix.Core.Call& call, System.Boolean isInstantiate) [0x00000] in <00000000000000000000000000000000>:0 01-12 09:42:04.718 E/Unity (14434): at IFix.Core.V 01-12 10:41:24.942 E/Unity (16474): Exception: Attempting to call method 'System.Threading.Interlocked::CompareExchange<TestPatchWithEventAssign+Invoke_Param0Int>' for which no ahead of time (AOT) code was generated. 01-12 10:41:24.942 E/Unity (16474): at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <00000000000000000000000000000000>:0 01-12 10:41:24.942 E/Unity (16474): at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 01-12 10:41:24.942 E/Unity (16474): at IFix.Core.ReflectionMethodInvoker.Invoke (IFix.Core.VirtualMachine virtualMachine, IFix.Core.Call& call, System.Boolean isInstantiate) [0x00000] in <00000000000000000000000000000000>:0 01-12 10:41:24.942 E/Unity (16474): at IFix.Core.ExternInvoker.Invoke (IFix.Core.VirtualMachine vm, IFix.Core.Call& call, System.Boolean isInstantiate) [0x00000] in <00000000000000000000000000000000>:0 01-12 10:41:24.942 E/Unity (16474): at IFix.Core.VirtualMachine.Execute (IFix.Core.Instruction* pc, IFix.Core.Value* a

相应的引起报错的地方: TestPatchWithEventAssign.onInvoke_Param0String += event_invoke_param0string; TestPatchWithEventAssign.onInvoke_Param0String -= event_invoke_param0string;

我的CustomBridge配置如下,应该是没错的吧

[IFix.CustomBridge]   
public static class AdditionalBridge   
{   
    private static List<Type> bridge = new List<Type>() {   
        typeof(INativeInterface),  //* Need for TEST_NEW_CLASS_IMPLEMENT_NATIVE_INTERFACE  
        typeof(System.Action<int>),   //* Need for TEST_ADD_NEW_LAMBDA_CLASS   
        typeof(TestPatchWithEventAssign.Invoke_Param0Int),  
        typeof(System.Action<string>),  
    };
}

Light0457 avatar Jan 12 '21 03:01 Light0457

CompareExchange

跟CustomBridge没关系,怎么配都没用。报错表示指令里有个对一个需要AOT的泛型方法调用。

chexiongsheng avatar Jan 12 '21 07:01 chexiongsheng

CompareExchange

跟CustomBridge没关系,怎么配都没用。报错表示指令里有个对一个需要AOT的泛型方法调用。

我反编译出来看过,

[Preserve]
	public static event Invoke_Param0Int onInvoke_Param0Int;

[Preserve]
	public static event Action<string> onInvoke_Param0String;

是两个event事件生成出来的add函数和remove函数,它们的指令里会有报错的那个方法(CompareExchange)。

IL_001c: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class TestPatchWithEventAssign/Invoke_Param0Int>(!!0&, !!0, !!0)
IL_001c: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action`1<string>>(!!0&, !!0, !!0)

有什么办法可以解决掉这个AOT报错吗? 我看它报错说"no ahead of time (AOT) code was generated", 所以尝试过在原生代码里面强行调用一次它会生成的函数,也是没什么用。感觉没法"generate"这个"ahead of time (AOT) code"的样子

public static void NoOneCallThisFunc_DefineForAOT()
  {
      // System_Threading_Interlocked_CompareExchange<System.Action<string>>();
      // System_Threading_Interlocked_CompareExchange<Invoke_Param0Int>();
      {
          var a = default(System.Action<string>);
          System.Threading.Interlocked.CompareExchange(ref a, a, a);
      }
      {
          var a = default(Invoke_Param0Int);
          System.Threading.Interlocked.CompareExchange(ref a, a, a);
      }
  }

Light0457 avatar Jan 12 '21 08:01 Light0457

据说你别Patch(Android),直接用patch就没这指令。

chexiongsheng avatar Jan 12 '21 08:01 chexiongsheng

据说你别Patch(Android),直接用patch就没这指令。

我试过的,直接用Patch,运行起来也是会碰到这个指令而报错。跟用Patch(Android)一样的报错。 我这边,这个指令在Editor下的Assembly-CSharp.dll里面就是会存在。不知道是不是跟什么环境配置有关系

Light0457 avatar Jan 12 '21 09:01 Light0457

不管是加入CustomBridge还是Patch选项,都不能解决问题 这里的关键是il2cpp之后,interlocked.CompareExchange函数在cpp层的实现并没有反射机制,直接变成了CompareExchangeImpl,所以运行时在尝试调用泛型的CompareExchange就会报错。这也是为什么Unity编辑器端OK但是Android就报错的原因。

解决办法1,适用于用户这边,改用delegate而不用event,或者在event里面手动添加add/remove,都可以去除调用CompareExchange这一条指令,但是这会使语义降级,把线程安全的变成线程不安全的。(因为CompareExchange是.net实现CAS机制的函数)

解决办法2,需要修改InjectFix,inject时自己增加一层wrapper函数,间接调用对应的interlocked.CompareExchange函数,并显式化这一层wrapper函数(防止被il2cpp裁剪);同时生成patch指令时把对应的对interlocked.CompareExchange的callExtern调用转到wrapper函数中

至于为什么il2cpp不把CompareExchange也增加反射机制,我理解这是il2cpp的优化机制,毕竟它也想不到会有InjectFix这样增加虚拟机调用神奇的用法。

作者有没有兴趣加个QQ我们细聊🤩

xtay avatar Jan 12 '21 10:01 xtay

CompareExchange

跟CustomBridge没关系,怎么配都没用。报错表示指令里有个对一个需要AOT的泛型方法调用。

我反编译出来看过,

[Preserve]
	public static event Invoke_Param0Int onInvoke_Param0Int;

[Preserve]
	public static event Action<string> onInvoke_Param0String;

是两个event事件生成出来的add函数和remove函数,它们的指令里会有报错的那个方法(CompareExchange)。

IL_001c: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class TestPatchWithEventAssign/Invoke_Param0Int>(!!0&, !!0, !!0)
IL_001c: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action`1<string>>(!!0&, !!0, !!0)

有什么办法可以解决掉这个AOT报错吗? 我看它报错说"no ahead of time (AOT) code was generated", 所以尝试过在原生代码里面强行调用一次它会生成的函数,也是没什么用。感觉没法"generate"这个"ahead of time (AOT) code"的样子

public static void NoOneCallThisFunc_DefineForAOT()
  {
      // System_Threading_Interlocked_CompareExchange<System.Action<string>>();
      // System_Threading_Interlocked_CompareExchange<Invoke_Param0Int>();
      {
          var a = default(System.Action<string>);
          System.Threading.Interlocked.CompareExchange(ref a, a, a);
      }
      {
          var a = default(Invoke_Param0Int);
          System.Threading.Interlocked.CompareExchange(ref a, a, a);
      }
  }

不管是加入CustomBridge还是Patch选项,都不能解决问题 这里的关键是il2cpp之后,interlocked.CompareExchange函数在cpp层的实现并没有反射机制,直接变成了CompareExchangeImpl,所以运行时在尝试调用泛型的CompareExchange就会报错。这也是为什么Unity编辑器端OK但是Android就报错的原因。

解决办法1,适用于用户这边,改用delegate而不用event,或者在event里面手动添加add/remove,都可以去除调用CompareExchange这一条指令,但是这会使语义降级,把线程安全的变成线程不安全的。(因为CompareExchange是.net实现CAS机制的函数)

解决办法2,需要修改InjectFix,inject时自己增加一层wrapper函数,间接调用对应的interlocked.CompareExchange函数,并显式化这一层wrapper函数(防止被il2cpp裁剪);同时生成patch指令时把对应的对interlocked.CompareExchange的callExtern调用转到wrapper函数中

至于为什么il2cpp不把CompareExchange也增加反射机制,我理解这是il2cpp的优化机制,毕竟它也想不到会有InjectFix这样增加虚拟机调用神奇的用法。

作者有没有兴趣加个QQ我们细聊🤩

额,我有点没太理解,这里不应该是Patch的方法体以反射的形式调用event的默认add方法么,没有直接调用Interlocked.CompareExchange啊

oahceh avatar Dec 24 '21 07:12 oahceh

额,我有点没太理解,这里不应该是Patch的方法体以反射的形式调用event的默认add方法么,没有直接调用Interlocked.CompareExchange啊

编译后调用了什么就调用什么。编译器有时会帮你干些事情。

chexiongsheng avatar Jan 04 '22 08:01 chexiongsheng