IL2CPP does not generate constructor for generic classes that have struct as parameter

We are using MediatR for our application, and running on Hololens 2 with the IL2CPP backend with Unity 2020.1.11. We configured the linker not to strip the assemblies we need.

<linker>
<!-- other preserved assemblies -->
    <assembly fullname="MediatR" preserve="all" />
    <assembly fullname="OurAssembly" preseve="all"/>
<!-- other preserved assemblies -->
</linker>

Requests that have a specified return value are working fine:

public sealed class LoadScan3DFile : IRequest<PointSet> // PointSet is a class
{
    public LoadScan3DFile(Guid measurementFileId) => MeasurementFileId = measurementFileId;

    public Guid MeasurementFileId { get; }
}

But requests which don’t return anything are actually requests which return the type Unit (From mediatR github) Request Unit

So a simple request:

public sealed class UnzipDatabase : IRequest
{
    public string TargetZipFile { get; }

    public UnzipDatabase(string targetZipFile) => TargetZipFile = targetZipFile;
}

Will not be executed when sent through MediatR, and will produce the following exception:

```System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ExecutionEngineException: Attempting to call method 'MediatR.Internal.RequestHandlerWrapperImpl`2[OurAssembly.UnzipDatabase, OurAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[MediatR.Unit, MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=null]]::.ctor' for which no ahead of time (AOT) code was generated.```

But will work when I introduce some response type which is a class:

public sealed class UnzipDatabase : IRequest<Result>
{
    public string TargetZipFile { get; }

    public UnzipDatabase(string targetZipFile) => TargetZipFile = targetZipFile;
}

public sealed class Result
{
    private Result()
    {
        IsSuccessful = true;
        Messages = ImmutableArray<string>.Empty;
    }

    private Result(ImmutableArray<string> messages)
    {
        IsSuccessful = false;
        Messages = messages;
    }

    public bool IsSuccessful { get; }

    public ImmutableArray<string> Messages { get; }

    public static Result Success { get; } = new Result();
    public static Result Failure(string message) => new Result(ImmutableArray.Create(message));
    public static Result Failure(Result previous, string message) => new Result(previous.Messages.Add(message));
}

The class used by MediatR which will not get it’s constructor generated can be found here.

Is this a bug in IL2CPP? (In the editor which uses the mono backend everything works fine. I have tried both Debug and Master builds for IL2CPP).

Have you tried just putting a call to that flavor of the constructor somewhere in your codebase? If the problem still exists, it’s probably not stripping.

Also, there may be two (or more) places of stripping.

When the C# code becomes IL2CPP code, I think that is one spot it can drop unused stuff.

When the IL2CPP code is compiled into OBJ files and linked, that’s another point.

Which one is your anti-strip XML file targeting?

1 Like

Right. I haven’t looked into the mediator implementation, but if it creates the objects solely through reflection it wouldn’t work since the constructor would get stripped if it’s not used “explicitly” somewhere in code. Try adding a method like

[UnityEngine.Scripting.Preserve]
protected static void IL2CPP()
{
    new UnzipDatabase("");
}

I think you don’t even need to call this method. Though you may need to use the Preserve attribute as mentioned here. it should prevent the stripping of this method and subsequently all code that it includes. You can try to put the Preserve attribute on the constructor itself. Though it’s sometimes better to keep all the “preserving” stuff in a single place.

1 Like

I like your thinking.

1 Like

The problem is not that the UnzipDatabase will get stripped it’s the MediatR handler implementation, with some generic parameters.

    internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>
        where TRequest : IRequest<TResponse>
    {
        public override Task<object?> Handle(object request, CancellationToken cancellationToken,
            ServiceFactory serviceFactory)
        {
            return Handle((IRequest<TResponse>)request, cancellationToken, serviceFactory)
                .ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
                    }
                    return (object?)t.Result;
                }, cancellationToken);
        }

        public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,
            ServiceFactory serviceFactory)
        {
            Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);

            return serviceFactory
                .GetInstances<IPipelineBehavior<TRequest, TResponse>>()
                .Reverse()
                .Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();
        }

