1

I have separated two projects in my solution because they each require libraries targeting different CPU. In one of my project, I just have classes that respond to clicks (let's call it ProjectClick 64 bits libraries), the other one is a sort of UI with an MVVM implementation (ProjectUser 32 bits libraries).

The thing I am searching for is a way to let the ProjectUser know that the click has been performed by the ProjectClick, without the Project Click knowing anything else.

What I have tried so far

I have been scattering the web and books to understand a bit more about C#. From what I understood, to communicate, the best way is to create a Interface. I have been looking at this subject for an answer, and have been trying to implement a third project with an interface between the two.

Ok, here goes the code, (this is a purposely simplified code, I hope it is clear enough)

First the Interface (in a console application)

namespace LinkApplication
{
    public interface IEvent
    {

        bool CompareClick { get; set; }
    }

 }

Then, the project clicking which is a wpf

namespace ProjectClick

public partial class MainWindow : Window, IEvent

{

    public MainWindow()
    {

        try { InitializeComponent(); }
        catch (Exception e)
        {
            Console.WriteLine(e.InnerException);
        }
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
         CompareClick = true;
    }
    private void Button_Leave(object sender, RoutedEventArgs e)
    {
         CompareClick = false;
    }


}

Finally the UI

namespace ProjectUser
{


public partial class MainWindow : Window, IEvent, INotifyPropertyChanged
{

    public MainWindow()
    {
        InitializeComponent();

        this.WindowStartupLocation = WindowStartupLocation.CenterScreen;    //start the window at the centre of the screen
        DataContext = this;

    }

    public bool CompareClick { get; set; }

    public bool ClickCheck
    {
        get { return CompareClick; }
        set
        {
            if (value != CompareClick)
            {
                CompareClick = value;
                OnPropertyChanged("ClickCheck");
            }
        }
    }

You can see the realted Label here in the Window

<Label Content="{Binding ClickCheck}" HorizontalAlignment="Left" Margin="690,358,0,0" VerticalAlignment="Top"/>

Here, the value always stays at false, and I don't really understand the logic of the changing value. I am still learning, and I have seen several other ideas on the web like a custom EventHandler, but I don't really understand the implementation between two projects not knowing each others. I will be glad if someone could route me towards a possible solution, or a better way to perform.

Edit I would preferably like to avoid referring the Project Click in the ProjectUser to keep the privileges of different CPU targeting. The other way around is not a problem.

Thank you for your kind answers.

Community
  • 1
  • 1
LePingu
  • 79
  • 1
  • 13

3 Answers3

3

I have been greatly advised and have looked into Inter Process Communication between instances. I have looked into different things but the most satisfying answer of all was on Omegaman's blog (bit thanks to this subject).

So basically, I have tried to avoid localhost information, thinking there would be a more straightforward solution. But since we have not thought of anything better, I think this is what I was looking for.

What I have found

So now, the solution here was to use a WCF service with NamedPipes. By creating a Sender and Receiver actions, the two process ProjectUser and ProjectClick never encounter each other directly. You have instead a pipe controlled by the WCF. You can see more details on the blog on how to communicate, I just adapted (without great change) what he did by changing the passing information.

One thing to note however

The processes cannot both start at the same time, and the receiver must start first to listen to the information coming through. Basically, the sender has to start afterwards.

I created two windows in WPF, and a WCFServiceLibrary. When the button is clicked, there is an incrementation, and it shows the number on the second screen.

enter image description here

A little bit of code

You can see a lot on Omegaman's blog, and I will just post what I have changed.

On the ProjectUser side, supposed to receive, the label is updated as follows Receiver pipe = new Receiver(); public MainWindow() { InitializeComponent();

        //this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
        DataContext = this;
        pipe.Data += new PipeLink.PipeService.DataIsReady(DataBeingRecieved);
        if (pipe.ServiceOn() == false)
            MessageBox.Show(pipe.error.Message);

        label1.Content = "Listening to Pipe: " + pipe.CurrentPipeName + Environment.NewLine;
    }

    void DataBeingRecieved(int data)
    {
        Dispatcher.Invoke(new Action(delegate()
        {
            label1.Content += string.Join(Environment.NewLine, data);
            label1.Content += Environment.NewLine;
        }));
    }

On the ProjectClick side, supposed to send, the button click updates as follows

 int i;
    public MainWindow()
    {
         try { InitializeComponent(); }
        catch (Exception e)
        {
            Console.WriteLine(e.InnerException);
        }
         i = 0;

    }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        int messages;

        i++;

        Stopwatch stoop = new Stopwatch();
        stoop.Start();
        messages = i;
        try
        {
            PipeLink.Sender.SendMessage(messages);
            stoop.Stop();
            Console.WriteLine(stoop.ElapsedMilliseconds + " ms");

        }
        catch (Exception u)
        {
            Console.WriteLine(u);
        }
    }

The important part of the code, is the creation of the pipe itself, using NetNamedPipeBinding. This is where the whole communication will take place

You can see it in the PipeService code :

public class PipeService : IPipeService
{

    public static string URI
       = "net.pipe://localhost/Pipe";

    // This is when we used the HTTP bindings.
    // = "http://localhost:8000/Pipe";

    #region IPipeService Members

    public void PipeIn(int data)
    {
        if (DataReady != null)
            DataReady(data);
    }

    public delegate void DataIsReady(int hotData);
    public DataIsReady DataReady = null;

    #endregion
}

What about the speed?

I was afraid simple data may take longer to arrive than on a simple click. I was mistaken : the first number took longer than the others because of the first connection, so about a second. But after that, for clicking about a 100 times, I had a, average of 10 ms (I know it is not significant data, still I thought it was good to test it a couple of times).

I am pushing everything on the GitHub used with Andreas, for anyone who might be interested.

I still do not know if the code is optimized though. Should you have a better solution, I will happily read it.

LePingu
  • 79
  • 1
  • 13
  • yeah. definitely interesting. you should post key parts of your solution here as well :) – Dbl Sep 04 '15 at 15:51
1

As others pointed out your concept of interfaces is wrong still. However i get what you're trying to do.

Try this:

namespace LinkApplication
{
    public interface IEventReceiver
    {
        void Receive<T>(T arg) where T : EventArgs;
    }

    public class SomeUniqueEvent : EventArgs
    {
        public bool Clicked { get; set; }

        public SomeUniqueEvent(bool clicked)
        {
            Clicked = clicked;
        }
    }

    public static class EventTunnel
    {
        private static readonly List<IEventReceiver> _receivers = new List<IEventReceiver>();
        public static void Publish<T>(T arg) where T : EventArgs
        {
            foreach (var receiver in _receivers)
            {
                receiver.Receive(arg);
            }
        }

        public static void Subscribe(IEventReceiver subscriber)
        {
            _receivers.Add(subscriber);
        }
    }
}

namespace ProjectClick
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {

            try { InitializeComponent(); }
            catch (Exception e)
            {
                Console.WriteLine(e.InnerException);
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(true));
        }
        private void Button_Leave(object sender, RoutedEventArgs e)
        {
            LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(false));
        }
    }
}

