Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
NotifyPropertyProcessor.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Linq;
5 using System.Reflection;
6 using Mono.Cecil;
7 using Mono.Cecil.Cil;
8 using Mono.Cecil.Pdb;
9 using Mono.Cecil.Rocks;
12 
13 namespace SiliconStudio.AssemblyProcessor
14 {
15  /// <summary>
16  /// This class is no longer used now.
17  /// TODO: Check if we need to keep this around
18  /// </summary>
20  {
21  private TypeReference voidType, stringType, objectType;
22 
24  {
25  // Force inclusion of Mono.Cecil.Pdb.dll by referencing it
26  typeof(PdbReader).ToString();
27  }
28 
29  public FieldReference GetPropertyChangedField(TypeDefinition typeDefinition)
30  {
31  // Not sure whether it's better to detect through name+type, or actual INotifyPropertyChanged interface (which we might not want to add sometime)
32  var propertyChangedField = typeDefinition.Fields.FirstOrDefault(x => x.FieldType.FullName == typeof(System.ComponentModel.PropertyChangedEventHandler).FullName && x.Name == "PropertyChanged");
33  if (propertyChangedField != null)
34  return propertyChangedField;
35 
36  // Go through inheritance hierarchy
37  //if (typeDefinition.BaseType != null)
38  // return GetPropertyChangedField(typeDefinition.BaseType.Resolve());
39 
40  return null;
41  }
42 
43  public MethodReference GetGetPropertyChangedMethod(AssemblyDefinition assembly, TypeDefinition typeDefinition)
44  {
45  var method = typeDefinition.Methods.FirstOrDefault(x => x.Name == "GetPropertyChanged");
46  if (method != null)
47  return assembly.MainModule.Import(method);
48 
49  if (typeDefinition.BaseType != null)
50  return GetGetPropertyChangedMethod(assembly, typeDefinition.BaseType.Resolve());
51 
52  return null;
53  }
54 
55  public MethodReference GetOrCreateGetPropertyChangedMethod(AssemblyDefinition assembly, TypeDefinition typeDefinition, FieldReference propertyChangedField)
56  {
57  var methodReference = GetGetPropertyChangedMethod(assembly, typeDefinition);
58  if (methodReference != null)
59  return methodReference;
60 
61  var method = new MethodDefinition("GetPropertyChanged", MethodAttributes.Family, propertyChangedField.FieldType);
62 
63  var bodyInstructions = method.Body.Instructions;
64 
65  // return PropertyChanged
66  bodyInstructions.Add(Instruction.Create(OpCodes.Ldarg_0));
67  bodyInstructions.Add(Instruction.Create(OpCodes.Ldfld, propertyChangedField));
68  bodyInstructions.Add(Instruction.Create(OpCodes.Ret));
69 
70  typeDefinition.Methods.Add(method);
71 
72  return method;
73  }
74 
75  public bool Process(AssemblyProcessorContext context)
76  {
77  var assembly = context.Assembly;
78  var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly);
79  if (mscorlibAssembly == null)
80  throw new InvalidOperationException("Missing mscorlib.dll from assembly");
81 
82  // For now, use import, but this can cause mixed framework versions when processing an assembly with a different framework version.
83  voidType = assembly.MainModule.Import(mscorlibAssembly.MainModule.GetTypeResolved(typeof(void).FullName));
84  stringType = assembly.MainModule.Import(mscorlibAssembly.MainModule.GetTypeResolved(typeof(string).FullName));
85  objectType = assembly.MainModule.Import(mscorlibAssembly.MainModule.GetTypeResolved(typeof(object).FullName));
86  var propertyInfoType = assembly.MainModule.Import(mscorlibAssembly.MainModule.GetTypeResolved(typeof(PropertyInfo).FullName));
87 
88  var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName);
89 
90 
91  TypeDefinition propertyChangedExtendedEventArgsType;
92 
93  AssemblyDefinition siliconStudioCoreAssembly = null;
94  try
95  {
96  siliconStudioCoreAssembly = assembly.Name.Name == "SiliconStudio.Core"
97  ? assembly
98  : context.AssemblyResolver.Resolve("SiliconStudio.Core");
99 
100  }
101  catch (Exception)
102  {
103  return true;
104  }
105 
106  propertyChangedExtendedEventArgsType = siliconStudioCoreAssembly.MainModule.GetTypes().First(x => x.Name == "PropertyChangedExtendedEventArgs").Resolve();
107 
108  var typeTokenInfoEx = mscorlibAssembly.MainModule.GetType("System.Reflection.TypeInfo").Resolve();
109  var getPropertyMethod = typeTokenInfoEx.Methods.First(x => x.Name == "GetDeclaredProperty" && x.Parameters.Count == 1);
110  var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == "GetTypeFromHandle");
111  var getTokenInfoExMethod = mscorlibAssembly.MainModule.GetType("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == "GetTypeInfo");
112 
113  var propertyChangedExtendedEventArgsConstructor = assembly.MainModule.Import(propertyChangedExtendedEventArgsType.Methods.First(x => x.IsConstructor));
114 
115  bool modified = false;
116 
117  foreach (var type in assembly.MainModule.GetTypes())
118  {
119  MethodReference getPropertyChangedMethod = null;
120 
121  getPropertyChangedMethod = GetGetPropertyChangedMethod(assembly, type);
122  //var propertyChangedField = GetPropertyChangedField(type);
123  //if (propertyChangedField == null)
124  // continue;
125 
126  var propertyChangedField = GetPropertyChangedField(type);
127 
128  if (getPropertyChangedMethod == null && propertyChangedField == null)
129  continue;
130 
131  TypeReference propertyChangedFieldType;
132 
133  if (getPropertyChangedMethod == null)
134  {
135  modified = true;
136  getPropertyChangedMethod = GetOrCreateGetPropertyChangedMethod(assembly, type, propertyChangedField);
137  }
138 
139  if (propertyChangedField != null)
140  {
141  propertyChangedField = assembly.MainModule.Import(propertyChangedField);
142  propertyChangedFieldType = propertyChangedField.FieldType;
143  }
144  else
145  {
146  propertyChangedFieldType = getPropertyChangedMethod.ReturnType;
147  }
148 
149  // Add generic to declaring type
150  if (getPropertyChangedMethod.DeclaringType.HasGenericParameters)
151  getPropertyChangedMethod = getPropertyChangedMethod.MakeGeneric(getPropertyChangedMethod.DeclaringType.GenericParameters.ToArray());
152 
153  var propertyChangedInvoke = assembly.MainModule.Import(propertyChangedFieldType.Resolve().Methods.First(x => x.Name == "Invoke"));
154 
155  foreach (var property in type.Properties)
156  {
157  if (property.SetMethod == null || !property.HasThis)
158  continue;
159 
160  MethodReference propertyGetMethod = property.GetMethod;
161 
162  // Only patch properties that have a public Getter
163  var methodDefinition = propertyGetMethod.Resolve();
164  if ((methodDefinition.Attributes & MethodAttributes.Public) != MethodAttributes.Public)
165  {
166  continue;
167  }
168 
169  // Add generic to declaring type
170  if (propertyGetMethod.DeclaringType.HasGenericParameters)
171  propertyGetMethod = propertyGetMethod.MakeGeneric(propertyGetMethod.DeclaringType.GenericParameters.ToArray());
172 
173  //var versionableAttribute = property.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == typeof(VersionableAttribute).FullName);
174  //if (versionableAttribute == null)
175  // continue;
176 
177  modified = true;
178 
179  FieldReference staticField = new FieldDefinition(property.Name + "PropertyInfo", FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly, propertyInfoType);
180  type.Fields.Add((FieldDefinition)staticField);
181 
182  // Add generic to declaring type
183  if (staticField.DeclaringType.HasGenericParameters)
184  staticField = staticField.MakeGeneric(staticField.DeclaringType.GenericParameters.ToArray());
185 
186  // In static constructor, find PropertyInfo and store it in static field
187  {
188  var staticConstructor = type.GetStaticConstructor();
189  if (staticConstructor == null)
190  {
191  staticConstructor = new MethodDefinition(".cctor",
192  MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
193  voidType);
194  staticConstructor.Body.GetILProcessor().Append(Instruction.Create(OpCodes.Ret));
195 
196  type.Methods.Add(staticConstructor);
197  }
198 
199 
200  VariableReference localTokenEx = null;
201  int localTokenExIndex = 0;
202  for (int i = 0; i < staticConstructor.Body.Variables.Count; i++)
203  {
204  var localVar = staticConstructor.Body.Variables[i];
205  if (localVar.VariableType.FullName == typeTokenInfoEx.FullName)
206  {
207  localTokenEx = localVar;
208  localTokenExIndex = i;
209  break;
210  }
211  }
212 
213  if (localTokenEx == null)
214  {
215  localTokenEx = new VariableDefinition(assembly.MainModule.Import(typeTokenInfoEx));
216  staticConstructor.Body.Variables.Add((VariableDefinition)localTokenEx);
217  localTokenExIndex = staticConstructor.Body.Variables.Count - 1;
218  }
219 
220  var ilProcessor = staticConstructor.Body.GetILProcessor();
221  var returnInstruction = staticConstructor.Body.Instructions.Last();
222 
223  var newReturnInstruction = Instruction.Create(returnInstruction.OpCode);
224  newReturnInstruction.Operand = returnInstruction.Operand;
225 
226  returnInstruction.OpCode = OpCodes.Nop;
227  returnInstruction.Operand = null;
228 
229  // Find PropertyInfo and store it in static field
230  ilProcessor.Append(Instruction.Create(OpCodes.Ldtoken, type));
231  ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.Import(getTypeFromHandleMethod)));
232  ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.Import(getTokenInfoExMethod)));
233  ilProcessor.Append(LocationToStloc(ilProcessor, localTokenExIndex));
234  ilProcessor.Append(ilProcessor.Create(OpCodes.Ldloca_S, (byte)localTokenExIndex));
235  ilProcessor.Append(Instruction.Create(OpCodes.Ldstr, property.Name));
236  ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.Import(getPropertyMethod)));
237  ilProcessor.Append(Instruction.Create(OpCodes.Stsfld, staticField));
238 
239  ilProcessor.Append(newReturnInstruction);
240  }
241 
242  {
243  var ilProcessor = property.SetMethod.Body.GetILProcessor();
244  var returnInstruction = property.SetMethod.Body.Instructions.Last();
245 
246  var firstInstruction = property.SetMethod.Body.Instructions[0];
247 
248  if (property.SetMethod.Body.Instructions[0].OpCode != OpCodes.Nop)
249  ilProcessor.InsertBefore(property.SetMethod.Body.Instructions[0], Instruction.Create(OpCodes.Nop));
250 
251  var newReturnInstruction = Instruction.Create(returnInstruction.OpCode);
252  newReturnInstruction.Operand = returnInstruction.Operand;
253 
254  returnInstruction.OpCode = OpCodes.Nop;
255  returnInstruction.Operand = null;
256 
257  var propertyChangedVariable = new VariableDefinition("propertyChanged", assembly.MainModule.Import(propertyChangedFieldType));
258  property.SetMethod.Body.Variables.Add(propertyChangedVariable);
259 
260  var oldValueVariable = new VariableDefinition("oldValue", objectType);
261  property.SetMethod.Body.Variables.Add(oldValueVariable);
262 
263  Instruction jump1, jump2;
264 
265  // Prepend:
266  // var propertyChanged = GetPropertyChanged();
267  // var oldValue = propertyChanged != null ? (object)Property : null;
268  property.SetMethod.Body.SimplifyMacros();
269  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldarg_0));
270  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, getPropertyChangedMethod));
271  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, propertyChangedVariable));
272  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldloc, propertyChangedVariable));
273  ilProcessor.InsertBefore(firstInstruction, jump1 = Instruction.Create(OpCodes.Brtrue, Instruction.Create(OpCodes.Nop)));
274  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldnull));
275  ilProcessor.InsertBefore(firstInstruction, jump2 = Instruction.Create(OpCodes.Br, Instruction.Create(OpCodes.Nop)));
276  ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump1.Operand = Instruction.Create(OpCodes.Ldarg_0)));
277  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, propertyGetMethod));
278  if (property.PropertyType.IsValueType)
279  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Box, property.PropertyType));
280  ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump2.Operand = Instruction.Create(OpCodes.Nop)));
281  ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, oldValueVariable));
282 
283  // Append:
284  // if (propertyChanged != null)
285  // propertyChanged(this, new PropertyChangedExtendedEventArgs("Property", oldValue, Property));
286  ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable));
287  ilProcessor.Append(Instruction.Create(OpCodes.Ldnull));
288  ilProcessor.Append(Instruction.Create(OpCodes.Ceq));
289  ilProcessor.Append(Instruction.Create(OpCodes.Brtrue, newReturnInstruction));
290  ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable));
291  ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0));
292  ilProcessor.Append(Instruction.Create(OpCodes.Ldsfld, staticField));
293  ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, oldValueVariable));
294  ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0));
295  ilProcessor.Append(Instruction.Create(OpCodes.Call, propertyGetMethod));
296  if (property.PropertyType.IsValueType)
297  ilProcessor.Append(Instruction.Create(OpCodes.Box, property.PropertyType));
298  ilProcessor.Append(Instruction.Create(OpCodes.Newobj, propertyChangedExtendedEventArgsConstructor));
299  ilProcessor.Append(Instruction.Create(OpCodes.Callvirt, propertyChangedInvoke));
300  ilProcessor.Append(Instruction.Create(OpCodes.Nop));
301  ilProcessor.Append(newReturnInstruction);
302  property.SetMethod.Body.OptimizeMacros();
303  }
304  }
305  }
306 
307  return modified;
308  }
309 
310  // TODO MOVE THIS CODE TO FACTORIZE IT
311  private static Instruction LocationToStloc(ILProcessor ilProcessor, int index)
312  {
313  Instruction stlocFixed;
314  switch (index)
315  {
316  case 0:
317  stlocFixed = ilProcessor.Create(OpCodes.Stloc_0);
318  break;
319  case 1:
320  stlocFixed = ilProcessor.Create(OpCodes.Stloc_1);
321  break;
322  case 2:
323  stlocFixed = ilProcessor.Create(OpCodes.Stloc_2);
324  break;
325  case 3:
326  stlocFixed = ilProcessor.Create(OpCodes.Stloc_3);
327  break;
328  default:
329  stlocFixed = ilProcessor.Create(OpCodes.Stloc, index);
330  break;
331  }
332  return stlocFixed;
333  }
334 
335  // TODO MOVE THIS CODE TO FACTORIZE IT
336  private static Instruction LocationToLdLoc(ILProcessor ilProcessor, int index)
337  {
338  Instruction ldlocFixed;
339  switch (index)
340  {
341  case 0:
342  ldlocFixed = ilProcessor.Create(OpCodes.Ldloc_0);
343  break;
344  case 1:
345  ldlocFixed = ilProcessor.Create(OpCodes.Ldloc_1);
346  break;
347  case 2:
348  ldlocFixed = ilProcessor.Create(OpCodes.Ldloc_2);
349  break;
350  case 3:
351  ldlocFixed = ilProcessor.Create(OpCodes.Ldloc_3);
352  break;
353  default:
354  ldlocFixed = ilProcessor.Create(OpCodes.Ldloc, index);
355  break;
356  }
357  return ldlocFixed;
358  }
359  }
360 }
FieldReference GetPropertyChangedField(TypeDefinition typeDefinition)
Mono.Cecil.MethodAttributes MethodAttributes
Mono.Cecil.FieldAttributes FieldAttributes
MethodReference GetGetPropertyChangedMethod(AssemblyDefinition assembly, TypeDefinition typeDefinition)
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
This class is no longer used now. TODO: Check if we need to keep this around
Mono.Cecil.MethodAttributes MethodAttributes
MethodReference GetOrCreateGetPropertyChangedMethod(AssemblyDefinition assembly, TypeDefinition typeDefinition, FieldReference propertyChangedField)