This is the type which will be created by MediatR, and it is used to resolve and call the concrete handler. The problem boiled down:

// This works fine

// Request
class GetStuff : IRequest<string> { }

// Handler
class GetStuffHandler : IRequestHandler<GetStuff, string> { }

// MediatR created handler
internal class RequestHandlerWrapperImpl<GetStuff, string> : RequestHandlerWrapper<string> { }

// This this will throw System.Reflection.TargetInvocationException //... RequestHandlerWrapperImpl<DoStuff, Unit> ..// .ctor' for which no ahead of time (AOT) code was generated

// Request
class DoStuff : IRequest<Unit> { } // This is the same as: class DoStuff : IRequest { }

// Handler
class DoStuffHandler : IRequestHandler<DoStuff, Unit> { }

// MediatR created handler
internal class RequestHandlerWrapperImpl<DoStuff, Unit> : RequestHandlerWrapper<string> { }

// This this will throw System.Reflection.TargetInvocationException //... RequestHandlerWrapperImpl<DoStuff, ImmutableArray<string>> ..// .ctor' for which no ahead of time (AOT) code was generated

// Request
class DoStuff : IRequest<ImmutableArray<string>> { } // This is the same as: class DoStuff : IRequest { }

// Handler
class DoStuffHandler : IRequestHandler<DoStuff, ImmutableArray<string>> { }

// MediatR created handler
internal class RequestHandlerWrapperImpl<DoStuff, ImmutableArray<string>> : RequestHandlerWrapper<ImmutableArray<string>> { }

To me it seems like, that there will not be a class generated in c++ when the RequestHandlerWrapperImpl class TResponse generic argument is a struct. The only common thing between failing examples is that both Unit and ImmutableArray are value types.

Simply trying to preserve the handlers/requests won’t solve the problem because with the link.xml configuration the whole assemblies are preserved.

1 Like

I have creatad a minimal repro, and added another example with a bool return type which also broke. Steps to reproduce are found in the README. GitHub - KuraiAndras/IL2CPPBugSample

1 Like

The generic interfaces are the problem.

You got this error becouse MediatR create generic types at runtime.

IL2cpp could deal with it if all generic arguments are reference types becouse for reference types all code is the same no matter what real type is.

To deal with this problem you should have explicit usage of generic types with value type argument so il2cpp could create AOT code for it.
But MediatR construct internal generic:(
To work around this you have few options:

  1. Do not use value types;
  2. Recompile MediatR and make internal types public;
  3. Use not standard compiler and create library that will create call to MediatR internal types.

I made a small experiment with option 3 and create such library GitHub - VolodymyrBS/MediatR.Il2Cpp

I’d add compiled dll into you repro project and add method that use all required generic types.

   [Preserve]
        public void Stub()
        {
            Inner<NotWorkingUnitRequest, Unit>();
            Inner<NotWorkingBoolRequest, bool>();
            Inner<NotWorkingArrayRequest, ImmutableArray<string>>();

            void Inner<TRequest, TResponse>()
            where TRequest : IRequest<TResponse>
            {
                MediatR.Il2Cpp.HandlerStub.MakeStub<TRequest, TResponse>();
                new RequestPreProcessorBehavior<TRequest, TResponse>(null);
                new RequestPostProcessorBehavior<TRequest, TResponse>(null);
                new RequestExceptionProcessorBehavior<TRequest, TResponse>(null);
                new RequestExceptionActionProcessorBehavior<TRequest, TResponse>(null);
            }
        }

after this all 4 buttons show “Worked” text.

Here is my fork:

1 Like

Welp, then for me Il2cpp just started to suck even more. I will try to create an editor utility which will generate the stub method in the editor. It will use the open sesame compiler with GitHub - mob-sakai/CSharpCompilerSettingsForUnity: Change the C# compiler (csc) used on your Unity project, as you like! . I hope I can finish it in the weekend. Might as well release it on openupm