Search code examples
c#iosunity-game-enginereflectionil2cpp

Invoke generic method via reflection in C# IL2CPP on iOS


This question is specifically about Unity3d IL2CPP and iOS.

Calling a generic method using reflection

class SourceValue<T> { public T value; }
class TargetValue<T> { public T value; }

static TargetValue<T> GenericMethod<T> (SourceValue<T> source) {
    return new TargetValue<T> { value = source.value };
}

void Main () {
    Type genericType = typeof(SourceValue<float>);
    Type typeArg = genericType.GenericTypeArguments[0];
    MethodInfo mi = GetType ().GetMethod ("GenericMethod", Flags | BindingFlags.Static);
    MethodInfo gmi = mi.MakeGenericMethod (typeArg);
    object src = new SourceValue<float> { value = 0.5f };
    object trg = gmi.Invoke (this, new object[] { src });
}

This works as expected when run in the Unity editor on mac. The invocation fails on iOS with error:

ExecutionEngineException: Attempting to call method 'GenericMethod<System.Single>' for which no ahead of time (AOT) code was generated.
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  

Is it simply that the AOT system can't call generic methods or am i missing something?


Solution

  • Yes, since IL2CPP is an ahead-of-time (AOT) compiler, it only works for code that exists at compile time. No code here uses GenericMethod<float> in "real" C# source code, so IL2CPP does not know to generate the corresponding code to make that implementation work.

    The real restriction here is that the generic argument type is float, which is a value type. You could use string (a reference type) in this case without any problems. IL2CPP shares the implementation of all generic types that have a generic argument which is a reference type (e.g. string, object, etc.). This is possible because all reference types in C# are the same size (IntrPtr.Size, to be exact).

    So the limitation here is really two-fold:

    1. IL2CPP can only generate code is knows about at compile time
    2. This restriction applies only to generic types when the type argument is a value type.

    Note that it is theoretically possible for IL2CPP to also share the implementation of generic types with value type generic arguments, although that has not yet been implemented.

    EDIT: As of Unity 2022.2 this has been implemented - ExecutionEngineException will not longer happen, and the original scenario will work.