namespace ProjectUser
{

    public partial class MainWindow : Window, LinkApplication.IEventReceiver, INotifyPropertyChanged
    {

        public MainWindow()
        {
            InitializeComponent();

            this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
            DataContext = this;
            LinkApplication.EventTunnel.Subscribe(this);

        }

        public bool CompareClick { get; set; }

        public bool ClickCheck
        {
            get { return CompareClick; }
            set
            {
                if (value != CompareClick)
                {
                    CompareClick = value;
                    OnPropertyChanged("ClickCheck");
                }
            }
        }

        public void Receive<T>(T arg) where T : EventArgs
        {
            var casted = arg as SomeUniqueEvent;
            if (casted != null)
            {
                ClickCheck = casted.Clicked;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
Dbl
  • 5,634
  • 3
  • 41
  • 66
  • Wow I have a million questions for you right now. So I have tried the code and it doesn't quite work like I wanted to. The first value false gets passed, but after that, if I click on the button, it doesn't update nor does it pass through the `EventTunnel`. However I have a feeling it is in the right direction. You have a List, does it mean each time the value gets inside the tunnel, it is added in the List? Will it not block the memory if the program runs all day long? – LePingu Sep 03 '15 at 14:42
  • i have a list of IEventReceiver, not IEvent. And no. Those EventArgs are fired and forgotten. You need to unsubscribe your windows from the tunnel subscriber list when closing the window though. Otherwise your code will leak. Spectate it with debugger attached. The concept is easy – Dbl Sep 03 '15 at 14:51
  • Yes List, my bad (I was still with my past syntax). Please bear with me just some more: I do not receive any EventArgs when I click the button, even though the `Publish` method gets called. The `SomeUniqueEvent` also changes accordingly. Also, the subscriber needs to be called by the listener only, right? – LePingu Sep 03 '15 at 15:25
  • Yes, you could post your entire code to github so i can see what you're trying? While this technique in general does work, it may just be a case where there is no communication possible yet. That would be the case if your shell application does not launch either of your Applications so they don't share the same application domain. In that way you'd have to do create a different form of communication. Impossible to tell without more code though. Are you starting up both assemblies individually? – Dbl Sep 03 '15 at 16:09
  • Certainly, here you [go] (https://github.com/HatimNourbay/ApplicationStack). Thank you for your time. I have had hard time not trying to annoy the Stack community, this is very kind of you. – LePingu Sep 03 '15 at 16:55
  • @HatimNourbay Well, personally i'm pissed off when someone asks super basic questions. But your subject is not as easy as most questions. As long as someone actually tries to figure stuff out i honestly don't mind. The only real annoying crowd at SO is the "give me teh codez plz" people who don't even bother to abstract their problem so it might help someone else with a similar issue. Anyway. i'll look at your project once i get home. 40~mins. – Dbl Sep 03 '15 at 17:31
  • 1
    On a quick glance though i see 2x app.xaml so i assume you will start both applications by themselves. Depending on wether they will run on the same machine or not you could look into these topics: microsoft message queue(or something), inter process communication, or in case it's another machine: tcp/udp server/client – Dbl Sep 03 '15 at 17:33
  • Oh ok I see the thing here we need to pass the data through a third party. I will try that first thing tomorrow on my work computer and let you know of the result. – LePingu Sep 03 '15 at 21:16
  • Judging by the looks of the code i have seen that will indeed be necessary. luckily there's plenty of people trying to do the same thing so it should be easy to find a solution that suits you best. but i'd be curious to know :) – Dbl Sep 04 '15 at 01:12
  • I have looked into your suggestions and a lot of possibilities popped out. I have tried this for AppDomain Process, but it doesn't quite fit my need I think. Anyways, I have looked into IPC like you suggested, but I had concerns about speed to relay information, since the entire solution (with the three projects) will be placed on very heterogeneous machines. What do you think? – LePingu Sep 04 '15 at 08:45
  • making the right decision depends on your use case. if you are planning a static environment for the clients to run you can pick tcp communication. in that case you would have to look up "c# server client tcp" – Dbl Sep 04 '15 at 09:25
  • I have found what I was looking for thanks to your recommendations. You can see the above answer. Thank you again. – LePingu Sep 04 '15 at 15:44
  • well if you want to thank me you can +1 i guess if it was helpful. but your scenario was different so the solution is another one ofc :) – Dbl Sep 04 '15 at 15:47
  • I can't unfortunately, I don't have enough reputation, I wanted to though. – LePingu Sep 04 '15 at 15:53
0

Here, you misunderstand what an interface is. Every implementation of an interface is a different one. When you click the button, CompareClick property of ProjectClick project's MainWindow changes value. But that doesn't change the ProjectUser project's MainWindow. They are two completely different objects! The best way that I can think of now, is to make the button public. Alternatively, you can create a method in the MainWindow class of the ProjectClick. Use this method to subscribe to the click event. Something like this:

public void SubscribeToClickEvent (EventHandler handler) {
    this.Button.Click += handler //whatever your button is called
}

If you want to encapsulate Button, use the method above. If you don't, then just make it public.

And you ask, how can I access an instance of MainWindow to use the method? The only way I can think of is to make MainWindow a singleton.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Oh ok. I am sorry, but I am still learning as I mentionned so I might have some very low level questions. Was the the purpose of th interfacegood (but badly used) in my example? (I mean communication-wise). For the encapsulation, do I need to replace the "handler" by the name of the method? I am sorry if I seem unsure, I just want to understand perfectly. Thank you again – LePingu Sep 03 '15 at 12:10
  • Interfaces are not suitable in this case. I guess you heard people say that interfaces are suitable for communication _between methods_. You can call a method that accepts an interface type as an argument. That method know nothing about the caller. It just knows that the caller has the methods that are specified by the interface. So interfaces are not suitable for communication between classes. Maybe delegates are better. That is why I told you to use events as they are a special kind of delegates. – Sweeper Sep 03 '15 at 12:10
  • You need to pass in a method name (without the brackets) that is compatible with the delegate EventHandler when you call the method but not in the declaration – Sweeper Sep 03 '15 at 12:12
  • Ok, I understand interface are no good. For what you showed me, I should have to instantiate `MainWindow` of `ProjectClick`, therefore, making a reference of the `ProjectClick` inside the `ProjectUser`? Because the main problem is that I want to avoid any reference of the 64 bits project in the 32 bits one. – LePingu Sep 03 '15 at 12:44
  • If you don't reference it, how can you subscribe to the click event? I recommand to use a Singleton. You can search for it on the internet. That way, you can use `MainWindow.Instance` to get the instance – Sweeper Sep 03 '15 at 13:05
  • That was actually the difficult part of my question. I dont want any reference of the `ProjectClick` in the other one. Otherwise, I would lose the privileges of separating the projects. Please forgive me if I was unclear I will directly change some things in the question. I will try the singleton pattern you recommended me. Thank you – LePingu Sep 03 '15 at 13:16
  • But I unfortunatly, in my knowledge, you an't do that. You can copy the file to `ProjectUser` though, but that is just _bad_. – Sweeper Sep 03 '15 at 13:17
  • I have found something by using WCF, you can check the answer I have posted if you are interested :) – LePingu Sep 04 '15 at 15:45