27 Aug 2010

onebit_26

Note: This is part of a series, you can find the rest of the parts in the series index.

Pull for me is as much about learning as it is about writing a useful tool that others will enjoy and often I head down a path to learn it was wrong. Sometimes I realise before a commit and no one ever knows, other times it is committed and reading the source history is like an example of how bad things can get and sometimes I even ship bad ideas. IMPF is one such area where I shipped a huge mistake which caused Pull to easily consume an entire core of CPU for doing zero work.

IMPF would check for messages as using the following process:

image 

The Thread.Sleep(0) is there to ensure application messages get processed, but it is zero so that as soon as a message arrives it is processed. This meant that the check, which did a lot of work, was running almost constantly. This means that Pull ended up eating 100% of a the power of a core Sad smile

The Solution

The solution to this was to change the process from constantly checking to getting notified when there is a new message.

image

This is also much simpler to draw than the other way, maybe that should be a design check, the harder to draw the less chance it works Winking smile

The only issue is how do I cause that trigger to fire from another application when it writes a message IMPF should read?

Windows Messaging

Windows internally has a full message system which you can use to send messages to various components in Windows, for example to turn the screen saver on or off, or to send messages to applications. I have used this previously in Pull to tell Windows to add the shield icon if needed (see Part IX) to the protocol handler buttons.

I can also use it to ping an application with a custom message which that application can act on. For Pull when I get that ping I know there is a new IMPF message.

The first part of this is finding the window handle of the primary instance that I want to ping. This I do by consulting the processes running on the machine and using a dash of LINQ filter it to the primary instance.

private static IntPtr GetWindowHandleForPreviousInstances()
{
    Process currentProcess = Process.GetCurrentProcess();
    string processName = currentProcess.ProcessName;

    List<Process> processes = new List<Process>(Process.GetProcessesByName(processName));
    IEnumerable<Process> matchedProcesses = from p in processes
                                            where (p.Id != currentProcess.Id) &&
                                            (p.ProcessName == processName)
                                            select p;

    if (matchedProcesses.Any())
    {
        return matchedProcesses.First().MainWindowHandle;
    }

    return IntPtr.Zero;
}

Now I know who to ping, I just need to send a ping. This is done by calling the Win32 API SendNotifyMessage:

public static int NotifyMessageID = 93956;

private static class NativeMethods
{
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return:MarshalAs(UnmanagedType.Bool)]
    public static extern bool SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}

public static void PingPreviousInstance()
{
    IntPtr otherInstance = GetWindowHandleForPreviousInstances();
    if (otherInstance != IntPtr.Zero)
    {
        NativeMethods.SendNotifyMessage(otherInstance, NotifyMessageID, IntPtr.Zero, IntPtr.Zero);
    }
}

That takes care of sending, but how do I receive the ping? I need to do is override the WndProc method on my main form to check for the message and if I get the right ID (see line 1 about – the NotifyMessageID) I then act on it. In my case I use the bus to tell IMPF that there is a new message.

protected override void WndProc(ref Message message)
{
    if (message.Msg == WinMessaging.NotifyMessageID)
    {
        this.bus.Broadcast(DataAction.CheckIPMF);
    }

    base.WndProc(ref message);
}
This has enabled IMPF to only act when needed, removed a thread (since it no longer needs it’s own thread), simplified the IMPF code and made Pull a better citizen on your machine. Smile

Add new comment