// // Instantiator.cs // // Author: // Jean-Philippe Bruyère // // Copyright (c) 2013-2017 Jean-Philippe Bruyère // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Threading; using System.IO; using System.Text; using System.Diagnostics; using System.Xml; using System.Reflection.Emit; using System.Reflection; using System.Collections.Generic; using System.Linq; using Crow.IML; namespace Crow { public delegate object InstanciatorInvoker(Interface iface); /// /// Reflexion being very slow, the settings of the starting values for widgets are set by a dynamic method. /// This method is created on the first instacing and is recalled for further widget instancing. /// /// It includes: /// - XML values setting /// - Default values (appearing as attribute in C#) loading /// - Styling /// /// Instantiators are shared amongs interfaces. Their are stored with their path as key, and inlined template /// and itemtemplate are stored with a generated uuid /// public class Instantiator { #region Dynamic Method ID generation static long curId = 0; internal static long NewId { get { return curId++; } } #endregion public Type RootType; InstanciatorInvoker loader; internal string sourcePath; #region CTOR /// /// Initializes a new instance of the Instantiator class. /// public Instantiator (string path) : this (Interface.GetStreamFromPath(path)) { sourcePath = path; } /// /// Initializes a new instance of the Instantiator class. /// public Instantiator (Stream stream) { #if DEBUG_LOAD Stopwatch loadingTime = new Stopwatch (); loadingTime.Start (); #endif using (XmlTextReader itr = new XmlTextReader (stream)) { parseIML (itr); } #if DEBUG_LOAD loadingTime.Stop (); Debug.WriteLine ("IML Instantiator creation '{2}' : {0} ticks, {1} ms", loadingTime.ElapsedTicks, loadingTime.ElapsedMilliseconds, imlPath); #endif } //TODO:check if still used public Instantiator (Type _root, InstanciatorInvoker _loader) { RootType = _root; loader = _loader; } /// /// Create a new instantiator from IML fragment provided directely as a string /// /// A new instantiator /// IML string public static Instantiator CreateFromImlFragment (string fragment) { try { using (Stream s = new MemoryStream (Encoding.UTF8.GetBytes (fragment))) { return new Instantiator (s); } } catch (Exception ex) { throw new Exception ("Error loading IML fragment:\n" + fragment + "\n", ex); } } #endregion /// /// Creates a new instance of the GraphicObject compiled in the instantiator /// and bind it the an interface /// /// The new graphic object instance /// The interface to bind to public GraphicObject CreateInstance(Interface iface){ return loader (iface) as GraphicObject; } List dsValueChangedDynMeths = new List(); List cachedDelegates = new List(); /// /// store indices of template delegate to be handled by root parentChanged event /// List templateCachedDelegateIndices = new List(); Delegate templateBinding; #region IML parsing /// /// Parses IML and build a dynamic method that will be used to instanciate one or multiple occurence of the IML file or fragment /// void parseIML (XmlTextReader reader) { Context ctx = new Context (findRootType (reader)); ctx.nodesStack.Push (new Node (ctx.RootType)); emitLoader (reader, ctx); ctx.nodesStack.Pop (); foreach (int idx in templateCachedDelegateIndices) ctx.emitCachedDelegateHandlerAddition(idx, CompilerServices.eiLogicalParentChanged); ctx.ResolveNamedTargets (); emitBindingDelegates (ctx); ctx.il.Emit (OpCodes.Ldloc_0);//load root obj to return ctx.il.Emit(OpCodes.Ret); reader.Read ();//close tag RootType = ctx.RootType; loader = (InstanciatorInvoker)ctx.dm.CreateDelegate (typeof (InstanciatorInvoker), this); } /// /// read first node to set GraphicObject class for loading /// and let reader position on that node /// Type findRootType (XmlTextReader reader) { string root = "Object"; while (reader.Read ()) { if (reader.NodeType == XmlNodeType.Element) { root = reader.Name; break; } } Type t = tryGetGOType (root); if (t == null) throw new Exception ("IML parsing error: undefined root type (" + root + ")"); return t; } void emitLoader (XmlTextReader reader, Context ctx) { string tmpXml = reader.ReadOuterXml (); if (ctx.nodesStack.Peek().HasTemplate) emitTemplateLoad (ctx, tmpXml); emitGOLoad (ctx, tmpXml); //emitCheckAndBindValueChanged (ctx); } /// /// process template and item template definition prior to /// other attributes or childs processing /// /// Loading Context /// xml fragment void emitTemplateLoad (Context ctx, string tmpXml) { //if its a template, first read template elements using (XmlTextReader reader = new XmlTextReader (tmpXml, XmlNodeType.Element, null)) { List itemTemplateIds = new List (); bool inlineTemplate = false; reader.Read (); string templatePath = reader.GetAttribute ("Template"); int depth = reader.Depth + 1; while (reader.Read ()) { if (!reader.IsStartElement () || reader.Depth > depth) continue; if (reader.Name == "Template") { inlineTemplate = true; reader.Read (); readChildren (reader, ctx, -1); } else if (reader.Name == "ItemTemplate") { string dataType = "default", datas = "", path = ""; while (reader.MoveToNextAttribute ()) { if (reader.Name == "DataType") dataType = reader.Value; else if (reader.Name == "Data") datas = reader.Value; else if (reader.Name == "Path") path = reader.Value; } reader.MoveToElement (); string itemTmpID; if (string.IsNullOrEmpty (path)) { itemTmpID = Guid.NewGuid ().ToString (); Interface.Instantiators [itemTmpID] = new ItemTemplate (new MemoryStream (Encoding.UTF8.GetBytes (reader.ReadInnerXml ())), dataType, datas); } else { if (!reader.IsEmptyElement) throw new Exception ("ItemTemplate with Path attribute may not include sub nodes"); itemTmpID = path+dataType+datas; if (!Interface.Instantiators.ContainsKey (itemTmpID)) Interface.Instantiators [itemTmpID] = new ItemTemplate (Interface.GetStreamFromPath (itemTmpID), dataType, datas); } itemTemplateIds.Add (new string [] { dataType, itemTmpID, datas }); } } if (!inlineTemplate) {//load from path or default template ctx.il.Emit (OpCodes.Ldloc_0);//Load current templatedControl ref if (string.IsNullOrEmpty (templatePath)) { ctx.il.Emit (OpCodes.Ldnull);//default template loading } else { ctx.il.Emit (OpCodes.Ldarg_1);//load currentInterface ctx.il.Emit (OpCodes.Ldstr, templatePath); //Load template path string ctx.il.Emit (OpCodes.Callvirt,//call Interface.Load(path) CompilerServices.miIFaceLoad); } ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miLoadTmp);//load template } //copy item templates (review this) foreach (string [] iTempId in itemTemplateIds) { ctx.il.Emit (OpCodes.Ldloc_0);//load TempControl ref ctx.il.Emit (OpCodes.Ldfld, CompilerServices.fldItemTemplates);//load ItemTemplates dic field ctx.il.Emit (OpCodes.Ldstr, iTempId [0]);//load key ctx.il.Emit (OpCodes.Ldstr, iTempId [1]);//load value ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miGetITemp); ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miAddITemp); if (!string.IsNullOrEmpty (iTempId [2])) { //expand delegate creation ctx.il.Emit (OpCodes.Ldloc_0);//load TempControl ref ctx.il.Emit (OpCodes.Ldfld, CompilerServices.fldItemTemplates); ctx.il.Emit (OpCodes.Ldstr, iTempId [0]);//load key ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miGetITempFromDic); ctx.il.Emit (OpCodes.Ldloc_0);//load root of treeView ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miCreateExpDel); } } } } /// /// process styling, attributes and children loading. /// /// parsing context /// xml fragment void emitGOLoad (Context ctx, string tmpXml) { using (XmlTextReader reader = new XmlTextReader (tmpXml, XmlNodeType.Element, null)) { reader.Read (); #region Styling and default values loading //first check for Style attribute then trigger default value loading if (reader.HasAttributes) { string style = reader.GetAttribute ("Style"); if (!string.IsNullOrEmpty (style)) CompilerServices.EmitSetValue (ctx.il, CompilerServices.piStyle, style); } ctx.il.Emit (OpCodes.Ldloc_0); ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miLoadDefaultVals); #endregion #region Attributes reading if (reader.HasAttributes) { while (reader.MoveToNextAttribute ()) { if (reader.Name == "Style") continue; MemberInfo mi = ctx.CurrentNodeType.GetMember (reader.Name).FirstOrDefault (); if (mi == null) throw new Exception ("Member '" + reader.Name + "' not found in " + ctx.CurrentNodeType.Name); if (mi.MemberType == MemberTypes.Event) { foreach (string exp in CompilerServices.splitOnSemiColumnOutsideAccolades(reader.Value)) { string trimed = exp.Trim(); if (trimed.StartsWith ("{", StringComparison.OrdinalIgnoreCase)) compileAndStoreDynHandler (ctx, mi as EventInfo, trimed.Substring (1, trimed.Length - 2)); else emitHandlerBinding (ctx, mi as EventInfo, trimed); } continue; } PropertyInfo pi = mi as PropertyInfo; if (pi == null) throw new Exception ("Member '" + reader.Name + "' is not a property in " + ctx.CurrentNodeType.Name); if (pi.Name == "Name") ctx.StoreCurrentName (reader.Value); if (reader.Value.StartsWith ("{", StringComparison.OrdinalIgnoreCase)) readPropertyBinding (ctx, reader.Name, reader.Value.Substring (1, reader.Value.Length - 2)); else CompilerServices.EmitSetValue (ctx.il, pi, reader.Value); } reader.MoveToElement (); } #endregion readChildren (reader, ctx); ctx.nodesStack.ResetCurrentNodeIndex (); } } /// /// Parse child node an generate corresponding msil /// void readChildren (XmlTextReader reader, Context ctx, int startingIdx = 0) { bool endTagReached = false; int nodeIdx = startingIdx; while (reader.Read ()) { switch (reader.NodeType) { case XmlNodeType.EndElement: endTagReached = true; break; case XmlNodeType.Element: //skip Templates if (reader.Name == "Template" || reader.Name == "ItemTemplate") { reader.Skip (); continue; } //push 2x current instance on stack for parenting and reseting loc0 to parent //loc_0 will be used for child ctx.il.Emit (OpCodes.Ldloc_0); ctx.il.Emit (OpCodes.Ldloc_0); Type t = tryGetGOType (reader.Name); if (t == null) throw new Exception (reader.Name + " type not found"); ConstructorInfo ci = t.GetConstructor ( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Type.EmptyTypes, null); if (ci == null) throw new Exception ("No default parameterless constructor found in " + t.Name); ctx.il.Emit (OpCodes.Newobj, ci); ctx.il.Emit (OpCodes.Stloc_0);//child is now loc_0 CompilerServices.emitSetCurInterface (ctx.il); ctx.nodesStack.Push (new Node (t, nodeIdx)); emitLoader (reader, ctx); ctx.nodesStack.Pop (); ctx.il.Emit (OpCodes.Ldloc_0);//load child on stack for parenting ctx.il.Emit (OpCodes.Callvirt, ctx.nodesStack.Peek().GetAddMethod(nodeIdx)); ctx.il.Emit (OpCodes.Stloc_0); //reset local to current go nodeIdx++; break; } if (endTagReached) break; } } #endregion void readPropertyBinding (Context ctx, string sourceMember, string expression) { NodeAddress sourceNA = ctx.CurrentNodeAddress; BindingDefinition bindingDef = sourceNA.GetBindingDef (sourceMember, expression); #if DEBUG_BINDING Debug.WriteLine("Property Binding: " + bindingDef.ToString()); #endif if (bindingDef.IsDataSourceBinding)//bind on data source emitDataSourceBindings (ctx, bindingDef); else ctx.StorePropertyBinding (bindingDef); } #region Emit Helper void dataSourceChangedEmitHelper(object dscSource, object dataSource, int dynMethIdx){ if (dataSource is IValueChange) (dataSource as IValueChange).ValueChanged += (EventHandler)dsValueChangedDynMeths [dynMethIdx].CreateDelegate (typeof(EventHandler), dscSource); } /// Emits remove old data source event handler./summary> void emitRemoveOldDataSourceHandler(ILGenerator il, string eventName, string delegateName, bool DSSide = true){ System.Reflection.Emit.Label cancel = il.DefineLabel (); il.Emit (OpCodes.Ldarg_2);//load old parent il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); il.Emit (OpCodes.Brfalse, cancel);//old parent is null //remove handler if (DSSide){//event is defined in the dataSource instance il.Emit (OpCodes.Ldarg_2);//1st arg load old datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); }else//the event is in the source il.Emit (OpCodes.Ldarg_1);//1st arg load old datasource il.Emit (OpCodes.Ldstr, eventName);//2nd arg event name il.Emit (OpCodes.Ldstr, delegateName);//3d arg: delegate name il.Emit (OpCodes.Call, CompilerServices.miRemEvtHdlByName); il.MarkLabel(cancel); } #endregion #region Event Bindings /// /// Compile events expression in IML attributes, and store the result in the instanciator /// Those handlers will be bound when instatiing /// void compileAndStoreDynHandler (Context ctx, EventInfo sourceEvent, string expression) { //store event handler dynamic method in instanciator int dmIdx = cachedDelegates.Count; cachedDelegates.Add (CompilerServices.compileDynEventHandler (sourceEvent, expression, ctx.CurrentNodeAddress)); ctx.emitCachedDelegateHandlerAddition(dmIdx, sourceEvent); } /// Emits handler method bindings void emitHandlerBinding (Context ctx, EventInfo sourceEvent, string expression){ NodeAddress currentNode = ctx.CurrentNodeAddress; BindingDefinition bindingDef = currentNode.GetBindingDef (sourceEvent.Name, expression); #if DEBUG_BINDING Debug.WriteLine("Event Binding: " + bindingDef.ToString()); #endif if (bindingDef.IsTemplateBinding | bindingDef.IsDataSourceBinding) { //we need to bind datasource method to source event DynamicMethod dm = new DynamicMethod ("dyn_dsORtmpChangedForHandler" + NewId, typeof(void), CompilerServices.argsBoundDSChange, true); ILGenerator il = dm.GetILGenerator (256); System.Reflection.Emit.Label cancel = il.DefineLabel (); il.DeclareLocal (typeof(MethodInfo));//used to cancel binding if method doesn't exist il.Emit (OpCodes.Nop); emitRemoveOldDataSourceHandler (il, sourceEvent.Name, bindingDef.TargetMember, false); //fetch method in datasource and test if it exist il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Brfalse, cancel);//cancel if new datasource is null il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//load handler method name il.Emit (OpCodes.Call, CompilerServices.miGetMethInfoWithRefx); il.Emit (OpCodes.Stloc_0);//save MethodInfo il.Emit (OpCodes.Ldloc_0);//push mi for test if null il.Emit (OpCodes.Brfalse, cancel); il.Emit (OpCodes.Ldarg_1);//load datasource change source where the event is as 1st arg of handler.add if (bindingDef.IsTemplateBinding)//fetch source instance with address CompilerServices.emitGetInstance (il, bindingDef.SourceNA); //load handlerType of sourceEvent to create delegate (1st arg) il.Emit (OpCodes.Ldtoken, sourceEvent.EventHandlerType); il.Emit (OpCodes.Call, CompilerServices.miGetTypeFromHandle); il.Emit (OpCodes.Ldarg_2);//load new datasource where the method is defined il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Ldloc_0);//load methodInfo (3rd arg) il.Emit (OpCodes.Callvirt, CompilerServices.miCreateBoundDel); il.Emit (OpCodes.Callvirt, sourceEvent.AddMethod);//call add event System.Reflection.Emit.Label finish = il.DefineLabel (); il.Emit (OpCodes.Br, finish); il.MarkLabel (cancel); il.EmitWriteLine (string.Format ("Handler method '{0}' for '{1}' not found in new dataSource ", bindingDef.TargetMember, sourceEvent.Name)); il.MarkLabel (finish); il.Emit (OpCodes.Ret); //store dschange delegate in instatiator instance for access while instancing graphic object int delDSIndex = cachedDelegates.Count; cachedDelegates.Add (dm.CreateDelegate (CompilerServices.ehTypeDSChange, this)); if (bindingDef.IsDataSourceBinding) ctx.emitCachedDelegateHandlerAddition (delDSIndex, CompilerServices.eiDSChange); else //template handler binding, will be added to root parentChanged templateCachedDelegateIndices.Add (delDSIndex); } else {//normal in tree handler binding, store until tree is complete (end of parse) ctx.UnresolvedTargets.Add (new EventBinding ( bindingDef.SourceNA, sourceEvent, bindingDef.TargetNA, bindingDef.TargetMember, bindingDef.TargetName)); } } #endregion #region Property Bindings /// /// Create and store in the instanciator the ValueChanged delegates /// those delegates uses grtree functions to set destination value so they don't /// need to be bound to destination instance as in the ancient system. /// void emitBindingDelegates(Context ctx){ foreach (KeyValuePair>> bindings in ctx.Bindings ) { if (bindings.Key.Count == 0)//template binding emitTemplateBindings (ctx, bindings.Value); else emitPropertyBindings (ctx, bindings.Key, bindings.Value); } } void emitPropertyBindings(Context ctx, NodeAddress origine, Dictionary> bindings){ Type origineNodeType = origine.NodeType; //value changed dyn method DynamicMethod dm = new DynamicMethod ("dyn_valueChanged" + NewId, typeof (void), CompilerServices.argsValueChange, true); ILGenerator il = dm.GetILGenerator (256); System.Reflection.Emit.Label endMethod = il.DefineLabel (); il.DeclareLocal (CompilerServices.TObject); il.Emit (OpCodes.Nop); int i = 0; foreach (KeyValuePair> bindingCase in bindings ) { System.Reflection.Emit.Label nextTest = il.DefineLabel (); #region member name test //load source member name il.Emit (OpCodes.Ldarg_1); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCMbName); il.Emit (OpCodes.Ldstr, bindingCase.Key);//load name to test il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal il.Emit (OpCodes.Callvirt, CompilerServices.stringEquals); il.Emit (OpCodes.Brfalse, nextTest);//if not equal, jump to next case #endregion #region destination member affectations PropertyInfo piOrig = origineNodeType.GetProperty (bindingCase.Key); Type origineType = null; if (piOrig != null) origineType = piOrig.PropertyType; foreach (MemberAddress ma in bindingCase.Value) { //first we have to load destination instance onto the stack, it is access //with graphic tree functions deducted from nodes topology il.Emit (OpCodes.Ldarg_0);//load source instance of ValueChanged event NodeAddress destination = ma.Address; if (destination.Count == 0){//template reverse binding //fetch destination instance (which is the template root) for (int j = 0; j < origine.Count ; j++) il.Emit(OpCodes.Callvirt, CompilerServices.miGetLogicalParent); }else CompilerServices.emitGetInstance (il, origine, destination); if (origineType != null && destination.Count > 0){//else, prop less binding or reverse template bind, no init requiered //for initialisation dynmeth, push destination instance loc_0 is root node in ctx ctx.il.Emit(OpCodes.Ldloc_0); CompilerServices.emitGetInstance (ctx.il, destination); //init dynmeth: load actual value from origine ctx.il.Emit (OpCodes.Ldloc_0); CompilerServices.emitGetInstance (ctx.il, origine); ctx.il.Emit (OpCodes.Callvirt, origineNodeType.GetProperty (bindingCase.Key).GetGetMethod()); } //load new value il.Emit (OpCodes.Ldarg_1); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCNewValue); if (origineType == null)//property less binding, no init CompilerServices.emitConvert (il, ma.Property.PropertyType); else if (destination.Count > 0) { if (origineType.IsValueType) ctx.il.Emit(OpCodes.Box, origineType); CompilerServices.emitConvert (ctx.il, origineType, ma.Property.PropertyType); CompilerServices.emitConvert (il, origineType, ma.Property.PropertyType); ctx.il.Emit (OpCodes.Callvirt, ma.Property.GetSetMethod());//set init value } else {// reverse templateBinding il.Emit (OpCodes.Ldstr, ma.memberName);//arg 3 of setValueWithReflexion il.Emit (OpCodes.Call, CompilerServices.miSetValWithRefx); continue; } il.Emit (OpCodes.Callvirt, ma.Property.GetSetMethod());//set value on value changes } #endregion il.Emit (OpCodes.Br, endMethod); il.MarkLabel (nextTest); i++; } il.MarkLabel (endMethod); il.Emit (OpCodes.Ret); //store and emit Add in ctx int dmIdx = cachedDelegates.Count; cachedDelegates.Add (dm.CreateDelegate (typeof(EventHandler))); ctx.emitCachedDelegateHandlerAddition (dmIdx, CompilerServices.eiValueChange, origine); #if DEBUG_BINDING Debug.WriteLine("\tCrow property binding: " + dm.Name); #endif } void emitTemplateBindings(Context ctx, Dictionary> bindings){ //value changed dyn method DynamicMethod dm = new DynamicMethod ("dyn_tmpValueChanged", typeof (void), CompilerServices.argsValueChange, true); ILGenerator il = dm.GetILGenerator (256); //create parentchanged dyn meth in parallel to have only one loop over bindings DynamicMethod dmPC = new DynamicMethod ("dyn_InitAndLogicalParentChanged", typeof (void), CompilerServices.argsBoundDSChange, true); ILGenerator ilPC = dmPC.GetILGenerator (256); il.Emit (OpCodes.Nop); ilPC.Emit (OpCodes.Nop); System.Reflection.Emit.Label endMethod = il.DefineLabel (); il.DeclareLocal (CompilerServices.TObject); ilPC.DeclareLocal (CompilerServices.TObject);//used for checking propery less bindings ilPC.DeclareLocal (typeof(MemberInfo));//used for checking propery less bindings System.Reflection.Emit.Label cancel = ilPC.DefineLabel (); #region Unregister previous parent event handler //unregister previous parent handler if not null ilPC.Emit (OpCodes.Ldarg_2);//load old parent ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); ilPC.Emit (OpCodes.Brfalse, cancel);//old parent is null ilPC.Emit (OpCodes.Ldarg_2);//load old parent ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); //Load cached delegate ilPC.Emit(OpCodes.Ldarg_0);//load ref to this instanciator onto the stack ilPC.Emit(OpCodes.Ldfld, CompilerServices.fiTemplateBinding); //add template bindings dynValueChanged delegate to new parent event ilPC.Emit(OpCodes.Callvirt, CompilerServices.eiValueChange.RemoveMethod);//call remove event #endregion ilPC.MarkLabel(cancel); #region check if new parent is null cancel = ilPC.DefineLabel (); ilPC.Emit (OpCodes.Ldarg_2);//load datasource change arg ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); ilPC.Emit (OpCodes.Brfalse, cancel);//new ds is null #endregion int i = 0; foreach (KeyValuePair> bindingCase in bindings ) { System.Reflection.Emit.Label nextTest = il.DefineLabel (); #region member name test //load source member name il.Emit (OpCodes.Ldarg_1); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCMbName); il.Emit (OpCodes.Ldstr, bindingCase.Key);//load name to test il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal il.Emit (OpCodes.Callvirt, CompilerServices.stringEquals); il.Emit (OpCodes.Brfalse, nextTest);//if not equal, jump to next case #endregion #region destination member affectations foreach (MemberAddress ma in bindingCase.Value) { if (ma.Address.Count == 0){ Debug.WriteLine("\t\tBUG: reverse template binding in normal template binding"); continue;//template binding } //first we try to get memberInfo of new parent, if it doesn't exist, it's a propery less binding ilPC.Emit (OpCodes.Ldarg_2);//load new parent onto the stack for handler addition ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); ilPC.Emit (OpCodes.Stloc_0);//save new parent //get parent type ilPC.Emit (OpCodes.Ldloc_0);//push parent instance ilPC.Emit (OpCodes.Ldstr, bindingCase.Key);//load member name ilPC.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); ilPC.Emit (OpCodes.Stloc_1);//save memberInfo ilPC.Emit (OpCodes.Ldloc_1);//push mi for test if null System.Reflection.Emit.Label propLessReturn = ilPC.DefineLabel (); ilPC.Emit (OpCodes.Brfalse, propLessReturn); //first we have to load destination instance onto the stack, it is access //with graphic tree functions deducted from nodes topology il.Emit (OpCodes.Ldarg_0);//load source instance of ValueChanged event CompilerServices.emitGetChild (il, typeof(TemplatedControl), -1); CompilerServices.emitGetInstance (il, ma.Address); ilPC.Emit (OpCodes.Ldarg_2);//load destination instance to set actual value of member ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); CompilerServices.emitGetChild (ilPC, typeof(TemplatedControl), -1); CompilerServices.emitGetInstance (ilPC, ma.Address); //load new value il.Emit (OpCodes.Ldarg_1); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCNewValue); //for the parent changed dyn meth we need to fetch actual value for initialisation thrue reflexion ilPC.Emit (OpCodes.Ldloc_0);//push root instance of instanciator as parentChanged source ilPC.Emit (OpCodes.Ldloc_1);//push mi for value fetching ilPC.Emit (OpCodes.Call, CompilerServices.miGetValWithRefx); CompilerServices.emitConvert (il, ma.Property.PropertyType); CompilerServices.emitConvert (ilPC, ma.Property.PropertyType); il.Emit (OpCodes.Callvirt, ma.Property.GetSetMethod()); ilPC.Emit (OpCodes.Callvirt, ma.Property.GetSetMethod()); ilPC.MarkLabel(propLessReturn); } #endregion il.Emit (OpCodes.Br, endMethod); il.MarkLabel (nextTest); i++; } //il.Emit (OpCodes.Pop); il.MarkLabel (endMethod); il.Emit (OpCodes.Ret); //store template bindings in instanciator templateBinding = dm.CreateDelegate (typeof(EventHandler)); #region emit LogicalParentChanged method //load new parent onto the stack for handler addition ilPC.Emit (OpCodes.Ldarg_2); ilPC.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); //Load cached delegate ilPC.Emit(OpCodes.Ldarg_0);//load ref to this instanciator onto the stack ilPC.Emit(OpCodes.Ldfld, CompilerServices.fiTemplateBinding); //add template bindings dynValueChanged delegate to new parent event ilPC.Emit(OpCodes.Callvirt, CompilerServices.eiValueChange.AddMethod);//call add event ilPC.MarkLabel (cancel); ilPC.Emit (OpCodes.Ret); //store dschange delegate in instatiator instance for access while instancing graphic object int delDSIndex = cachedDelegates.Count; cachedDelegates.Add(dmPC.CreateDelegate (CompilerServices.ehTypeDSChange, this)); #endregion ctx.emitCachedDelegateHandlerAddition(delDSIndex, CompilerServices.eiLogicalParentChanged); } /// /// create the valuechanged handler, the datasourcechanged handler and emit event handling /// void emitDataSourceBindings(Context ctx, BindingDefinition bindingDef){ DynamicMethod dm = null; ILGenerator il = null; int dmVC = 0; PropertyInfo piSource = ctx.CurrentNodeType.GetProperty(bindingDef.SourceMember); //if no dataSource member name is provided, valuechange is not handle and datasource change //will be used as origine value string delName = "dyn_DSvalueChanged" + NewId; if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ #region create valuechanged method dm = new DynamicMethod (delName, typeof (void), CompilerServices.argsBoundValueChange, true); il = dm.GetILGenerator (256); System.Reflection.Emit.Label endMethod = il.DefineLabel (); il.DeclareLocal (CompilerServices.TObject); il.Emit (OpCodes.Nop); //load value changed member name onto the stack il.Emit (OpCodes.Ldarg_2); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCMbName); //test if it's the expected one il.Emit (OpCodes.Ldstr, bindingDef.TargetMember); il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal il.Emit (OpCodes.Callvirt, CompilerServices.stringEquals); il.Emit (OpCodes.Brfalse, endMethod); //set destination member with valueChanged new value //load destination ref il.Emit (OpCodes.Ldarg_0); //load new value onto the stack il.Emit (OpCodes.Ldarg_2); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCNewValue); //by default, source value type is deducted from target member type to allow //memberless binding, if targetMember exists, it will be used to determine target //value type for conversion CompilerServices.emitConvert (il, piSource.PropertyType); il.Emit (OpCodes.Callvirt, piSource.GetSetMethod ()); il.MarkLabel (endMethod); il.Emit (OpCodes.Ret); //vc dyn meth is stored in a cached list, it will be bound to datasource only //when datasource of source graphic object changed dmVC = dsValueChangedDynMeths.Count; dsValueChangedDynMeths.Add (dm); #endregion } #region emit dataSourceChanged event handler //now we create the datasource changed method that will init the destination member with //the actual value of the origin member of the datasource and then will bind the value changed //dyn methode. //dm is bound to the instanciator instance to have access to cached dyn meth and delegates dm = new DynamicMethod ("dyn_dschanged" + NewId, typeof (void), CompilerServices.argsBoundDSChange, true); il = dm.GetILGenerator (256); il.DeclareLocal (CompilerServices.TObject);//used for checking propery less bindings il.DeclareLocal (typeof(MemberInfo));//used for checking propery less bindings System.Reflection.Emit.Label cancel = il.DefineLabel (); System.Reflection.Emit.Label newDSIsNull = il.DefineLabel (); System.Reflection.Emit.Label cancelInit = il.DefineLabel (); il.Emit (OpCodes.Nop); emitRemoveOldDataSourceHandler(il, "ValueChanged", delName, true); if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ if (bindingDef.TwoWay){ // System.Reflection.Emit.Label cancelRemove = il.DefineLabel (); // //remove handler if not null // il.Emit (OpCodes.Ldarg_2);//load old parent // il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); // il.Emit (OpCodes.Brfalse, cancelRemove);//old parent is null //remove handler emitRemoveOldDataSourceHandler(il, "ValueChanged", delName, false); // il.Emit (OpCodes.Ldarg_1);//3d arg: instance bound to delegate (the source) // il.Emit (OpCodes.Ldstr, "ValueChanged");//2nd arg event name // il.Emit (OpCodes.Ldarg_2);//1st arg load old datasource // il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCOldDS); // il.Emit (OpCodes.Call, CompilerServices.miRemEvtHdlByTarget); // il.MarkLabel(cancelRemove); } il.Emit (OpCodes.Ldarg_2);//load datasource change arg il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Brfalse, newDSIsNull);//new ds is null } #region fetch initial Value if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//load member name il.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); il.Emit (OpCodes.Stloc_1);//save memberInfo il.Emit (OpCodes.Ldloc_1);//push mi for test if null il.Emit (OpCodes.Brfalse, cancelInit);//propertyLessBinding } il.Emit (OpCodes.Ldarg_1);//load source of dataSourceChanged which is the dest instance il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ il.Emit (OpCodes.Ldloc_1);//push mi for value fetching il.Emit (OpCodes.Call, CompilerServices.miGetValWithRefx); } CompilerServices.emitConvert (il, piSource.PropertyType); il.Emit (OpCodes.Callvirt, piSource.GetSetMethod ()); #endregion if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ il.MarkLabel(cancelInit); //check if new dataSource implement IValueChange il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Isinst, typeof(IValueChange)); il.Emit (OpCodes.Brfalse, cancel); il.Emit(OpCodes.Ldarg_0);//load ref to this instanciator onto the stack il.Emit (OpCodes.Ldarg_1);//load datasource change source il.Emit (OpCodes.Ldarg_2);//load new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit(OpCodes.Ldc_I4, dmVC);//load index of dynmathod il.Emit (OpCodes.Call, CompilerServices.miDSChangeEmitHelper); il.MarkLabel (cancel); if (bindingDef.TwoWay){ il.Emit (OpCodes.Ldarg_1);//arg1: dataSourceChange source, the origine of the binding il.Emit (OpCodes.Ldstr, bindingDef.SourceMember);//arg2: orig member il.Emit (OpCodes.Ldarg_2);//arg3: new datasource il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//arg4: dest member il.Emit (OpCodes.Call, CompilerServices.miDSReverseBinding); } } il.MarkLabel (newDSIsNull); il.Emit (OpCodes.Ret); //store dschange delegate in instatiator instance for access while instancing graphic object int delDSIndex = cachedDelegates.Count; cachedDelegates.Add(dm.CreateDelegate (CompilerServices.ehTypeDSChange, this)); #endregion ctx.emitCachedDelegateHandlerAddition(delDSIndex, CompilerServices.eiDSChange); #if DEBUG_BINDING Debug.WriteLine("\tDataSource ValueChanged: " + delName); Debug.WriteLine("\tDataSource Changed: " + dm.Name); #endif } /// /// Two way binding for datasource, graphicObj=>dataSource link, datasource value has priority /// and will be set as init for source property (in emitDataSourceBindings func) /// /// Graphic object instance, source of binding /// Origin member name /// datasource instance, target of the binding /// Destination member name static void dataSourceReverseBinding(IValueChange orig, string origMember, object dest, string destMember){ Type tOrig = orig.GetType (); Type tDest = dest.GetType (); PropertyInfo piOrig = tOrig.GetProperty (origMember); PropertyInfo piDest = tDest.GetProperty (destMember); if (piDest == null) { Debug.WriteLine ("Member '{0}' not found in new DataSource '{1}' of '{2}'", destMember, dest, orig); return; } #if DEBUG_BINDING Debug.WriteLine ("DS Reverse binding: Member '{0}' found in new DS '{1}' of '{2}'", destMember, dest, orig); #endif #region ValueChanged emit DynamicMethod dm = new DynamicMethod ("dyn_valueChanged" + NewId, typeof (void), CompilerServices.argsBoundValueChange, true); ILGenerator il = dm.GetILGenerator (256); System.Reflection.Emit.Label endMethod = il.DefineLabel (); il.DeclareLocal (CompilerServices.TObject); il.Emit (OpCodes.Nop); //load value changed member name onto the stack il.Emit (OpCodes.Ldarg_2); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCMbName); //test if it's the expected one il.Emit (OpCodes.Ldstr, origMember); il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal il.Emit (OpCodes.Callvirt, CompilerServices.stringEquals); il.Emit (OpCodes.Brfalse, endMethod); //set destination member with valueChanged new value //load destination ref il.Emit (OpCodes.Ldarg_0); //load new value onto the stack il.Emit (OpCodes.Ldarg_2); il.Emit (OpCodes.Ldfld, CompilerServices.fiVCNewValue); CompilerServices.emitConvert (il, piOrig.PropertyType, piDest.PropertyType); il.Emit (OpCodes.Callvirt, piDest.GetSetMethod ()); il.MarkLabel (endMethod); il.Emit (OpCodes.Ret); #endregion orig.ValueChanged += (EventHandler)dm.CreateDelegate (typeof(EventHandler), dest); } #endregion /// /// search for graphic object type in crow assembly, if not found, /// search for type independently of namespace in entry assembly /// /// the corresponding type object /// graphic object type name without its namespace Type tryGetGOType (string typeName){ Type t = Type.GetType ("Crow." + typeName); if (t != null) return t; Assembly a = Assembly.GetEntryAssembly (); foreach (Type expT in a.GetExportedTypes ()) { if (expT.Name == typeName) return expT; } return null; } } }