public bool Process(AssemblyProcessorContext context) { var registry = new AssemblyScanRegistry(); foreach (var type in context.Assembly.MainModule.GetAllTypes()) { // Ignore interface types as well as types with generics // Note: we could support generic types at some point but we probably need // to get static generic instantiation type list from serializer code generator if (type.IsInterface || type.HasGenericParameters) { continue; } var currentType = type; // Scan type and parent types while (currentType != null) { // Scan interfaces foreach (var @interface in currentType.Interfaces) { ScanAttributes(context.Log, registry, @interface.InterfaceType, type); } ScanAttributes(context.Log, registry, currentType, type); currentType = currentType.BaseType?.Resolve(); } } if (registry.HasScanTypes) { // This code should mirror what AssemblyScanCodeGenerator.tt generates var assembly = context.Assembly; var strideCoreModule = assembly.GetStrideCoreModule(); var assemblyRegistryType = strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry"); // Generate code var assemblyScanType = new TypeDefinition("Stride.Core.Serialization.AssemblyScan", Utilities.BuildValidClassName(assembly.Name.Name) + "AssemblyScan", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract, assembly.MainModule.TypeSystem.Object); assembly.MainModule.Types.Add(assemblyScanType); // Create Initialize method var initializeMethod = new MethodDefinition("Initialize", MethodAttributes.Assembly | MethodAttributes.HideBySig | MethodAttributes.Static, assembly.MainModule.TypeSystem.Void); assemblyScanType.Methods.Add(initializeMethod); // Make sure it is called at module startup initializeMethod.AddModuleInitializer(-2000); var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); var collectionAssembly = CecilExtensions.FindCollectionsAssembly(assembly); var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly); // Type var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); var typeTypeRef = assembly.MainModule.ImportReference(typeType); var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle)); var getTokenInfoExMethod = reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo)); var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName); // Note: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime var getTypeInfoAssembly = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod; // List<Type> var listType = collectionAssembly.MainModule.GetTypeResolved(typeof(List <>).FullName); var listTypeTypeRef = assembly.MainModule.ImportReference(listType).MakeGenericType(typeTypeRef); // Dictionary<Type, List<Type>> var dictionaryType = collectionAssembly.MainModule.GetType(typeof(Dictionary <,>).FullName); var initializeMethodIL = initializeMethod.Body.GetILProcessor(); // AssemblyRegistry.RegisterScanTypes(typeof(AssemblyScanType).GetTypeInfo().Assembly, dictionary); initializeMethodIL.Emit(OpCodes.Ldtoken, assemblyScanType); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly)); // dictionary = new Dictionary<Type, List<Type>>(); initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(dictionaryType.GetEmptyConstructor()).MakeGeneric(typeTypeRef, listTypeTypeRef)); foreach (var scanTypeEntry in registry.ScanTypes) { initializeMethodIL.Emit(OpCodes.Dup); // typeof(X) initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(scanTypeEntry.Key.Resolve())); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); // new List<Type>(); initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(listType.GetEmptyConstructor()).MakeGeneric(typeTypeRef)); foreach (var scanType in scanTypeEntry.Value) { initializeMethodIL.Emit(OpCodes.Dup); initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(scanType)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(listType.Methods.First(x => x.Name == "Add")).MakeGeneric(typeTypeRef)); } initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(dictionaryType.Methods.First(x => x.Name == "Add")).MakeGeneric(typeTypeRef, listTypeTypeRef)); } initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(assemblyRegistryType.NestedTypes.First(x => x.Name == "ScanTypes").Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1))); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(assemblyRegistryType.Methods.First(x => x.Name == "RegisterScanTypes"))); initializeMethodIL.Emit(OpCodes.Ret); //var assemblyScanCodeGenerator = new AssemblyScanCodeGenerator(assembly, registry); //sourceCodeRegisterAction(assemblyScanCodeGenerator.TransformText(), "AssemblyScan"); } return(registry.HasScanTypes); }
/// <summary> /// Generates serializer code using Cecil. /// </summary> /// <param name="registry"></param> private static void GenerateSerializerCode(ComplexSerializerRegistry registry, out ObjectId serializationHash) { var hash = new ObjectIdBuilder(); // First, hash global binary format version, in case it gets bumped hash.Write(DataSerializer.BinaryFormatVersion); var assembly = registry.Assembly; var strideCoreModule = assembly.GetStrideCoreModule(); var dataSerializerTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializer`1")); var serializerSelectorType = strideCoreModule.GetType("Stride.Core.Serialization.SerializerSelector"); var serializerSelectorTypeRef = assembly.MainModule.ImportReference(serializerSelectorType); var serializerSelectorGetSerializerRef = assembly.MainModule.ImportReference(serializerSelectorType.Methods.Single(x => x.Name == "GetSerializer" && x.Parameters.Count == 0 && x.GenericParameters.Count == 1)); var memberSerializerCreateRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.MemberSerializer`1").Methods.Single(x => x.Name == "Create")); var dataSerializerSerializeMethod = dataSerializerTypeRef.Resolve().Methods.Single(x => x.Name == "Serialize" && (x.Attributes & MethodAttributes.Abstract) != 0); var dataSerializerSerializeMethodRef = assembly.MainModule.ImportReference(dataSerializerSerializeMethod); // Generate serializer code for each type (we generate code similar to ComplexClassSerializerGenerator.tt, see this file for reference) foreach (var complexType in registry.Context.ComplexTypes) { var type = complexType.Key; var serializerType = (TypeDefinition)complexType.Value.SerializerType; var genericParameters = serializerType.GenericParameters.ToArray <TypeReference>(); var typeWithGenerics = type.MakeGenericType(genericParameters); // Hash hash.Write(typeWithGenerics.FullName); TypeReference parentType = null; FieldDefinition parentSerializerField = null; if (complexType.Value.IsComplexSerializerProcessParentType) { parentType = ResolveGenericsVisitor.Process(serializerType, type.BaseType); serializerType.Fields.Add(parentSerializerField = new FieldDefinition("parentSerializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(parentType))); hash.Write("parent"); } var serializableItems = ComplexSerializerRegistry.GetSerializableItems(type).ToArray(); var serializableItemInfos = new Dictionary <TypeReference, (FieldDefinition SerializerField, TypeReference Type)>(TypeReferenceEqualityComparer.Default); var localsByTypes = new Dictionary <TypeReference, VariableDefinition>(TypeReferenceEqualityComparer.Default); ResolveGenericsVisitor genericResolver = null; if (type.HasGenericParameters) { var genericMapping = new Dictionary <TypeReference, TypeReference>(); for (int i = 0; i < type.GenericParameters.Count; i++) { genericMapping[type.GenericParameters[i]] = serializerType.GenericParameters[i]; } genericResolver = new ResolveGenericsVisitor(genericMapping); } foreach (var serializableItem in serializableItems) { if (serializableItemInfos.ContainsKey(serializableItem.Type)) { continue; } var serializableItemType = serializableItem.Type; if (genericResolver != null) { serializableItemType = genericResolver.VisitDynamic(serializableItemType); } var fieldDefinition = new FieldDefinition($"{Utilities.BuildValidClassName(serializableItemType.FullName)}Serializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(serializableItemType)); serializableItemInfos.Add(serializableItem.Type, (fieldDefinition, serializableItemType)); serializerType.Fields.Add(fieldDefinition); hash.Write(serializableItem.Type.FullName); hash.Write(serializableItem.Name); hash.Write(serializableItem.AssignBack); } // Add constructor (call parent constructor) var ctor = new MethodDefinition(".ctor", MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, assembly.MainModule.TypeSystem.Void); ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(serializerType.BaseType.Resolve().GetEmptyConstructor(true)).MakeGeneric(typeWithGenerics))); ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); serializerType.Methods.Add(ctor); // Add Initialize method var initialize = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void); initialize.Parameters.Add(new ParameterDefinition("serializerSelector", ParameterAttributes.None, serializerSelectorTypeRef)); if (complexType.Value.IsComplexSerializerProcessParentType) { initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, serializerSelectorGetSerializerRef.MakeGenericMethod(parentType))); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, parentSerializerField.MakeGeneric(genericParameters))); } foreach (var serializableItem in serializableItemInfos) { initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1)); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Call, memberSerializerCreateRef.MakeGeneric(serializableItem.Value.Type))); initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, serializableItem.Value.SerializerField.MakeGeneric(genericParameters))); } initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); serializerType.Methods.Add(initialize); // Add Serialize method var serialize = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void); serialize.Parameters.Add(new ParameterDefinition("obj", ParameterAttributes.None, typeWithGenerics.MakeByReferenceType())); // Copy other parameters from parent method for (int i = 1; i < dataSerializerSerializeMethod.Parameters.Count; ++i) { var parentParameter = dataSerializerSerializeMethod.Parameters[i]; serialize.Parameters.Add(new ParameterDefinition(parentParameter.Name, ParameterAttributes.None, assembly.MainModule.ImportReference(parentParameter.ParameterType))); } if (complexType.Value.IsComplexSerializerProcessParentType) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, parentSerializerField.MakeGeneric(genericParameters))); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(parentType))); } if (serializableItems.Length > 0) { var blockStartInstructions = new[] { Instruction.Create(OpCodes.Nop), Instruction.Create(OpCodes.Nop) }; // Iterate over ArchiveMode for (int i = 0; i < 2; ++i) { var archiveMode = i == 0 ? ArchiveMode.Serialize : ArchiveMode.Deserialize; // Check mode if (archiveMode == ArchiveMode.Serialize) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int)archiveMode)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ceq)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, blockStartInstructions[0])); } else { serialize.Body.Instructions.Add(blockStartInstructions[0]); } foreach (var serializableItem in serializableItems) { if (serializableItem.HasFixedAttribute) { throw new NotImplementedException("FixedBuffer attribute is not supported."); } var memberAssignBack = serializableItem.AssignBack; var memberVariableName = (serializableItem.MemberInfo is PropertyDefinition || !memberAssignBack) ? ComplexSerializerRegistry.CreateMemberVariableName(serializableItem.MemberInfo) : null; var serializableItemInfo = serializableItemInfos[serializableItem.Type]; serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, serializableItemInfo.SerializerField.MakeGeneric(genericParameters))); var fieldReference = serializableItem.MemberInfo is FieldReference?assembly.MainModule.ImportReference((FieldReference)serializableItem.MemberInfo).MakeGeneric(genericParameters) : null; if (memberVariableName != null) { // Use a temporary variable if (!localsByTypes.TryGetValue(serializableItemInfo.Type, out var tempLocal)) { tempLocal = new VariableDefinition(serializableItemInfo.Type); localsByTypes.Add(serializableItemInfo.Type, tempLocal); serialize.Body.Variables.Add(tempLocal); serialize.Body.InitLocals = true; } if (!(archiveMode == ArchiveMode.Deserialize && memberAssignBack)) { // obj.Member serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); if (!type.IsValueType) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); } if (serializableItem.MemberInfo is PropertyDefinition property) { var getMethod = property.Resolve().GetMethod; serialize.Body.Instructions.Add(Instruction.Create(getMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(getMethod).MakeGeneric(genericParameters))); } else if (serializableItem.MemberInfo is FieldDefinition) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, fieldReference)); } serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stloc, tempLocal)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal)); } else { // default(T) serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Dup)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Initobj, serializableItemInfo.Type)); } } else { // Use object directly serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); if (!type.IsValueType) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); } serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldflda, fieldReference)); } serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3)); serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(serializableItemInfo.Type))); if (archiveMode == ArchiveMode.Deserialize && memberVariableName != null && memberAssignBack) { // Need to copy back to object serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); if (!type.IsValueType) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); } serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloc, localsByTypes[serializableItemInfo.Type])); if (serializableItem.MemberInfo is PropertyDefinition property) { var setMethod = property.Resolve().SetMethod; serialize.Body.Instructions.Add(Instruction.Create(setMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(setMethod).MakeGeneric(genericParameters))); } else if (serializableItem.MemberInfo is FieldDefinition) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, fieldReference)); } } } if (archiveMode == ArchiveMode.Serialize) { serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Br, blockStartInstructions[1])); } } serialize.Body.Instructions.Add(blockStartInstructions[1]); } serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); serializerType.Methods.Add(serialize); //assembly.MainModule.Types.Add(serializerType); } var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly); // String var stringType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(string).FullName); var stringTypeRef = assembly.MainModule.ImportReference(stringType); // Type var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); var typeTypeRef = assembly.MainModule.ImportReference(typeType); var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle)); var getTokenInfoExMethod = reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo)); var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName); // NOTE: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime var getTypeInfoAssembly = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod; var getTypeInfoModule = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Module)).GetMethod; var typeHandleProperty = typeType.Properties.First(x => x.Name == nameof(Type.TypeHandle)); var getTypeHandleMethodRef = assembly.MainModule.ImportReference(typeHandleProperty.GetMethod); // Generate code var serializerFactoryType = new TypeDefinition("Stride.Core.DataSerializers", Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract, assembly.MainModule.TypeSystem.Object); assembly.MainModule.Types.Add(serializerFactoryType); var dataSerializerModeTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGenericMode")); var dataSerializerGlobalAttribute = strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGlobalAttribute"); var dataSerializerGlobalCtorRef = assembly.MainModule.ImportReference(dataSerializerGlobalAttribute.GetConstructors().Single(x => !x.IsStatic && x.Parameters.Count == 5)); foreach (var profile in registry.Context.SerializableTypesProfiles) { foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.IsLocal)) { // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(false)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(false) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")] serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef) { ConstructorArguments = { new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null), new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)), new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.GenericsMode), new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.IsInherited), new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.IsComplexSerializer), }, Properties = { new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key)) }, }); } foreach (var type in profile.Value.GenericSerializableTypes.Where(x => x.Value.IsLocal)) { // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(true)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(true) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")] serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef) { ConstructorArguments = { new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null), new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)), new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.GenericsMode), new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.IsInherited), new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.IsComplexSerializer), }, Properties = { new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key)) }, }); } } // Create Initialize method var initializeMethod = new MethodDefinition("Initialize", MethodAttributes.Assembly | MethodAttributes.HideBySig | MethodAttributes.Static, assembly.MainModule.TypeSystem.Void); serializerFactoryType.Methods.Add(initializeMethod); // Make sure it is called at module startup initializeMethod.AddModuleInitializer(-1000); var initializeMethodIL = initializeMethod.Body.GetILProcessor(); // Generating: var assemblySerializers = new AssemblySerializers(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly); var assemblySerializersType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializers"); var assemblySerializersGetDataContractAliasesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "DataContractAliases").GetMethod); var assemblySerializersGetDataContractAliasesAdd = assemblySerializersGetDataContractAliasesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); var dataContractAliasTypeRef = ((GenericInstanceType)assemblySerializersGetDataContractAliasesRef.ReturnType).GenericArguments[0]; var dataContractAliasTypeCtorRef = assembly.MainModule.ImportReference(dataContractAliasTypeRef.Resolve().GetConstructors().Single()); var assemblySerializersGetDataContractAliasesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetDataContractAliasesAdd).MakeGeneric(dataContractAliasTypeRef); initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly)); initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(assemblySerializersType.Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1))); foreach (var alias in registry.Context.DataContractAliases) { initializeMethodIL.Emit(OpCodes.Dup); // Generating: assemblySerializers.DataContractAliases.Add(new AssemblySerializers.DataContractAlias(@"<#= alias.Item1 #>", typeof(<#= alias.Item2.ConvertCSharp(true) #>), <#=alias.Item3 ? "true" : "false"#>)); initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesRef); initializeMethodIL.Emit(OpCodes.Ldstr, alias.Item1); initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(alias.Item2)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(alias.Item3 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); initializeMethodIL.Emit(OpCodes.Newobj, dataContractAliasTypeCtorRef); initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesAddRef); } var assemblySerializersGetModulesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Modules").GetMethod); var assemblySerializersGetModulesAdd = assemblySerializersGetModulesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); var moduleRef = ((GenericInstanceType)assemblySerializersGetModulesRef.ReturnType).GenericArguments[0]; var assemblySerializersGetModulesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetModulesAdd).MakeGeneric(moduleRef); foreach (var referencedAssemblySerializerFactoryType in registry.ReferencedAssemblySerializerFactoryTypes) { initializeMethodIL.Emit(OpCodes.Dup); // Generating: assemblySerializers.Modules.Add(typeof(<#=referencedAssemblySerializerFactoryType.ConvertCSharp()#>).GetTypeInfo().Module); initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesRef); initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(referencedAssemblySerializerFactoryType)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoModule)); initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesAddRef); } var objectIdCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Storage.ObjectId").GetConstructors().Single(x => x.Parameters.Count == 4)); var serializerEntryTypeCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerEntry").GetConstructors().Single()); var assemblySerializersPerProfileType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializersPerProfile"); var assemblySerializersPerProfileTypeAddRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.BaseType.Resolve().Methods.First(x => x.Name == "Add")).MakeGeneric(serializerEntryTypeCtorRef.DeclaringType); var assemblySerializersPerProfileTypeCtorRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.GetEmptyConstructor()); var assemblySerializersGetProfilesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Profiles").GetMethod); var assemblySerializersGetProfilesSetItemRef = assembly.MainModule.ImportReference(assemblySerializersGetProfilesRef.ReturnType.Resolve().Methods.First(x => x.Name == "set_Item")) .MakeGeneric(((GenericInstanceType)assemblySerializersGetProfilesRef.ReturnType).GenericArguments.ToArray()); var runtimeHelpersType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(RuntimeHelpers).FullName); var runClassConstructorMethod = assembly.MainModule.ImportReference(runtimeHelpersType.Methods.Single(x => x.IsPublic && x.Name == "RunClassConstructor" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.FullName == typeof(RuntimeTypeHandle).FullName)); foreach (var profile in registry.Context.SerializableTypesProfiles) { initializeMethodIL.Emit(OpCodes.Dup); // Generating: var assemblySerializersProfile = new AssemblySerializersPerProfile(); // Generating: assemblySerializers.Profiles["<#=profile.Key#>"] = assemblySerializersProfile; initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesRef); initializeMethodIL.Emit(OpCodes.Ldstr, profile.Key); initializeMethodIL.Emit(OpCodes.Newobj, assemblySerializersPerProfileTypeCtorRef); foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.IsLocal)) { // Generating: assemblySerializersProfile.Add(new AssemblySerializerEntry(<#=type.Key.ConvertTypeId()#>, typeof(<#= type.Key.ConvertCSharp() #>), <# if (type.Value.SerializerType != null) { #>typeof(<#= type.Value.SerializerType.ConvertCSharp() #>)<# } else { #>null<# } #>)); initializeMethodIL.Emit(OpCodes.Dup); var typeName = type.Key.ConvertToValidCSharp(false); var typeId = ObjectId.FromBytes(Encoding.UTF8.GetBytes(typeName)); unsafe { var typeIdHash = (int *)&typeId; for (int i = 0; i < ObjectId.HashSize / 4; ++i) { initializeMethodIL.Emit(OpCodes.Ldc_I4, typeIdHash[i]); } } initializeMethodIL.Emit(OpCodes.Newobj, objectIdCtorRef); initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Key)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); if (type.Value.SerializerType != null) { initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Value.SerializerType)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); } else { initializeMethodIL.Emit(OpCodes.Ldnull); } initializeMethodIL.Emit(OpCodes.Newobj, serializerEntryTypeCtorRef); initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersPerProfileTypeAddRef); if (type.Value.SerializerType?.Resolve()?.Methods.Any(x => x.IsConstructor && x.IsStatic) == true) { // Generating: System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(<#=type.Value.SerializerType.ConvertCSharp()#>).TypeHandle); initializeMethodIL.Append(Instruction.Create(OpCodes.Ldtoken, type.Value.SerializerType)); initializeMethodIL.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod))); initializeMethodIL.Append(Instruction.Create(OpCodes.Callvirt, getTypeHandleMethodRef)); initializeMethodIL.Append(Instruction.Create(OpCodes.Call, runClassConstructorMethod)); } } initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesSetItemRef); } // Generating: DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers); var dataSerializerFactoryRegisterSerializationAssemblyMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerFactory").Methods.Single(x => x.Name == "RegisterSerializationAssembly" && x.Parameters[0].ParameterType.FullName == assemblySerializersType.FullName)); initializeMethodIL.Emit(OpCodes.Call, dataSerializerFactoryRegisterSerializationAssemblyMethodRef); // Generating: AssemblyRegistry.Register(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly, AssemblyCommonCategories.Engine); initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly)); // create new[] { AssemblyCommonCategories.Engine } initializeMethodIL.Emit(OpCodes.Ldc_I4_1); initializeMethodIL.Emit(OpCodes.Newarr, assembly.MainModule.TypeSystem.String); initializeMethodIL.Emit(OpCodes.Dup); initializeMethodIL.Emit(OpCodes.Ldc_I4_0); initializeMethodIL.Emit(OpCodes.Ldstr, Core.Reflection.AssemblyCommonCategories.Engine); initializeMethodIL.Emit(OpCodes.Stelem_Ref); var assemblyRegistryRegisterMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry").Methods.Single(x => x.Name == "Register" && x.Parameters[1].ParameterType.IsArray)); initializeMethodIL.Emit(OpCodes.Call, assemblyRegistryRegisterMethodRef); initializeMethodIL.Emit(OpCodes.Ret); // Add AssemblySerializerFactoryAttribute var assemblySerializerFactoryAttribute = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerFactoryAttribute"); assembly.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.ImportReference(assemblySerializerFactoryAttribute.GetEmptyConstructor())) { Fields = { new CustomAttributeNamedArgument("Type", new CustomAttributeArgument(typeTypeRef, serializerFactoryType)), } }); serializationHash = hash.ComputeHash(); }
public bool Process(AssemblyProcessorContext context) { var assembly = context.Assembly; var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); if (mscorlibAssembly == null) { throw new InvalidOperationException("Missing mscorlib.dll from assembly"); } // For now, use import, but this can cause mixed framework versions when processing an assembly with a different framework version. voidType = assembly.MainModule.TypeSystem.Void; stringType = assembly.MainModule.TypeSystem.String; objectType = assembly.MainModule.TypeSystem.Object; var propertyInfoType = assembly.MainModule.ImportReference(mscorlibAssembly.MainModule.GetTypeResolved(typeof(PropertyInfo).FullName)); var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); TypeDefinition propertyChangedExtendedEventArgsType; ModuleDefinition strideCoreModule; try { strideCoreModule = assembly.GetStrideCoreModule(); } catch (Exception) { return(true); } propertyChangedExtendedEventArgsType = strideCoreModule.GetTypes().First(x => x.Name == "PropertyChangedExtendedEventArgs").Resolve(); var typeTokenInfoEx = mscorlibAssembly.MainModule.GetTypeResolved("System.Reflection.TypeInfo").Resolve(); var getPropertyMethod = typeTokenInfoEx.Methods.First(x => x.Name == "GetDeclaredProperty" && x.Parameters.Count == 1); var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == "GetTypeFromHandle"); var getTokenInfoExMethod = CecilExtensions.FindReflectionAssembly(assembly).MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == "GetTypeInfo"); var propertyChangedExtendedEventArgsConstructor = assembly.MainModule.ImportReference(propertyChangedExtendedEventArgsType.Methods.First(x => x.IsConstructor)); bool modified = false; foreach (var type in assembly.MainModule.GetTypes()) { MethodReference getPropertyChangedMethod; getPropertyChangedMethod = GetGetPropertyChangedMethod(assembly, type); //var propertyChangedField = GetPropertyChangedField(type); //if (propertyChangedField == null) // continue; var propertyChangedField = GetPropertyChangedField(type); if (getPropertyChangedMethod == null && propertyChangedField == null) { continue; } TypeReference propertyChangedFieldType; if (getPropertyChangedMethod == null) { modified = true; getPropertyChangedMethod = GetOrCreateGetPropertyChangedMethod(assembly, type, propertyChangedField); } if (propertyChangedField != null) { propertyChangedField = assembly.MainModule.ImportReference(propertyChangedField); propertyChangedFieldType = propertyChangedField.FieldType; } else { propertyChangedFieldType = getPropertyChangedMethod.ReturnType; } // Add generic to declaring type if (getPropertyChangedMethod.DeclaringType.HasGenericParameters) { getPropertyChangedMethod = getPropertyChangedMethod.MakeGeneric(getPropertyChangedMethod.DeclaringType.GenericParameters.ToArray()); } var propertyChangedInvoke = assembly.MainModule.ImportReference(propertyChangedFieldType.Resolve().Methods.First(x => x.Name == "Invoke")); foreach (var property in type.Properties) { if (property.SetMethod == null || !property.HasThis) { continue; } MethodReference propertyGetMethod = property.GetMethod; // Only patch properties that have a public Getter var methodDefinition = propertyGetMethod.Resolve(); if ((methodDefinition.Attributes & MethodAttributes.Public) != MethodAttributes.Public) { continue; } // Add generic to declaring type if (propertyGetMethod.DeclaringType.HasGenericParameters) { propertyGetMethod = propertyGetMethod.MakeGeneric(propertyGetMethod.DeclaringType.GenericParameters.ToArray()); } //var versionableAttribute = property.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == typeof(VersionableAttribute).FullName); //if (versionableAttribute == null) // continue; modified = true; FieldReference staticField = new FieldDefinition(property.Name + "PropertyInfo", FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly, propertyInfoType); type.Fields.Add((FieldDefinition)staticField); // Add generic to declaring type if (staticField.DeclaringType.HasGenericParameters) { staticField = staticField.MakeGeneric(staticField.DeclaringType.GenericParameters.ToArray()); } // In static constructor, find PropertyInfo and store it in static field { var staticConstructor = type.GetStaticConstructor(); if (staticConstructor == null) { staticConstructor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, voidType); staticConstructor.Body.GetILProcessor().Append(Instruction.Create(OpCodes.Ret)); type.Methods.Add(staticConstructor); } VariableReference localTokenEx = null; int localTokenExIndex = 0; for (int i = 0; i < staticConstructor.Body.Variables.Count; i++) { var localVar = staticConstructor.Body.Variables[i]; if (localVar.VariableType.FullName == typeTokenInfoEx.FullName) { localTokenEx = localVar; localTokenExIndex = i; break; } } if (localTokenEx == null) { localTokenEx = new VariableDefinition(assembly.MainModule.ImportReference(typeTokenInfoEx)); staticConstructor.Body.Variables.Add((VariableDefinition)localTokenEx); localTokenExIndex = staticConstructor.Body.Variables.Count - 1; } var ilProcessor = staticConstructor.Body.GetILProcessor(); var returnInstruction = staticConstructor.Body.Instructions.Last(); var newReturnInstruction = Instruction.Create(returnInstruction.OpCode); newReturnInstruction.Operand = returnInstruction.Operand; returnInstruction.OpCode = OpCodes.Nop; returnInstruction.Operand = null; // Find PropertyInfo and store it in static field ilProcessor.Append(Instruction.Create(OpCodes.Ldtoken, type)); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod))); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod))); ilProcessor.Append(LocationToStloc(ilProcessor, localTokenExIndex)); ilProcessor.Append(ilProcessor.Create(OpCodes.Ldloca_S, (byte)localTokenExIndex)); ilProcessor.Append(Instruction.Create(OpCodes.Ldstr, property.Name)); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getPropertyMethod))); ilProcessor.Append(Instruction.Create(OpCodes.Stsfld, staticField)); ilProcessor.Append(newReturnInstruction); } { var ilProcessor = property.SetMethod.Body.GetILProcessor(); var returnInstruction = property.SetMethod.Body.Instructions.Last(); var firstInstruction = property.SetMethod.Body.Instructions[0]; if (property.SetMethod.Body.Instructions[0].OpCode != OpCodes.Nop) { ilProcessor.InsertBefore(property.SetMethod.Body.Instructions[0], Instruction.Create(OpCodes.Nop)); } var newReturnInstruction = Instruction.Create(returnInstruction.OpCode); newReturnInstruction.Operand = returnInstruction.Operand; returnInstruction.OpCode = OpCodes.Nop; returnInstruction.Operand = null; var propertyChangedVariable = new VariableDefinition(assembly.MainModule.ImportReference(propertyChangedFieldType)); property.SetMethod.Body.Variables.Add(propertyChangedVariable); var oldValueVariable = new VariableDefinition(objectType); property.SetMethod.Body.Variables.Add(oldValueVariable); Instruction jump1, jump2; // Prepend: // var propertyChanged = GetPropertyChanged(); // var oldValue = propertyChanged != null ? (object)Property : null; property.SetMethod.Body.SimplifyMacros(); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, getPropertyChangedMethod)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, propertyChangedVariable)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.InsertBefore(firstInstruction, jump1 = Instruction.Create(OpCodes.Brtrue, Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldnull)); ilProcessor.InsertBefore(firstInstruction, jump2 = Instruction.Create(OpCodes.Br, Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump1.Operand = Instruction.Create(OpCodes.Ldarg_0))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, propertyGetMethod)); if (property.PropertyType.IsValueType) { ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Box, property.PropertyType)); } ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump2.Operand = Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, oldValueVariable)); // Append: // if (propertyChanged != null) // propertyChanged(this, new PropertyChangedExtendedEventArgs("Property", oldValue, Property)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldnull)); ilProcessor.Append(Instruction.Create(OpCodes.Ceq)); ilProcessor.Append(Instruction.Create(OpCodes.Brtrue, newReturnInstruction)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.Append(Instruction.Create(OpCodes.Ldsfld, staticField)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, oldValueVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.Append(Instruction.Create(OpCodes.Call, propertyGetMethod)); if (property.PropertyType.IsValueType) { ilProcessor.Append(Instruction.Create(OpCodes.Box, property.PropertyType)); } ilProcessor.Append(Instruction.Create(OpCodes.Newobj, propertyChangedExtendedEventArgsConstructor)); ilProcessor.Append(Instruction.Create(OpCodes.Callvirt, propertyChangedInvoke)); ilProcessor.Append(Instruction.Create(OpCodes.Nop)); ilProcessor.Append(newReturnInstruction); property.SetMethod.Body.OptimizeMacros(); } } } return(modified); }