/// François-Régis Colin /// http://www.tls.cena.fr/products/ivy/ /// * /// (C) CENA /// * /// namespace IvyBus { using System; using System.IO; using System.Collections; using System.Collections.Specialized; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; using System.Configuration; using System.Globalization; using System.Text.RegularExpressions; using System.Text; using System.Reflection; using System.ComponentModel; using System.Diagnostics; #if (!PocketPC) using System.ComponentModel.Design; #endif // using System.Drawing.Design; /// The Main bus Class /// /// #if (!PocketPC) // Show this property in the property grid. [ToolboxItemFilter("System.Windows.Forms.Form", ToolboxItemFilterType.Allow)] [Description("IVY Main API")] #endif [DesignerCategory("Component")] public class Ivy : System.ComponentModel.Component, ISupportInitialize { /* Event */ /// fires when a new client connect to the bus public event EventHandler ClientConnected; /// fires when a client discconnect from the bus public event EventHandler ClientDisconnected; /// fires when a client receive a direct message from another client public event EventHandler DirectMessageReceived; /// fires when somebody ask for killing every client on the bus public event EventHandler DieReceived; /// fires when a client receive a add binding from another client public event EventHandler BindingAdd; /// fires when a client receive a remove binding from another client public event EventHandler BindingRemove; /// fires when a client receive a binding from another client and it as been filtered public event EventHandler BindingFilter; /// fires when a client receive a remove binding from another client public event EventHandler ErrorMessage; #if (!PocketPC) [Bindable(true)] [Category("Ivy")] #endif [DefaultValue(false)] public static bool DebugProtocol { get { return debugProtocol; } set { debugProtocol = value; } } #if (!PocketPC) [Category("Ivy")] #endif public CultureInfo Culture { get { return culture; } set { culture = value; } } /// IvyClients accesses the list of the connected clients #if (!PocketPC) [Browsable(false)] #endif public List IvyClients { get { return clients; } } /// AppName the application name #if (!PocketPC) [Category("Ivy")] [Bindable(true)] #endif [DefaultValue(null)] public string AppName { set { appName = value; } get { return appName; } } /// AppId the Application Unique ID #if (!PocketPC) [Browsable(false)] #endif public string AppId { get { return applicationUniqueId; } } /// AppPriority the Application Priority: the clients list is sorted against priority #if (!PocketPC) [Category("Ivy")] #endif [DefaultValue(DEFAULT_PRIORITY)] public ushort AppPriority { set { applicationPriority = value; lock (clients) { foreach (IvyClient client in clients) { client.stream.TokenApplicationId(applicationPriority, AppId); } } } get { return applicationPriority; } } #if (!PocketPC) [Browsable(false)] #endif public int ProtocolVersion { get { return protocolVersion; } } /// IsRunning is the bus Started and connected ? #if (!PocketPC) [Browsable(false)] #endif public bool IsRunning { get { return !stopped; } } ///SentMessageClasses the first word token of sent messages /// optimise the parsing process when sending messages /// #if (!PocketPC) [Category("Ivy")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] // sinon bug System.String constructor not found ! [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")] #endif public List SentMessageFilter { get { return sent_messageFilter; } } /// ReadyMessage message send when Application receive all the regexp at the connection of the client #if (!PocketPC) [Bindable(true)] [Category("Ivy")] #endif [DefaultValue(null)] public string ReadyMessage { get { return ready_message; } set { ready_message = value; } } #if (PocketPC) #if (!PocketPC) [Category("Ivy")] #endif [DefaultValue(null)] public System.Windows.Forms.ContainerControl ContainerControl { get { return parentControl; } set { parentControl = value; } } #endif #if (!PocketPC) [Category("Ivy")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] #endif public List Bindings { get { return app_bindings; } } internal class MyTcpListener : TcpListener { public MyTcpListener(IPAddress address, int port) : base(address, port) { } public bool IsActive() { return this.Active; } } /// the name of the application on the bus internal string appName; /// the port for the UDP rendez vous, if none is supplied internal const ushort DEFAULT_PORT = 2010; // client default priority internal const ushort DEFAULT_PRIORITY = 100; /// the domain for the UDP rendez vous private static readonly string DEFAULT_DOMAIN = "127.255.255.255:" + DEFAULT_PORT; internal int protocolVersion = 3; private static bool debugProtocol; // false by default runtime private static ushort serial; /* an unique ID for each regexp */ // 0 by default runtime private MyTcpListener app; private List watchers; private volatile Thread serverThread; // to ensure quick communication of the end internal Dictionary bindings; //TODO should be remove samve as above private List app_bindings; private List clients; private List sent_messageFilter; private bool stopped = true; internal ushort applicationPort; /* Application port number */ internal IPAddress applicationHost; /* Application host number */ internal string applicationUniqueId; /* identifier Application unique timestamp-ipaddress-port */ internal ushort applicationPriority = DEFAULT_PRIORITY; private string ready_message; private CultureInfo culture = new CultureInfo("en-us"); // for synchronous event #if (PocketPC) private System.Windows.Forms.ContainerControl parentControl; #else private readonly SynchronizationContext syncContext; #endif /// /// Initializes a new instance of the class. /// public Ivy() { #if (!PocketPC) syncContext = SynchronizationContext.Current; #endif clients = new List(); bindings = new Dictionary(); app_bindings = new List(); sent_messageFilter = new List(); #if (!PocketPC) debugProtocol = Properties.Settings.Default.IvyDebug; protocolVersion = Properties.Settings.Default.IvyProtocolVersion; #endif // get binding from Attribute IvyBinding //TODO Autobinding attribute #if (PocketPC) if (parentControl != null) AutoBinding(parentControl); #endif Assembly assembly = Assembly.GetCallingAssembly(); if ( assembly != this.GetType().Assembly ) BindAttibute(assembly); } public Ivy(IContainer container) : this() { container.Add(this); // get binding from Attribute IvyBinding //TODO Autobinding attribute Assembly assembly = Assembly.GetCallingAssembly(); if (assembly != this.GetType().Assembly) BindAttibute(assembly); } /// /// Readies the structures for the software bus connexion. /// /// This sample shows how to start working with Ivy. /// /// Ivy bus = new Ivy("Dummy agent","ready"); /// bus.bindMsg("(.*)",myMessageListener); /// bus.start(null); /// /// How to send & receive: /// the Ivy agent A performs b.bindMsg("^Hello (*)",cb); /// the Ivy agent B performs b2.sendMsg("Hello world"); /// a thread in A will run the callback cb with its second argument set /// to a array of string, with one single element, "world" /// /// /// the real work begin in the start() method /// /// /// The name of your Ivy agent on the software bus /// /// The hellow message you will send once ready /// public Ivy(string name, string rdy_message) : this() { appName = name; ready_message = rdy_message; // get binding from Attribute IvyBinding //TODO Autobinding attribute Assembly assembly = Assembly.GetCallingAssembly(); if (assembly != this.GetType().Assembly) BindAttibute(assembly); } protected override void Dispose(bool disposing) { try { if (disposing) { Stop(); } } finally { base.Dispose(disposing); } } // Autobinding on static method public void BindAttibute(Type type) { if (type == null) return; //Get instance of the IvyBindingAttribute. foreach (MethodInfo m in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)) { foreach (IvyBindingAttribute attr in Attribute.GetCustomAttributes(m, typeof(IvyBindingAttribute))) { //TODO check paramater type MessageHandler Debug.WriteLine("IvyBinding '" + attr.GetExpression(null) + "' to Method " + m.Name); EventHandler handler; #if (PocketPC) //Createdelegate mydlg = new Createdelegate(m, null, EventArgs.Empty); //bindMsg(attr.GetExpression(null), mydlg); handler = null; #else handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), m); #endif BindMsg(attr.GetExpression(null), handler); } } } // Autobinding on instance method public void BindAttibute(object obj) { if (obj == null) return; Type type = obj.GetType(); //Get instance of the IvyBindingAttribute. foreach (MethodInfo m in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) { foreach (IvyBindingAttribute attr in Attribute.GetCustomAttributes(m, typeof(IvyBindingAttribute))) { //TODO check paramater type MessageHandler Ivy.traceProtocol("Ivy", "BindAttibute " + attr.GetExpression(obj) + "' to Method " + m.Name); EventHandler handler; #if (PocketPC) handler = null; // TODO #else handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), obj, m); #endif BindMsg(attr.GetExpression(obj), handler); } } } // Autobinding on IvyBindingAttribute method public void BindAttibute(Assembly assy) { foreach (Type typ in assy.GetTypes()) { BindAttibute(typ); } } /// /// connects the Ivy bus to a domain or list of domains. /// /// a domain of the form 10.0.0:1234, it is similar to the /// netmask without the trailing .255. This will determine the meeting point /// of the different applications. Right now, this is done with an UDP /// broadcast. Beware of routing problems ! You can also use a comma /// separated list of domains. /// /// One thread (IvyWatcher) for each traffic rendezvous (either UDP broadcast or TCP Multicast). /// One thread (serverThread/Ivy) to accept incoming connexions on server socket. /// a thread for each IvyClient when the connexion has been done. /// public void Start(string domainbus) { domainbus = GetDomain(domainbus); try { long seed = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; Random rand = new Random( (int)seed ); app = new MyTcpListener(IPAddress.Any, 0); app.Start(); // should be started before getting port applicationHost = GetLocalIP(); applicationPort = (ushort)((IPEndPoint)app.LocalEndpoint).Port; applicationUniqueId = string.Format(culture,"{0}:{1}:{2}", rand.Next(), seed, //applicationHost.ToString().Replace(".", ""), applicationPort); } catch (IOException e) { throw new IvyException("can't open TCP service socket " + e); } Ivy.traceProtocol("BindAttibute", "Ivy protocol: " + ProtocolVersion + " TCP service open on port " + applicationPort); watchers = new List(); Domain[] d = parseDomains(domainbus); // readies the rendezvous : an IvyWatcher (thread) per domain bus for (int index = 0; index < d.Length; index++) { IvyWatcher watcher = new IvyWatcher(this, d[index].Domainaddr, d[index].Port); watchers.Add(watcher); } serverThread = new Thread(new ThreadStart(this.Run)); serverThread.Name = "Ivy Tcp Server Thread"; stopped = false; serverThread.Start(); #if (PocketPC ) traceDebug(" Ivy Threading start in progress..."); Thread.Sleep(100); #else // Wait for readyness while ( serverThread.ThreadState != System.Threading.ThreadState.Running || !app.IsActive()) { Ivy.traceError("BindAttibute", " Ivy Threading start in progress..." ); Thread.Sleep( 100 ); } #endif // sends the broadcasts and listen to incoming connexions for (int i = 0; i < watchers.Count; i++) { watchers[i].start(); } } internal void SortClients() { lock (clients) { //clients.Sort(new IvyClient.IvyClientPriority()); clients.Sort(); } } /* a small private method for debbugging purposes */ public static string Domains(string toparse) { string domainbus = GetDomain(toparse); StringBuilder s = new StringBuilder("broadcasting on "); Ivy.Domain[] d = parseDomains(domainbus); for (int index = 0; index < d.Length; index++) { s.Append(d[index].Domainaddr); s.Append(":"); s.Append(d[index].Port); s.Append(" "); } return s.ToString(); } internal static Domain[] parseDomains(string domainbus) { string[] st = domainbus.Split(','); Domain[] d = new Domain[st.Length]; for (int i = 0; i < st.Length; i++) { d[i] = new Domain(st[i]); } return d; } /// /// disconnects from the Ivy bus /// public void Stop() { if (stopped) return; lock (app) { stopped = true; Ivy.traceProtocol("Ivy", "beginning stopping the bus"); try { // stopping the serverThread if (serverThread != null) { app.Stop(); // Wait for Thread to end. bool term = serverThread.Join(10000); if (!term && (serverThread != null)) serverThread.Abort(); serverThread = null; } // The serverThread might be stopped even before having been created if (app != null) app.Stop(); // stopping the IvyWatchers if (watchers != null) for (int i = 0; i < watchers.Count; i++) { ((IvyWatcher)watchers[i]).stop(); } // stopping the remaining IvyClients // copy the values in temporary variable to eliminate Thread modifying collection if (clients.Count != 0) { IvyClient[] copyClient; copyClient = new IvyClient[clients.Count]; lock (clients) { clients.CopyTo(copyClient, 0); } foreach (IvyClient client in copyClient) { client.close(true); // will notify the reading thread //removeClient(client); already donne in the thread } } } catch (IOException e) { Ivy.traceError("Ivy", "IOexception Stop " + e.Message); } Ivy.traceProtocol("Ivy", "the bus should have stopped so far"); } } /// /// Send a formated message to someone on the bus /// /// /// Performs a pattern matching according to everyone's regexps, and sends /// the results to the relevant ivy agents. /// There is one thread for each client connected, we could also /// create another thread each time we send a message. /// /// A string message format to build the message /// args used in message format /// /// the number of messages actually sent /// public int SendMsg(string format, params object[] args) { string msg = string.Format(culture, format, args); int count = 0; // an alternate implementation would one sender thread per client // instead of one for all the clients. It might be a performance issue lock (clients) { // hash message in V4 protocol only if (ProtocolVersion == 4) IvyBindingSimple.Prepare(msg); foreach (IvyClient client in clients) { count += client.sendMsg(msg); } } return count; } // public ushort BindMsg(IvyApplicationBinding newbind) { newbind.Key = serial++; lock (bindings) bindings.Add(newbind.Key, newbind); // notifies the other clients this new regexp lock (clients) { foreach (IvyClient c in clients) { c.stream.TokenAddBinding(newbind.Binding, newbind.Key, newbind.FormatedExpression); } } return newbind.Key; } /// /// Subscribes to a regular expression. /// /// a regular expression, groups are done with parenthesis /// any objects implementing the Ivy.MessageListener /// The args. /// the id of the regular expression /// /// The callback will be executed with /// the saved parameters of the regexp as arguments when a message will sent /// by another agent. A program doesn't receive its own messages. /// // public ushort BindMsg(string regexp, EventHandler callback, params object[] args) { // creates a new binding (regexp,callback) IvyApplicationBinding newbind; newbind = new IvyApplicationBinding(); newbind.Binding = BindingType.Regexp; newbind.Expression = regexp; newbind.Callback += callback; newbind.Args = args; return BindMsg(newbind); } /// /// unsubscribes a regular expression /// /// the id of the regular expression, returned when it was bound public void UnbindMsg(ushort id) { if (!bindings.ContainsKey(id)) { throw new IvyException("client wants to remove an unexistant regexp " + id); } lock (clients) { foreach (IvyClient c in clients) { c.stream.TokenDelBinding(id); } } lock (bindings) bindings.Remove(id); } /// /// unsubscribes a regular expression /// /// the string for the regular expression /// /// a boolean, true if the regexp existed, false otherwise or /// whenever an exception occured during unbinding /// public bool UnbindMsg(string re) { foreach (IvyApplicationBinding bind in bindings.Values) { if (bind.Expression == re) { try { UnbindMsg(bind.Key); } catch (IvyException) { return false; } return true; } } return false; } /// /// Subscribes to a simple expression ( msg ar1 arg2 arg3 etc). /// /// /// The callback will be executed with /// the saved parameters of the regexp as arguments when a message will sent /// by another agent. A program doesn't receive its own messages. /// /// /// a regular expression, groups are done with parenthesis /// any objects implementing the EventHandler /// /// the id of the regular expression /// public int BindSimpleMsg(string expression, EventHandler callback, params object[] args) { // creates a new binding (regexp,callback) IvyApplicationBinding newbind; newbind = new IvyApplicationBinding(); newbind.Binding = BindingType.Simple; newbind.Expression = expression; newbind.Callback += callback; newbind.Args = args; return BindMsg(newbind); } /// /// Dies the specified target. /// /// The target. /// The reason message. /// public int Die(string target, string message) { List v = GetClientsByName(target); for (int i = 0; i < v.Count; i++) v[i].stream.TokenDie(0, message); return v.Count; } /// /// Pings the specified target. /// /// The target. /// The message. /// public int Ping(string target, string message) { List v = GetClientsByName(target); for (int i = 0; i < v.Count; i++) v[i].stream.TokenPing(message); return v.Count; } #if (PocketPC) internal virtual void FireEvent(EventHandler ev, IvyEventArgs e) { if (ev != null) { if (parentControl != null) { parentControl.Invoke(ev, this, e); } else ev(this, e); } } #else internal virtual void FireEvent(EventHandler ev, IvyEventArgs e) { if (ev != null) { if (syncContext != null) { SendOrPostCallback update = delegate(object state) { IvyEventArgs args = (IvyEventArgs)state; ev(this, args); }; syncContext.Post(update, e); } else ev(this, e); } } #endif internal virtual void OnDirectMessage(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = DirectMessageReceived; FireEvent(temp,e); } internal virtual void OnClientConnected(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = ClientConnected; FireEvent(temp,e); } internal virtual void OnClientDisconnected(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = ClientDisconnected; try { FireEvent(temp,e); } #if (!PocketPC) catch (SynchronizationLockException ex) { // protect terminaison } #endif catch (ObjectDisposedException) { // protect terminaison } } internal virtual void OnClientAddBinding(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = BindingAdd; FireEvent(temp,e); } internal virtual void OnClientRemoveBinding(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = BindingRemove; FireEvent(temp,e); } internal virtual void OnClientFilterBinding(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = BindingFilter; FireEvent(temp,e); } internal virtual void OnError(IvyEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = ErrorMessage; FireEvent(temp, e); } #if (PocketPC) internal virtual void OnDie(IvyDieEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = DieReceived; if (temp != null) { if (parentControl != null) { parentControl.Invoke(temp, this, e); } else temp(this, e); } } #else internal virtual void OnDie(IvyDieEventArgs e) { // Copy to a temporary variable to be thread-safe. EventHandler temp = DieReceived; if (temp != null) { if (syncContext != null) { SendOrPostCallback update = delegate(object state) { IvyDieEventArgs args = (IvyDieEventArgs)state; temp(this, args); }; syncContext.Post(update, e); } else temp(this, e); } } #endif internal void OnMessage(IvyMessageEventArgs e) { IvyApplicationBinding bind = bindings[e.Id]; if (bind == null) { throw new IvyException("(callCallback) Not regexp matching id " + e.Id); } #if(PocketPC) bind.Firevent(parentControl, e); #else bind.Firevent(syncContext, e); #endif } /* * removes a client from the list */ internal void removeClient(IvyClient c) { lock (clients) { clients.Remove(c); } } /// /// gives a list of IvyClient(s) with the name given in parameter /// /// The name of the Ivy agent you're looking for /// public List GetClientsByName(string name) { List v = new List(); foreach (IvyClient ic in clients) { if (ic.ApplicationName.CompareTo(name) == 0) v.Add(ic); } return v; } /////////////////////////////////////////////////////////////////: // // Protected methods // /////////////////////////////////////////////////////////////////: internal void addClient(Socket socket, string appname) { if (stopped) return; IvyClient client = new IvyClient(this, socket, appname); lock (clients) { clients.Add(client); } client.SendBindings(); } public static IPAddress GetLocalIP() { IPAddress returnaddr = null; //TODO remove ALL reverse DNS search !!!! IPHostEntry ip = Dns.GetHostEntry(Dns.GetHostName()); foreach (IPAddress addr in ip.AddressList) { returnaddr = addr; if (IPAddress.IsLoopback(addr)) continue; break; } return returnaddr; } public static string GetDomain(string domainbus) { #if (PocketPC) // TODO integrate in normal version if (domainbus == null || domainbus.Length == 0) { IPAddress addr = GetLocalIP(); //TODO Find Braodcast addr from IP; byte[] bytes = addr.GetAddressBytes(); if (IPAddress.IsLoopback(addr)) { // 127.255.255.255 bytes[1] = 255; bytes[2] = 255; bytes[3] = 255; } else { // else assume class C network // TODO find exact netmask bytes[3] = 255; } IPAddress bcast = new IPAddress(bytes); domainbus = bcast + ":" + Domain.getPort(domainbus); } #else if (domainbus == null || domainbus.Length == 0 ) { domainbus = Environment.GetEnvironmentVariable("IVYBUS"); } if (domainbus == null || domainbus.Length == 0) { domainbus = Properties.Settings.Default.IvyBus; } #endif if (domainbus == null || domainbus.Length == 0) domainbus = DEFAULT_DOMAIN; return domainbus; } /// checks the "validity" of a regular expression. //TODO put in IvyBinding internal bool CheckRegexp(string exp) { bool regexp_ok = true; // Attention Bug // ClockStop ClockStart & ^Clock(Start|Pause) // should Stop to the first parent if ((sent_messageFilter.Count != 0) && exp.StartsWith("^")) { regexp_ok = false; // extract first word from regexp... string token = Regex.Replace(exp, @"^\^(?[a-zA-Z_0-9-]+).*", @"${token}"); foreach (string exp_class in sent_messageFilter) { if (exp_class.StartsWith(token)) return true; } } return regexp_ok; } /* * prevents two clients from connecting to each other at the same time * there might still be a lingering bug here, that we could avoid with the * SchizoToken. */ //TODO bug multiple instance Reco car en 127.0.0.1 internal bool checkConnected(IvyClient clnt) { if (clnt.AppPort == 0) return false; lock (clients) { foreach (IvyClient client in clients) { if (clnt != client && client.sameClient(clnt)) return true; } } return false; } /* * the service socket thread reader main loop */ private void Run() { Ivy.traceProtocol("Ivy", "Ivy service Thread started"); bool running = true; while (running) { try { Socket socket = app.AcceptSocket(); if (stopped) break; // early disconnexion addClient(socket, "Unkown(waiting for name reception)"); // the peer called me } catch (IOException e) { Ivy.traceError("Ivy","Ivy server socket reader caught an exception: " + e.Message); } catch (SocketException e) { Ivy.traceError("Ivy","my server socket has been closed " + e.Message); running = false; } } Ivy.traceProtocol("Ivy", "Ivy service Thread stopped"); } [Conditional("DEBUG")] internal static void traceProtocol(string name, string message) { if ( debugProtocol ) Console.WriteLine( "-->{0}<-- {1}", name, message); } internal static void traceError(string name, string message) { Console.WriteLine("-->{0}<-- {1}", name, message); } internal class Domain { public virtual string Domainaddr { get { return domainaddr; } } public virtual int Port { get { return port; } } private string domainaddr; private int port; public Domain(string net) { this.domainaddr = getDomain(net); this.port = getPort(net); } public Domain(string domainaddr, int port) { this.domainaddr = domainaddr; this.port = port; } public override string ToString() { return domainaddr + ":" + port; } public static string getDomain(string net) { int sep_index = net.LastIndexOf(":"); if (sep_index != -1) { net = net.Substring(0, (sep_index) - (0)); } try { net += ".255.255.255"; Regex exp = new Regex("^(\\d+\\.\\d+\\.\\d+\\.\\d+).*"); net = exp.Replace(net, "$1"); } catch (ArgumentException e) { Ivy.traceError("Ivy","Bad broascat addr " + net + "error " + e.Message); return null; } return net; } public static int getPort(string net) { if (net == null) return Ivy.DEFAULT_PORT; int sep_index = net.LastIndexOf(":"); int port = (sep_index == -1) ? Ivy.DEFAULT_PORT : Int32.Parse(net.Substring(sep_index + 1)); return port; } } public static bool ValidatingDomain(string p) { // domain if of the form ip1[:port][,ip2[:port]] with ip of the form n1.n2.n3.n4 return Regex.IsMatch(p, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+"); } #region ISupportInitialize Members public void BeginInit() { } public void EndInit() { // TODO ugly should be added directly the bindings Dictionary ! foreach (IvyApplicationBinding bind in app_bindings) { BindMsg(bind); } } #endregion } }