C Sends Messages to Windowless Processes

  • 2021-10-11 19:22:59
  • OfStack

Note: This article applies to the winform program for. net2.0 +
1 winform program, I hope it can't be opened more, so when the user starts the second instance, as the second instance, there are probably several ways:

1. Pop up a window to inform the user that "the program has run". After the user clicks on the pop-up window, he exits himself

2. Do nothing and quit yourself silently

3. Let the first instance that has been run show its form and exit itself when it is finished

Obviously, the third approach is more authentic, and the core question to achieve this effect is actually: How to display the window of the specified process?

The first thing that comes to mind is to call API such as ShowWindow and SetForegroundWindow. When used together, the front row of the blocked and minimized window can be displayed, which is also a method introduced by many net articles related to this case. The limitation of this method is that the main window of the target process must exist, to be precise, it must have an effective main window handle, which shows that visiting Process. MainWindowHandle can get a non-IntPtr. Zero value, that is, an effective handle; Or use spy class tools to see that there is at least one window under the process; Or press alt+tab to switch out its window.

What if the process has no window? First of all, let's talk about what circumstances the process will have no window. It's very simple. Just let Form.Visible=false (or Form.Hide (), equivalent). At this time, the form disappears, is neither visible nor has a corresponding taskbar button, and alt+tab can't be cut out. When all Form in the program are Hide, the MainWindowHandle accessing the process will get IntPtr. Zero, which is a windowless process. What kind of program will do this? Too many. Well, all kinds of music players, killing soft and so on are allowed to "close/minimize to the system tray". After you click the cross or minimize, the form will be hidden, leaving only one icon in the tray area. Since the MainWindowHandle of this process does not have a valid handle, the above API is useless, and we have to find another way.

Back to the question "How to display the window of the specified process", if your program is not allowed to close to the tray area and there is always a window (minimization also exists), then you can happily use ShowWindow, SetForegroundWindow and other API instead of continuing. But if your program wants to allow users to hide windows like a player kills software, That has to continue tossing. At this time, the problem becomes "how to make the windowless process display the window". My thinking is this: Since the target process has no window, I can't operate its form purely by external means, but because the program is written by myself, can I do it from inside to outside? For example, send a specific message to it, and after receiving the message, it will get the message and display its own window ~ then sorry, who enjoys prosperity and prosperity, will enter the play. This idea mainly involves two problems, how to send and how to receive, and how to display windows in front row after receiving, small case.

How to send it

SendMessage/PostMessage can't be mentioned naturally, because these two goods are also based on windows. In fact, I doubt whether it is feasible to take the message road for the first time, which involves a principle problem, that is, if message 1 can only be sent to windows, it is doomed to be impassable, and other inter-process communication schemes can only be considered. Fortunately, knowing PostThreadMessage, API, solved my problem. This API is to send a message to the specified thread (MSDN document here), which also shows that in principle, messages can not only be sent to windows, but also to threads, and it is not known whether they can be sent to anything else. Look at the 1 send statement first:


void Main()
{
...
// Send a message to the main thread of the target process 
PostThreadMessage(Process.GetProcessById(pid).Threads[0].Id, 0x80F0, IntPtr.Zero, IntPtr.Zero);
...
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostThreadMessage(int threadId, uint msg, IntPtr wParam, IntPtr lParam); 

The first parameter of API is the ID of the target thread. Note two points: ① This ID is the global thread ID of the system, not the "fake" ID; Thread. ManagedThreadId; A message loop must exist on the target thread. The main thread of winform is often the UI thread, and there is a message loop naturally, so there is no need to consider this problem. The second parameter is the message ID to be sent. Our goal is to send a message agreed by both the sender and the sender, so this message should be special enough not to collide with the system message, so the range should be between 0x8001 and 0xBFFF, which is the message segment left by the system for the application's own use (WM_APP). I don't use the last two parameters. You can also use them if you want to make the message more special, or if you want to carry other information. Method returns true/false for send success/failure, respectively.

In addition, the target process may have multiple threads, and I have no scientific way to judge which one is the main thread that can receive messages. Bold speculation is the first item in Process. Threads collection, which has worked well so far, regardless of it. If you have scientific judgment, please let us know ~ thank you.

How to collect it

Because the message is coming by thread, don't think about receiving it in WndProc of the main window. Besides, when the message comes, it is a problem whether the main window exists or not. To use the application level message filter to receive, the filter is a implementation of System. Windows. Forms. IMessageFilter interface class (MSDN), the interface only need to implement a method: bool PreFilterMessage (ref Message m), the method logic is, if the received message m is you want to deal with and eat, return true, the rest of the message return false release. The entire filter looks like this:


class MsgFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x80F0)
{
DoSomething(); // Show a window or something 
return true;
}
return false;
}
}

In fact, after I received the message, I didn't directly do something related to the display window, but raised an event, and the main form registered the event, and then wrote the code related to the display window in the event handling method. This is a design consideration, which has nothing to do with the main purpose of this article.

After the filter is written, you have to add it to a place to work, and it will start to work whenever you add it, so it is best to add it as early as possible, for example, at the beginning of main. Like this:


void Main()
{
Application.AddMessageFilter(new MsgFilter());
...
}

At this point, the problem of sending and receiving is solved. This is essentially an inter-process communication problem, so in fact, any means of process communication can be applied in this case, and walking messages is only one of them.


Related articles: