diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index 9124beee04c9..be94cb100e3b 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -30,6 +30,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion +using Google.Protobuf.WellKnownTypes; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -43,6 +44,16 @@ namespace Google.Protobuf.Reflection /// public sealed class FileDescriptor : IDescriptor { + // Prevent linker failures when using IL2CPP with the well-known types. + static FileDescriptor() + { + ForceReflectionInitialization(); + ForceReflectionInitialization(); + ForceReflectionInitialization(); + ForceReflectionInitialization(); + ForceReflectionInitialization(); + } + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) { SerializedData = descriptorData; @@ -334,5 +345,18 @@ public override string ToString() /// The (possibly empty) set of custom options for this file. /// public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty; + + /// + /// Performs initialization for the given generic type argument. + /// + /// + /// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated) + /// to make calls into the reflection machinery of this library to express an intention to use that type + /// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers + /// attempting to determine which generic type arguments need to be handled will spot the code path and act + /// accordingly. + /// + /// The type to force initialization for. + public static void ForceReflectionInitialization() => ReflectionUtil.ForceInitialize(); } } diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index 80d5c774f232..18a70b80b896 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -50,6 +50,29 @@ namespace Google.Protobuf.Reflection /// internal static class ReflectionUtil { + static ReflectionUtil() + { + ForceInitialize(); // Handles all reference types + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + ForceInitialize(); + SampleEnumMethod(); + } + + internal static void ForceInitialize() => new ReflectionHelper(); + /// /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching. /// @@ -163,7 +186,6 @@ private static bool CheckCanConvertEnumFuncToInt32Func() { try { - PreventLinkerFailures(); // Try to do the conversion using reflection, so we can see whether it's supported. MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod)); // If this passes, we're in a reasonable runtime. @@ -176,23 +198,6 @@ private static bool CheckCanConvertEnumFuncToInt32Func() } } - /// - /// This method is effectively pointless, but only called once. It's present (and called) - /// to avoid the Unity linker from removing code that's only called via reflection. - /// - private static void PreventLinkerFailures() - { - // Exercise the method directly. This should avoid any pro-active linkers from stripping - // the method out. - SampleEnum x = SampleEnumMethod(); - if (x != SampleEnum.X) - { - throw new InvalidOperationException("Failure in reflection utilities"); - } - // Make sure the ReflectionHelper parameterless constructor isn't removed... - var helper = new ReflectionHelper(); - } - public enum SampleEnum { X