Siccity / SerializableCallback

UnityEvent and System.Func had a child
MIT License
356 stars 53 forks source link

Incompatible with IL2CPP #24

Open holodia opened 3 years ago

holodia commented 3 years ago

ExecutionEngineException: Attempting to call method 'InvokableCallback`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.ctor' for which no ahead of time (AOT) code was generated.

Callstack:

System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters)
System.RuntimeType.CreateInstanceImpl (System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture, System.Object[] activationAttributes, System.Threading.StackCrawlMark& stackMark)
System.Activator.CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture, System.Object[] activationAttributes)
SerializableCallbackBase`1[TReturn].GetPersistentMethod ()
SerializableCallback`1[TReturn].Cache ()
SerializableCallback`1[TReturn].Invoke ()
rfadeev commented 2 years ago

Hi,

Could you please provide example code for reproducing the issue? Unity version and target platform would be also helpful.

I tried to reproduce the issue with PC Windows IL2CPP build and I did not get that exception. I used example code from README.md and SerializableCallback<int, MyProduct> callback.

Esildor commented 1 year ago

Trying to get some internet points for anyone else that finds this in the future since I also ran into this.

This is how I fixed it, check out the class comments for how and why I think it fixes it. I'm too lazy to rewrite things up here, so just read the class comments 😂

/// <summary>
/// Since comparer nodes and other classes use the SerializableCallback library, which uses
/// reflection to make function calls. These classes/function calls are stripped by
/// the compiler at compile time. This means any function not manually called
/// (in code) gets removed at compile time. Mainly some InvokableCallback<T, J>
/// never get created or used at compile time. In the editor, this works fine
/// since it supports JIT compilation, mobile devices don't. So we manually create and
/// "use" these classes so they are not stripped.
/// 
/// Some thoughts:
/// It's strange since methods that have a single param don't get stripped. But
/// anytime there is a function with 2 or more parameters, it gets stripped and requires
/// this... Not sure why. My only guess would be the SerializableCallback library 
/// serializes 1 param methods differently from X param methods. Notably, Unity 
/// doesn't strip functions that are only used in UnityAction's. I wonder if this lib, or Unity
/// is using some engine feature? 
/// Regardless, defining them manually for each method signature makes it work.
/// </summary>
public static class AOTStubsIL2CPP
{
  public static void Stub()
  {
    // Quests
    new QuestLineIntInvokable(null, string.Empty);
  }

  class QuestLineIntInvokable : InvokableCallback<QuestLine, int, bool>
  {
    public QuestLineIntInvokable(object target, string methodName) : base(target, methodName) { }
  }
}

For my use case above, I needed a method that too 2 params, a QuestLine (custom class in project) and an int. At the bottom, I define the class. Then "use" it in code. From your error message, you can sort of "parse" what the method is. Attempting to call method 'InvokableCallback`2[[**System.String**, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.ctor'

It looks like you're trying to call a function with the signature of: bool Foo(string bar). So your class might like something like this:

  class FooBarInvokable : InvokableCallback<string, bool>
  {
    public FooBarInvokable (object target, string methodName) : base(target, methodName) { }
  }

Hope this helps lol

EpsilonD3lta commented 1 year ago

I am no expert on C# compiler but as I understand it, methods are not being "stripped", they are not being created at all. Generic methods are being created by compiler - each type and combination of types it stumbles upon when it reads the code creates a separate method. Since here the methods are constructed in runtime, it does not create those combinations AOT.

As of why some methods with smaller number of parameters do exist - I don't know. In my case, 0 input and 1 output params callbacks were created, callbacks with 1 input and 1 output were not. Maybe compiler does create some basic types? No idea.

But this is the reason why even [UnityEngine.Scripting.Preserve] attribute or linker xml file won't work - the methods aren't stripped - they don't exist.