Windows send message to process

At times I’ll build a suite of related, but separate applications. Even though each application is a separate executable, I like to be able to integrate the applications so they can work together. Sending messages between your applications is a great way to do just that. I build this kind of thing into my apps often, but really think nothing of it since the code is hidden in a base form class. I was asked by someone how to communicate with other apps so I decided to blog a sample to answer the question (which is where most of my posts come from it seems).

There are two parts to communicating via messages. First, you need to be able to listen, or filter the messages. Second, you need to be able to send the messages. Let’s look at sending them first.

Sending Windows Messages via the SendMessage API

Sending Windows Messages is easy. First of all you’ll need to p/invoke the SendMessage Win32API. Add the DllImport to your code and you’re ready to go.

[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern IntPtr SendMessage(IntPtr hwnd, uint Msg, IntPtr wParam, IntPtr lParam);

With the DllImport added, you’ll need to know a little bit about what the parameters are about. For this example, we’ll just focus on the first two. The first is the handle for the application that you’ll be sending the message to, the second is the message you’re sending. I’ll blog at a later date about how to use the wParam and lParam to send additional data with the message.

For this example we’ll define our own messages to send to our applications. We could define messages that mean a number of different things to our app, but we’ll just define a simple generic one that we’ll send to other instances of our sample app. Choose a unique int and assign it to a const to give it some meaning. Something like this:

private const int RF_TESTMESSAGE = 0xA123;

Now for sending the message. Let’s say in our sample application we want to send this message to all other running instances of our app. What we’ll need to do is get all running instances of our application and send this message to each of them. An easy enough task. Take a look at the code.

//get this running process
Process proc = Process.GetCurrentProcess();
//get all other (possible) running instances
Process[] processes = Process.GetProcessesByName(proc.ProcessName);

if (processes.Length > 1)
{
    //iterate through all running target applications
    foreach (Process p in processes)
    {
        if (p.Id != proc.Id)
        {
            //now send the RF_TESTMESSAGE to the running instance
            SendMessage(p.MainWindowHandle, RF_TESTMESSAGE, IntPtr.Zero, IntPtr.Zero);
        }
    }
}
else
{
    MessageBox.Show("No other running applications found.");
}

That will cover sending the message to all running instances of our app (assuming we have multiple instances of the app running). Now we just need to set up the ability to receive and act on the messages.

Receiving Windows Messages by overriding the WndProc

To listen for the message we need to override the base Form class’ WndProc method (Well, the WndProc is really part of the Control base class, but since the Form class is in it’s downstream we’ll use it there). To use the WndProc we need to override it so we can look for our specific messages. Once we do that the rest is a peice of cake. We just test for our message and do whatever action we need to perform. In this sample, we’ll just indicate that we received the message by adding a line to a ListBox.

protected override void WndProc(ref Message message)
{
    //filter the RF_TESTMESSAGE
    if (message.Msg == RF_TESTMESSAGE)
    {
        //display that we recieved the message, of course we could do
        //something else more important here.
        this.listBox1.Items.Add("Received message RF_TESTMESSAGE");
    }
    //be sure to pass along all messages to the base also
    base.WndProc(ref message);
}

That’s it. Now you can start up multiple instances of the app and send messages from one of them to all the other running instances. You can also download a sample project that includes the code from this post. Start up multiple instances of the app and send messages to the other running instances.

 Download sample project: SendMessageDemo.zip

(Click to view sample)     

 

News

Also see my CRM Developer blog

Connect:  

@ryanfarley@mastodon.social

 

 

 

 

 

Sponsor

Recent Posts

  • Using Google Docs as an Online Database
  • Announcing the Growl for Windows Target for NLog (and another reason to love Growl)
  • Sign a .NET Assembly with a Strong Name Without Recompiling
  • A Developer’s Look at Xobni
  • ClickOnce Application «This operation is only supported on Windows 2000 SP3 or later» Error
  • Browser Performance — What’s Changed with Chrome in the Mix?
  • Scraping, or Programatically Accessing, a Secure Webpage

Sections

  • General
  • C#/Development
  • Personal
  • Web/ASP.NET
  • SQL/Data
  • Product Review
  • Community Server
  • Self Improvement
  • Open Source

Learn effective methods for managing your user-defined messages.

Introduction

There are two kinds of messages which you, as a programmer, can defined. The first kind is the compile-time-constant variety. I have found, after years of experience, These Are Not Your Friend. I’ll nonetheless describe them as many people still prefer them, but as a matter of opinion I avoid them as much as I can. The second kind are messages which are guaranteed to be unique within a system. I much prefer these, and use them exclusively.

  • WM_USER messages (obsolete)
  • WM_APP messages
  • Registered Window Messages
  • Guaranteeing uniqueness of registered messages
  • GUIDGEN
  • Defining messages
  • Passing pointers in messages
  • Sending messages across threads
  • Sending messages across processes
  • Sending Messages to Views (new 13-Oct-99)
  • Hazards of Cross-Process Messages (new 13-Oct-99)
  • WM_COPYDATA

WM_USER: obsolete

Older books on Windows programming tell about how to define user-defined messages using the symbol WM_USER. This technique is obsolete. There were too many problems with WM_USER-based symbols conflicting with messages that Microsoft was using. The new method is to use WM_APP as the base. If you have something that uses WM_USER, the usage is identical to the usage of messages based on WM_APP. Therefore, I will not reproduce the discussion here.

WM_APP: constant messages

If you are comfortable with the idea of compile-time constant messages—and after you read the next section, you may not be—then you can use definitions based on the symbol WM_APP, which Microsoft now specifies as the desirable symbol to use. The correct form of the definition of such a symbol is

#define UWM_MYMESSAGE (WM_APP + n)

where n is some integer, typically a small integer like 1, 2, 3, etc. This defines a value which identifies the message. While strictly speaking the parentheses are not mandatory, good programming practice demands their presence.

I prefer a naming convention that does not conflict with the Microsoft naming convention. For one thing, it makes your code difficult for someone else to read and understand; for another, it makes it hard for you to read and understand. My preference is to use a UWM_ prefix (User Window Message); other people have used WMU_ (Window Message, User), and you can pick any convention you want, but do not use prefixes that conflict with those already in use by Microsoft.

Note that there is absolutely no requirement that every user-defined message be unique. Messages are always interpreted in the context of a particular window. Thus, you can have messages like

#define UWM_PAINT_VIEW_PURPLE (WM_APP + 7)
#define UWM_RESET_VIEW        (WM_APP + 7)

These are perfectly valid providing that the view that accepts the purple request is never sent a reset request, and vice-versa.

To create an entry in the table to dispatch on these messages, you make an entry

ON_MESSAGE(UWM_RESET_VIEW, OnReset)

This requires that you define the handler OnReset. In the message handler part of your .h file, you add the declaration

afx_msg LRESULT OnReset(WPARAM, LPARAM);

When your window class receives the UWM_RESET_VIEW message, it will call the OnReset handler.

Registered Window Messages: non-constant messages

There are several problems with constant messages.

  • You can’t send them between processes reliably. If you accidentally send a message to a process that has never heard of your message, it could crash. If you receive a message that you think you understand, you might crash.
  • You can’t create a DLL that notifies its clients via messages. This is because you might choose (WM_APP+7) as your desired message, and some other DLL writer you never heard of might have also chosen (WM_APP+7) as his or her desired message. The poor programmer who is trying to use both DLLs is in deep trouble, because of the conflict.
  • You can’t even think of sending one of these messages down through your window hierarchy by using SendMessageToDescedants, because some window you never heard of may be using that message. Consider the example in the previous section where a message to paint the view purple and a message to reset the view were the same code. If you sent this message to all descendants of your main frame, some would reset and some would change to purple, which is not a particularly desired outcome.

The way this is solved is by using a Registered Window Message. This is a message which is guaranteed to be unique. Only those windows or processes or DLLs that create it, and those which specifically use it, will actually have the same message number.

How is this done?

There is a range of messages, 0xC000 through 0xEFFF, which is reserved for use by registered window messages. When you call the API function ::RegisterWindowMessage you pass it a string. It looks up the string in an internal table. If it finds the string, it returns the integer which has been assigned. If it does not find the string, it creates a new entry in the table, assigns it a new integer value from the range 0xC000 through 0xEFFF, and returns that integer value. The table in which these strings are kept is global to all processes on the machine, so if two completely different programs register the same string, they both get the same integer. They may now communicate with each other via these messages.

No, you can’t «unregister» a message. You don’t need to.

So a simple form of the user-defined message would be to declare a variable, which I usually just make static in each module that uses it:

static const UINT UWM_RESET_VIEW = 
        ::RegisterWindowMessage(_T("UWM_RESET_VIEW"));

I’ll tell you later why this still isn’t quite adequate, but take it as a working example for the moment.

The way you handle a registered message is just like you handle a constant user-defined message. The macro is slightly different, but the rest of the handling is the same. Add the line to your MESSAGE_MAP:

ON_REGISTERED_MESSAGE(UWM_RESET_VIEW, OnReset)

As with the constant messages, you will need to define the OnReset handler by adding a declaration to the handler section of your class in the .h file:

afx_msg LRESULT OnReset(WPARAM, LPARAM);

The handlers for a registered window message and for a constant user-defined message are absolutely identical. In fact, in the handler, you can’t really tell if the programmer has rewritten the code to use one or the other.

But is it unique?

You may have already discovered a problem that is identical for both constant messages and registered messages. What if you choose a nice, obvious name like «UWM_RESET_VIEW» as the string name of the message, and some other programmer has also chosen another nice, obvious, simple name for his or her DLL, such as «UWM_RESET_VIEW«. Have we really made progress?

No.

But there is a way around it. There is a program which is part of the SDK, called GUIDGEN. What this program does is create a unique 128-bit binary value. Each time you create a new Globally Unique IDentifier, a GUID, you can be sure that it is really, truly, unique. It is not only unique for you, it is unique for everyone, everywhere, all the time. It incorporates the time and date, the MAC address from your network card (and if you don’t have a network card, it uses another method, which has something like one chance in 263 of conflicting with another GUID), and a bunch of other information. Therefore, there is no way, short of explicit collusion between two programmers, that they will use the same GUID.

All my registered window messages use a GUID as part of their name.

When you run GUIDGEN, you get a screen that looks like this:

Select option 4, «Registry format». Click the «Copy» button. A copy of the string as shown in the Result box will be placed in the clipboard. Then go to your editor and type something like

#define UWM_RESET_VIEW_MSG _T("UWM_RESET_VIEW-<paste>")

which creates a name like

_T("UWM_RESET_VIEW-{4E7F6EC0-6ADC-11d3-BC36-006067709674}")

I actually have a macro

#define DECLARE_USER_MESSAGE(name) \
     static const UINT name = ::RegisterWindowMessage(name##_MSG);

which handles most of the hassle for me. While strictly speaking my GUID can suffice for all my messages, I usually just generate a new GUID for each message.

Any time I want to create an instance of a message, for use in a message table or for posting, I just do

DECLARE_USER_MESSAGE(UWM_RESET_VIEW)

and, because of my convention of naming the string the same, I get the variable initialized. Typically these appear in the outer block of each module that uses the symbols. All I have to make sure is that the header file for DECLARE_USER_MESSAGE and the header file for the messages I want are both included.

So why do I even bother to put a readable name into the string? The 128-bit GUID should be sufficient! Well, the truth is that the readable name is totally redundant and essentially irrelevant. Unless you happen to be using Spy++ to trace message traffic. In that case, you really, really want to see something you understand, and 128-bit GUIDs are not really high on the list of easily readable values. The only reason the readable string is there is for the convenience of you, the developer. It doesn’t matter, otherwise.

Defining messages

User-defined messages are an interface. As an interface, they need to be defined. I have some macros in my text editor that make this easy. They generate a standard header for me which I fill in. The header looks something like the examples below.


Example 1: A message which has simple parameters, and which is sent but whose response doesn’t matter:

#define UWM_COLOR_IT_MSG _T("UWM_COLOR_IT-{4E7F6EC1-6ADC-11d3-BC36-006067709674}")

Example 2: A message which has no parameters, and which is sent for the purpose of getting a result:

#define UWM_QUERY_CUT_MSG _T("UWM_QUERY_CUT-{4E7F6EC3-6ADC-11d3-BC36-006067709674}")

Example 3: A message which has complex parameters, and which returns an interesting value:

#define UWM_SET_COORD_MSG _T("UWM_SET_COORD-{4E7F6EC2-6ADC-11d3-BC36-006067709674}")

Note that I carefully document the casting that is required to get the desired WPARAM and LPARAM values. Then I know when I’m writing my method how I should cast it. Here’s an example of a handler for another message, which takes a pointer to an object.

LRESULT CMyView::OnAssignMyInfo(WPARAM, LPARAM lParam)
{
    LPMYINFO info = (LPMYINFO)lParam;
    visible = info.visible;
    if(info.IsValidCount)
        count = info.count;
    return 0; 
}

Passing Pointers in Messages

When you pass a pointer as a WPARAM or LPARAM, you need to be careful about what you pass and how you pass it. The key is in whether you do a SendMessage or a PostMessage. If you do a SendMessage, you can use a reference to an object on the stack, an object in static storage, or an object on the heap. This is because control does not resume in the thread that does the SendMessage until the handler completes its action. In particular, this means that any address referencing the stack remains valid during the processing of the message. This does not apply to cross-process messages! See below!

However, if you ever plan to use PostMessage to pass a pointer to an object, then you are constrained to always use a static or heap-based object. The address of a stack-based object is nonsensical in this context. This is because the function that performs the PostMessage will quite possibly return long before the message is processed—in fact, if it is posting the message to the same thread, it must return before the message is processed. This means that the object reference to the stack is pointing to valid space, but space that may have been overwritten. If the object on the stack is a C++ object that has a destructor, the destructor will be called and objects within the object on the stack might be deallocated. For example, you cannot use PostMessage in the following context:

{
    CString s;
    
	PostMessage(UWM_LOG_MESSAGE, 0, (LPARAM)&s);
}

Even if the address referenced on the stack is not overwritten by subsequent calls, the data referenced by the string is deallocated by the CString destructor. When the handler is called and attempts to reference the string, you will get some effect between completely incorrect data and an access fault. The chances of anything working as you expect are incredibly slim.

However, the following code is correct. We first look at the definition:


Then we can write the handler:


(Note that I often replicate the definitions in both places; while it means I have to update the handler comments if I make changes, since I have to edit the code anyway it is no serious hazard).

LRESULT CMainFrame::OnLogMessage(WPARAM, LPARAM lParam)
{
    CString * s = (CString *)lParam;
    c_Log.AddString(*s);
    delete s;
    return 0; 
}

Sending Messages Across Threads

It is often a Bad Idea to send messages across threads. The problems of potential deadlock are substantial. If you SendMessage to a thread that is blocked, and that thread is waiting for the sending thread to complete, you’re dead in the water. Your process is blocked, and it will stay blocked, and you will have to set of sticks of dynamite under it to make it go away.

Note that you can get into this inadvertently. You should never, ever manipulate a GUI object from a worker thread, or a GUI object owned by a thread other than the user-interface thread that is sending the message. If you are getting deadlock, these are key problems to look for.

You are safest, when transferring information across threads, to use PostMessage to handle it. A cross-thread PostMessage will not block the sender. If you need a positive acknowledgement, it is often best to restructure your algorithm to be a two-phase algorithm, where one method posts a message to the alternate thread, and expects the alternate thread to post a message back indicating completion. While harder to program, it avoids the deadlock issue.

If you must send across threads, and you need a positive response, and you have the potential for deadlock, you should use SendMessageTimeout. This will send the message to the thread, but if the thread does not respond within the timeout period, the message is completed, and you get control back, with an error indication. A typical call I use looks like the example below.

DWORD result;
if(!SendMessageTimeout(wnd->m_hWnd,      
                       UWM_QUERY_SOMETHING, 
                       0,                   
                       0,                   
                       SMTO_ABORTIFHUNG |
                       SMTO_NORMAL,
                       TIMEOUT_INTERVAL,
                       &result))
{ 
    
    
	
    if(::GetLastError() == 0)
    { 
        
    } 
    else
    { 
        
    } 
} 
else
{ 
    
    switch(result)
    { 
    case ...: 
        break;
    case ...:
        break;
    } 
} 

Sending Messages Across Processes

Sending a user-defined message across processes is somewhat more complex. First, you really have to use a Registered Window Message. Using a WM_APP-based message is somewhere between seriously dangerous and totally insane.

When you send a message across processes, you are implicitly sending it across threads. All of the caveats about cross-thread messages apply. But even more, there are other serious restrictions on cross-process messages.

The most significant one is that you cannot send a pointer across process boundaries. This is because process address spaces are separate, and a pointer has no meaning when it is received in the other process. For example,

LRESULT CMainFrame::OnLogMessage(WPARAM, LPARAM lParam)
{
    CString * s = (CString *)lParam;
    if(s[0] == _T('$')) 
    { 
        
    } 
}

When the operation s[0] is performed, the chances are almost dead certainty that the application receiving the message will take an access fault. The chances of the pointer being valid (and if it is, it will point to meaningless gibberish) are close to zero, and the gibberish pointed to will certainly not resemble a CString.

You can’t even pass a pointer to shared memory, even DLL-shared-segment shared memory. This is because the shared memory is not guaranteed to be in the same locations in all processes that share the memory (this is described in detail in Win32 Programming). Essentially, figure that you can’t pass information across the process boundary using ordinary messages.

Hazards of Cross-Process Messages

In addition to all of the previous issues, posting messages across processes have additional hazards. For example, you cannot under any circumstances pass an address across process boundaries.

This is true even if the address is in a shared memory area, such as shared DLL data segment or a memory-mapped files. Just because an address is valid in one process does not guarantee it is valid in a different process. And if you have evidence that says this is not so, that you’ve seen that you can pass a shared memory pointer from one process to another and it is valid, be aware that the phenomenon you are seeing is transient and accidental. While Win32 makes a sincere attempt to map shared memory into the same location in each process, it is important to emphasis that it does not guarantee this. Depending on this opens you to serious problems with the delivered product, which might not show up for months. But that’s an essay for some other time.

In addition, you can hit nasty deadlock situations, where a SendMessage does not complete, and hangs forever, or appears to.

Here’s a typical example, and we’ve actually seen this in practice:

My application wants to be the one-and-only-copy running. One of the many detection mechanisms for this is to search for a window of the same class (which is a Bad Idea, since the class names are invented by MFC), the same caption (which is a Bad Idea since the captions may vary depending on which MDI child is active), etc. One of the better ways is to send a Registered Window Message to each window and look for a specific response. It used to be easy; it is now somewhat harder. The naive approach is to do something like shown below. What I show here is the handler for the EnumWindows call:

BOOL CMyApp::myEnumProc(HWND hWnd, LPARAM lParam)
{
    CMyApp * me = (CMyApp *)lParam;
    if(::SendMessage(hWnd, UWM_ARE_YOU_ME))
    { 
        
        me->foundPrevious = TRUE;
       return FALSE; 
    } 
    return TRUE; 
}

If I have a handler in my main window

LRESULT CMainFrame::OnAreYouMe(WPARAM, LPARAM)
{
    return TRUE;
}

then in principle any message sent to any window that doesn’t recognize the Registered Window Message will pass it on to DefWindowProc, which, because it does not recognize the message, will return 0, which means that only my window will respond. This is also a technique for locating a client or server window of an affiliated application.

So if I initially set foundPrevious to FALSE and call EnumWindows(myEnumProc, (LPARAM)this) then when I return I’ll know if I found a previous instance.

There are three major flaws in this code, none of which are evident.

First, while this is a fine technique for recognizing an affiliated window such as a client window or server window (and in that case, the message would probably pass a window handle as WPARAM to tell the affiliated window who sent the message), it won’t work for finding a duplicate instance of the application. That’s the topic of a different essay.

Second, it can deadlock on a failed or non-responsive application, leaving you hanging.

Third, Microsoft has gone out of their way to make life totally miserable by installing, on Windows 98, as part of the FrontPage product, a window which ALWAYS responds with TRUE to any message!!!! While it is hard to believe that anyone would have been stupid enough to do this, they have done it, and we all suffer.

Let’s look at these problems in detail:

The first flaw is very evident when you have a desktop configured to launch an application on a single click. A user who is accustomed to double-clicking to launch an application will double-click, and this will launch TWO copies of the application. The first one does the EnumWindows loop and sees that it is the only instance running. It then proceeds to create its main window and continue on. The second instance comes up, and does the EnumWindows loop as well. Because we are in a preemptive multithreaded environment, it actually manages to complete its EnumWindows loop while the first application is still creating its main window! So the first application’s main window isn’t up yet, and the second application doesn’t find it, and thinks that IT is the one-and-only application. So we have two copies running, which is not what we wanted.

We can’t avoid this. Come up with any scenario (such as not calling the EnumWindows until after the main window is created) and you still get the same race condition.

So we rule this out for finding duplicate copies of the application. But let’s look at the issue of polling all the windows to find a client or server. In this case, I might do

HWND target = ::SendMessage(hWnd, UWM_HERE_I_AM, (WPARAM)m_hWnd);

which is handled by the response:

LRESULT CTheOtherApp::OnHereIAm(WPARAM wParam, LPARAM)
{
    other = new CWnd;
    other.Attach((HWND)wParam);
	return (LRESULT)m_hWnd;
}

which looks like a pretty good exchange of window handles. If you don’t know what Attach does, read my essay on Attach/Detach.

Now, somewhere on the system I’ve got a wedged application. Perhaps it is a copy of IE waiting for an infinitely-long Web page timeout, and is blocked somewhere in the network handler. Perhaps it is some poorly-designed program that is doing a convolution algorithm on a terabit bitmap, and is doing it in the one-and-only main thread, and isn’t allowing any messages to come in (it will be finished in a week or two). Perhaps it is one of your own programs you’re debugging that is lost in a loop. Whatever. The key is that there is very long, possibly infinite delay, in an application you may not have even heard of. And guess what? Your innocent application hangs.

The solution to this is to use ::SendMessageTimeout, so if one of these applications would block you, you will not hang. You should select a small timeout interval, such as 100ms, otherwise, you’ll have a simply very long startup.

Now to the nasty part, in which Microsoft really does us in. There is some application which has a window whose class is «Front Page Message Sink», and which exhibits the pathological and unsociable behavior that it returns a nonzero value for EVERY message it receives. This is colossally stupid. It is undocumented, it violates all known Windows standards, and it will be fatal to you. This only shows up if you have Personal Web Server running. But it is absolutely inexcusable to have done this.

All I know is that I see it return the value 1, consistently, for any kind of Registered Window Message it receives. Perhaps this is the only value it returns. I don’t know, and nobody at Microsoft responded to my bug report with anything explaining what is going on.

Thus testing the result of the ::SendMessage or the DWORD result of ::SendMessageTimeout against 0 is not informative. Thus far, if you see 0 or 1, you can’t depend on the value having any meaning. With any luck, Microsoft will fix this (hah!), and ideally they will not introduce other gratuitous examples of such insanity in the future.

I worked around this by modifying the receiving handler to return a non-zero, non-one value. For example, I actually use ::RegisterWindowMessage to get a value, although I suppose I could have used ::GlobalAddAtom. At least I know that ::RegisterWindowMessage will return a non-zero, non-one value.

#define MY_TOKEN T("Self token-{E32F4800-8180-11d3-BC36-006067709674}")


LRESULT CMainFrame::OnAreYouMe(WPARAM, LPARAM)
{
    return ::RegisterWindowMessage(MY_TOKEN);
}


UINT token = ::RegisterWindowmessage(MY_TOKEN);


DWORD result;
BOOL ok = ::SendMessageTimeout(hWnd,
                               (WPARAM)m_hWnd,   
                               0,                
                               SMTO_ABORTIFHUNG |
                               SMTO_NORMAL,
                               TIMEOUT_INTERVAL,
                               &result));

if(!ok)
    return TRUE; 
if(result == token)
{ 
    
    return FALSE; 
} 

If you are trying to pass a handle back, instead of just a designated value, you would have to make the test

if(result != 0 && result != 1)    
{ 
    target = new CWnd;
    target->Attach(hWnd);
    
    return FALSE; 
} 

Note that if you choose to use ::FindWindow, it does an ::EnumWindows internally, and consequently can be subject to the same permanent hang. Using ::FindWindow is extremely hazardous, since it assumes that in fact every ::SendMessage will complete. Note that ::FindWindow does a ::SendMessage(hWnd, WM_GETTEXT,...) to get the window text to search for.

WM_COPYDATA

There is a possibly useful message, WM_COPYDATA, that will transfer information across the boundary. It does this by passing the data thru the kernel. Space is allocated in the receiving process to hold the information that is copied, by the kernel, from the source process to the target process. Or something that resembles that. The implementation details are actually concealed from you.

The sender passes a pointer to a COPYDATASTRUCT, which is defined as a structure of the following:

typedef struct tagCOPYDATASTRUCT {
    DWORD dwData;
    DWORD cbData;
    PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

The dwData member holds an arbitrary 32-bit value that is being passed to the target. You can set this to any value the two processes agree on. The cbData member tells how many bytes are in the value referenced by lpData. When the target process receives the information, it handles it via a method

BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)

The CWnd * is a reference to the sending window, and the COPYDATASTRUCT * references the COPYDATASTRUCT that was passed in. Note that you don’t know the actual address in the sender. The data which is passed in must not contain pointers.

There are some potential problems with WM_COPYDATA, in that you need to identify who has sent it in order to understand if it is valid, or you must have some other way to identify it. One way to handle it is to use our old friend the GUID. If you put a GUID in the beginning of the data packet, you can compare it to the expected GUID and if they are equal you know for certain that the packet you received is the packet you want.

You must not store the pCopyDataStruct.lpData pointer, because after you return from the OnCopyData handler the pointer should be assumed to be no longer valid. You must also not attempt to write into the data referenced by the lpData pointer; it must be treated as read-only.

Sending Messages to Views

MFC has a wonderful message routing mechanism. A command message first comes to the active view, then the view frame, then the document, and so on. This works for WM_COMMAND and WM_UPDATE_COMMAND_UI messages, but does not work for any other kind of message, including user-defined messages. This is a real pain, and somewhat silly because it would be easy for Microsoft to implement. So a problem arises when you need to send a message to some sub-window, but you can’t send a WM_COMMAND message because you are not directly responding to a GUI component.

Of course, you could invent a fictitious control and generate your own imitation WM_COMMAND messages. But this is very risky, and contributes to significant future unmaintainability.

What can you do?

I’ve done several things. Sometimes, I just put a message handler in the child frame whose sole purpose is to route the message to the child window contained in the frame. This can be a pain, but gives you serious control over how the messages are processed. Thus, the main frame, to which I post a message (often from a thread), has a handler of the form

ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)


LRESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
    CView * active = GetActiveView();
    if(active != NULL)
        active->SendMessage(UWM_THREAD_DID_SOMETHING, wParam, lParam);
    return 0;
}

Sometimes you have to send the message not to the view, but to its parent frame window. This is done by the following code:

RESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
    CFrameWnd * active = GetActiveFrame();
    if(active != this)
        active->SendMessage(UWM_THREAD_DID_SOMETHING, wParam, lParam);
    return 0;
}

Note the special test above. If, for some reason, you had managed to kill off all the MDI children while the thread was still running, GetActiveFrame returns this, which would mean you would get into a semi-infinite SendMessage loop which would terminate when you ran out of stack space. Note that because the function is defined as returning this we don’t have to worry about the possibility that a temporary window handle has been returned, a caution I discuss in my essay on the use of Attach/Detach. This requires that you use the CMDIChildFrame subclass, and introduce a similar handler there:

BEGIN_MESSAGE_MAP(CMyMDIChildFrame, CMDIChildWnd)
    ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)


LRESULT CMyMDIChildFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
    
    
    SendMessageToDescendants(UWM_THREAD_DID_SOMETHING,
	                         wParam,
	                         lParam,
	                         FALSE, 
                             TRUE); 
    return 0;

}


Now, you may wonder why I didn’t send it directly to the child window. Partly because I’m lazy, and SendMessageToDescendants does what I need. Partly because I don’t need the result, because it came in from PostMessage. In a case where I had to route a message to the descendant where I needed the result, I’d instead write

CView * view = (CView *)GetWindow(GW_CHILD);
ASSERT(view != NULL && view->IsKindOf(RUNTIME_CLASS(CView)));
view->SendMessage(...);

Summary

User-defined messages are a powerful and flexible mechanism for handling the passing of information and control between levels of your application, threads of your application, and processes. However, using them effectively requires certain degrees of care. I favor using ::RegisterWindowMessage and GUIDs to define all messages. Interthread and interprocess sends are risky, and should be avoided; if you must, use SendMessageTimeout to ensure that you will not end up in deadlock.


The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this article.
Copyright © 1999 CompanyLongName All Rights Reserved.
http://www.flounder.com/mvp_tips.htm

Messages are one of the foundations of Windows programming. A Windows application is event-driven. Then means that it doesn’t call functions to get user-input. It waits for input to arrive, and responds to it. In fact, the typical situation is that there are hundreds of threads in the system at any moment, of which almost all are in a waiting state. Many of them are waiting for Messages.

The system communicates with applications via messages. For example, every mouse movement or mouse click is translated into a message, and posted to the message queue of the relevant window. Applications can use messages for inter-process and inter-window communication. Messages can also be posted to specific threads, and can be used as a means of cooperation between threads of a process.

The contents of a message

A message contains three elements: The message identifier (unsigned int), and two parameters. The message identifier tells what kind of message this is: For example, WM_PAINT is a message ordering a window to be drawn. WM_QUIT orders a window to close itself. The meaning of the parameters varies, and it depends on the kind of message being processed.

Message Queue

A message typically cannot be treated immediately when it is sent, because the recieving thread is busy doing other things. Messages stand in line to be treated at a First-In-First-Out order, with the exception of certain messages that have higher priority or lower priority than others. This queue of messages is handled by the system. Every thread potentially has a message queue attached to it. The message queue is not created until it is accessed by the thread for the first time (typically by calling GetMessage or PeekMessage).

Message Loop / Message Pump

At the top of a Windows application you will always find a message loop (also called messaeg pump). A minimal message loop may look as follows:

MSG msg;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)  {
        if (bRet == -1) {
            // handle the error and possibly exit
        } else {
             DispatchMessage(&msg);
        }
}

The function GetMessage waits until the is a message in the message queue, and returns the message contents. The function DispatchMessage sends the message contents to the appropriate function that performs actions in response to the message.

The contents of an MSG is as follows:

typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG;

hwnd is the handle of the window to which the message is targeted. It is NULL when the message is posted to a thread rather than a window. The next 3 parameters message, wParam and lParam are the message identifier and the two parameters. time and pt signify the time the message was posted and the mouse cursor location at that time.

In the case of messages posted to a thread (i.e. hwnd is NULL), DispatchMessage is useless. You need to write your own switch-case section that will handle the message recieved from GetMessage, instead of calling DispatchMessage.

Window Procedures

There are two kinds of messages: Messages posted or sent to windows, and messages posted to threads that are not associated with any window. Most messages in the system belong to the first group (i.e. the hwnd parameter is not NULL). The purpose of DispatchMessage is to send the contents of the message to the appropriate function that should deal with it, depending on the window (hwnd) associated with this message. The system registers a specific procedure (called Window Procedure) for every type of window in the system (called Window Class). This is the prototype of a window procedure:

LRESULT CALLBACK WindowProc(      
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
);

In MFC you don’t directly deal with this basic window procedure. MFC is arranged such that the window procedure calls the function CWnd::WindowProc, that has the following prototype:

virtual LRESULT CWnd::WindowProc( UINT message, WPARAM wParam, LPARAM lParam );

You typically don’t even need to override this function. It does the work of dispatching the message to specific message-handler functions, such as CWnd::OnMouseMove, CWnd::OnPaint etc…, where you implement application-specific behavior.

Posting and Sending messages to windows

The most important functions for pushing messages to the message queue are PostMessage and SendMessage. Their prortotypes are:

BOOL PostMessage(HWND hWnd,    UINT Msg,    WPARAM wParam,    LPARAM lParam);
LRESULT SendMessage(HWND hWnd,    UINT Msg,    WPARAM wParam,    LPARAM lParam);

PostMessage adds the message to the given window’s message queue, and returns immediately, without waiting for the message to be processed. SendMessage does not return until the message has been handled by the window procedure (i.e. the recieving Windows procedures has called ReplyMessage).

Messages posted through PostMessage are called queued messages. Messages sent through SendMessage are called non-queued messages, and they are first to be retrieved by GetMessage. This means that they will be treated first, before the queued messages, regardless of the order in which the messages were posted/sent.

When calling SendMessage(hWnd,…)  from the same thread that manages the window of hWnd, the system will directly call that window’s window procedure. The message will not enter the message queue at all. When SendMessage is called from a different thread, the message is written to the message queue, but has precedence over all queued messages. SendMessage will not return until the recieving thread treats the message.

Things to notice when using SendMessage

Because SendMessage blocks execution, you should use it with care. If two windows of different threads (or even different applications) communicate carelessly using SendMessage, they can reach a dead-lock, when each thread waits for the other one to complete its task. When using SendMessage you have to be sure that the recieving window is not waiting for the calling thread, and is not too busy to handle the sent message.

A way to avoid deadlocks

Suppose that a worker thread wants to run an operation on the main thread (because it update GUI elements that should only be accessed by the main thread), and it uses SendMessage() to do so. If the main thread, at that time, is waiting for some event to be signaled by the worker thread, it will result in a deadlock. One solution, is to replace the wait from WaitForSingleObject(event,INFINITY) to the following loop:

While (WaitForSingleObject(event,50)=WAIT_TIMEOUT)

PeekMessage(&msg,…);

WaitForSingleObject waits for the event to be signaled. After 50 milliseconds, if the event has not been signaled, we call PeekMessage, which will respond to sent messages (not to queued messages), thereby preventing the deadlock.

Before using SendMessage, you should consult the MSDN documentation of this function.

Things to notice when using PostMessage

When you use PostMessage there is no risk of a dead-lock, since you’re not blocking the execution. However, there is a different risk: You don’t know when the message will eventually be treated. It can be far in the future – depending on how busy the application is. By that time, the parametes lParam and wParam may become invalid (if they are pointers to data), or the entire message may be irrelevant. If such a risk exists in your application, you have to find a way to signify to the function that responds to the message, that the message is not relevant any more.

Posting messages to threads

You can also use messages as a means of inter-thread communication. It is a bit like using events, in the sense that one thread is waiting for an event, and another thread signals the event, thereby causing the first thread to react. But it enables more detailed control due to the fact that messages have three parameters – the message type, and two parameters. In order to exploit this mechanism, you need to write your own message loop in one thread, and use PostThreadMessage to post messages to the thread’s message queue.

BOOL PostThreadMessage(      
    DWORD idThread,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

The thread that handles the messages should run a message loop such as the following:

while(GetMessage( &msg, -1, 0, 0 ))  {
        switch (msg.message) {
            case WM_APP: do_some_action(msg.wParam,msg.lParam); break;
            case WM_APP+1: do_some_action1(); break;
            case WM_QUIT: return;
        }
}

IMPORTANT: Before you post a message to a thread, you should make sure that the thread has a message queue. When the thread is created, it doesn’t have a message queue, until the first call to PeekMessage, or other message-queue functions. To make sure the message queue is ready, call PeekMessage before the message loop, to create the message queue, and then signal an event to let other threads know that the message queue is ready.

Notice that PeekMessage and GetMessage have similar functions. The main difference between them is that PeekMessage returns immediately, and GetMessage waits for a message to be present in the message queue.

Ranges of message identifiers

It is important to know which numbers can be used as message identifiers, if you are going to use your own messages for communicating among windows or threads. The following ranges have different meanings:

0 to WM_USER-1: Reserved for use by the system. For example, WM_PAINT, WM_MOUSEMOVE, WM_CHAR. Send messages in this range to mimic system behavior. For example – to cause a window to paint itself, or to cause a dialog to react as if the user pressed a certain button, or to cause a window to close.

WM_USER to WM_APP-1: For use by private window classes. Don’t use it to send messages to other applications unless both applications are defined to understand them.

WM_APP to 0xBFFF: Available for use by applications. These will not conflict with system messages. To use them, you need to send WM_APP+x and in the message loop or window procedure, respond to messages with the identifier WM_APP+x. This is good for any x < 16384.

0xC000 to 0xFFFF: Named messages (called “string messages”) for use by applications. Like CreateEvent is used to create a named event for inter-process communication, RegisterWindowMessage is used to get the message identifier for a given name of message. The returned number won’t be the same in different sessions.

Greater than 0xFFFF: Reserved by the system for future use.

The WM_COPYDATA message

Normal messages send only three parameters. The WM_COPYDATA message has a special behavior that enables to send larger amounts of data between processes. When sending a WM_COPYDATA message, the lParam points to data, which will be copied to the address space of the recievnig process.

To send a WM_COPYDATA, you have to use SendMessage. PostMessage will not work. The wParam and lParam have special roles: wParam is a handle to the sender window, and lParam is a poiniter to a COPYDATASTRUCT structure:

typedef struct tagCOPYDATASTRUCT {
    ULONG_PTR dwData; // Some description of the data
    DWORD cbData; // Size, in bytes, of the data.
    PVOID lpData; // Pointer to data.
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

The recieving application should consider the data read-only. The recieved data in lParam is only valid in the recieving application during the processing of the message.

The WM_TIMER message

Timers also work through the message loop. A timer is a mechanism that enables you run a prescribed operation every X milliseconds. To define a timer, you use the function SetTimer:

UINT_PTR SetTimer(      
    HWND hWnd,
    UINT_PTR nIDEvent,
    UINT uElapse,
    TIMERPROC lpTimerFunc
);

The most common way to work with a timer is to associate it with a window (by sending hWnd in the first parameter). When you do so, the window procedure associated with that window will recieve WM_TIMER messages every uElapse milliseconds. The default windows procedure calls the callback function lpTimerFunc, if it is not NULL.

In MFC, you use CWnd::SetTimer:

UINT_PTR SetTimer(
   UINT_PTR nIDEvent,
   UINT nElapse,
   void (CALLBACK* lpfnTimer)(HWND,   UINT,   UINT_PTR,   DWORD)
);

which is exactly the same, but saves you the need of knowing hWnd.

In MFC, the most convenient thing is to send a NULL lpfnTimer, and treat the message using the OnTimer() event handler of the relevant window.

A WM_TIMER message is generated when the time elapses by GetMessage or PeekMessage of the appropriate thread, which is the thread in which the relevant window was created, or, in case hWnd is NULL, the thread that called SetTimer().

In the case the hWnd parameter in SetTimer() is NULL, this means that the timer is not associated with any window. It is handled in one of two ways: Either you write your own switch-case statement in the message loop, that handles WM_TIMER messages, or the default window procedure is called, and it invokes the given callback function lpfnTimer.

It is important to know that WM_TIMER is a low-priority message. It won’t be generated if there are messages waiting in the message queue, or unqueued (sent) messages. If a timer is designed to send a WM_TIMER message every 1 second, and the thread is busy doing some long computation 10 seconds, then immediately after the thread finishes the long computation, a WM_TIMER will be sent. During these 10 seconds, 9 scheduled WM_TIMER messages have been lost (or, more precisely, they have not been generated). For the exact order or priority at which messages get treated, see the remarks section of the MSDN entry on PeekMessage.

To stop a timer from working, call KillTimer. If KillTimer is called from a different thread from the thread that responds to the WM_TIMER message, it is possible that the OnTimer event handler will work once after KillTimer has finished. If this is hazardeous to your application, you have to write your own synchronization code to prevent it from happening.

Содержание

  1. Отправка сообщения для обработки окон
  2. Описание процесса отправки сообщения на окна Windows
  3. Какие окна поддерживают отправку сообщений?
  4. Какие данные можно передать с помощью сообщений?
  5. Как происходит процесс отправки сообщения на окно Windows?
  6. Отправка сообщений на окна Windows: классы и функции
  7. Пример использования функции SendMessage:
  8. Пример использования функции PostMessage:
  9. Основные классы и функции для отправки сообщений
  10. Пример использования функции SendMessage:
  11. Примеры использования классов и функций для отправки сообщений

Отправка сообщения для обработки окон

Отправка сообщений для обработки окон — важный аспект взаимодействия между различными процессами операционной системы Windows. Приложения в Windows взаимодействуют друг с другом путем отправки и получения сообщений, что позволяет им совместно работать и передавать информацию между собой.

Независимо от того, является ли это простым текстовым сообщением или сложной командой, отправка сообщения для обработки окон требует специального подхода и знания функций, предоставляемых операционной системой. Каждое сообщение отправляется с уникальным идентификатором, который указывает, какое окно должно его получить, и содержит определенные данные или команды, которые должны быть обработаны получателем.

Отправка сообщений для обработки окон важна для широкого спектра приложений, включая графические интерфейсы пользовательского опыта, многозадачные приложения и системные службы. Этот механизм обеспечивает эффективную и безопасную коммуникацию между различными процессами и позволяет создавать сложные и гибкие приложения для операционной системы Windows.

В данной статье мы рассмотрим, как отправлять сообщения для обработки окон в приложениях Windows, то есть каким образом можно установить связь между процессами и передавать им информацию для выполнения различных задач.

Описание процесса отправки сообщения на окна Windows

Для начала, необходимо определить, какой именно процесс запускается на Windows. Ведь каждое окно на рабочем столе представляет собой отдельный процесс, который имеет свой собственный идентификатор. Чтобы отправить сообщение на конкретное окно, необходимо знать его идентификатор, который можно получить с помощью специальных функций операционной системы.

Один из таких инструментов — функция SendMessage, которая позволяет отправить сообщение на указанное окно. Для этого необходимо указать идентификатор окна, тип сообщения и параметры, если они требуются. Эта функция является частью API Windows и может быть вызвана из кода программы на языке C++ или другом языке программирования, поддерживающим данную функцию.

Важно отметить, что отправка сообщений на окна Windows может быть полезна в различных сценариях. Например, она может использоваться для управления окнами других приложений, передачи данных между процессами, реализации межпроцессного взаимодействия и других задач. Для достижения наилучшей производительности и стабильности рекомендуется использовать документацию и ознакомиться со всеми доступными функциями и параметрами.

Какие окна поддерживают отправку сообщений?

Когда речь идет о закрытых программных окнах, отправка сообщений между процессами может быть чрезвычайно полезной функцией. Этот механизм позволяет процессам обмениваться информацией и взаимодействовать друг с другом, даже если они работают в изолированных окружениях. Возможность отправки сообщений может использоваться для передачи данных, команд и уведомлений между различными окнами.

Теперь вы наверняка интересуетесь, какие именно окна поддерживают такую функциональность. В общем, отправка сообщений может осуществляться между любыми процессами, которые запущены в операционной системе Windows. Это может быть выполнено как в пределах одной программы, так и между различными программами или даже процессами операционной системы. Конечно, существуют различные условия и требования для работы с механизмом отправки сообщений, но в целом его функциональность доступна для большинства оконных приложений в Windows.

Одним из самых распространенных способов реализации отправки сообщений в Windows является использование специальных функций и API, предоставляемых операционной системой. Например, с помощью функций, таких как SendMessage или PostMessage, мы можем отправлять сообщения определенным окнам или процессам. Эти функции позволяют нам передавать различные параметры и данные, а также получать ответы от получателя сообщения.

Также стоит отметить, что наличие функциональности отправки сообщений может зависеть от версии операционной системы Windows и используемой технологии разработки. Некоторые старые версии Windows, например, могут иметь ограничения в использовании этой функциональности, а некоторые современные технологии разработки могут предоставлять собственные инструменты и API для обмена сообщениями между окнами и процессами.

Какие данные можно передать с помощью сообщений?

Сообщения используются для обмена данными между процессами, и в зависимости от типа сообщения, можно передавать различные типы информации. Сообщения могут содержать текстовые данные, числа, бинарные данные, ссылки на объекты и даже пользовательские структуры данных.

В текстовом сообщении можно передать любую строку, содержащую информацию, которую необходимо передать другому процессу. Такое сообщение может содержать, например, текстовое сообщение с приветствием, запросом или ответом на запрос. Текстовые сообщения удобны в использовании и позволяют передать любую информацию, которую можно представить в виде строки.

Если нужно передать числовые данные, можно использовать числовые сообщения. Они могут содержать целочисленные или дробные числа, а также специальные числовые значения. Числовые сообщения могут использоваться для передачи данных, связанных с вычислениями, статистикой или любыми другими числовыми значениями.

  • Текстовые данные
  • Числовые данные
  • Бинарные данные
  • Ссылки на объекты
  • Пользовательские структуры данных

Бинарные сообщения позволяют передавать данные в бинарном формате. Они могут использоваться для передачи изображений, звуковых файлов или любых других файлов, которые могут быть представлены в виде байтов. Бинарные сообщения могут быть полезны, если необходимо передать большие объемы данных или если требуется более эффективное кодирование информации.

Если важно передать ссылку на объект, можно использовать ссылочные сообщения. Ссылочное сообщение содержит указатель на объект, который можно использовать для получения доступа к его свойствам и методам. Такое сообщение может быть полезным, если процессы нужно совместно использовать объект или передать его другому процессу для дальнейшей обработки или анализа.

Также можно передавать пользовательские структуры данных с помощью сообщений. Это позволяет создавать и передавать сложные объекты, содержащие различные типы данных и информацию. Пользовательские структуры данных могут быть полезными, если требуется передать сложные объекты или данные, которые не могут быть представлены в виде простых типов данных.

Как происходит процесс отправки сообщения на окно Windows?

Когда мы отправляем сообщение на окно Windows, это означает, что мы хотим передать информацию или команду конкретному приложению или процессу, который запущен в операционной системе Windows. Этот процесс включает несколько шагов, которые позволяют нам связаться с нужным окном и передать нужную информацию.

Первым шагом в процессе отправки сообщения на окно Windows является определение идентификатора окна, с которым мы хотим связаться. В Windows каждое окно имеет свой уникальный идентификатор, так называемый «HWND» (Handle to Window). Идентификатор окна может быть получен с помощью функции Windows API, такой как FindWindow или EnumWindows.

Когда идентификатор окна определен, следующим шагом является создание сообщения, которое мы хотим отправить. Сообщение может быть любым, от простого текстового сообщения до специальных команд или событий. Для создания сообщения мы используем функцию Windows API, такую как SendMessage или PostMessage. В сообщении мы передаем тип сообщения, параметры и другую необходимую информацию.

Когда сообщение создано, оно отправляется указанному окну с помощью функции SendMessage или PostMessage. Эти функции передают сообщение в очередь сообщений процесса, к которому относится окно. Затем оконная процедура этого процесса получает сообщение из очереди и обрабатывает его в соответствии с заданными действиями.

В зависимости от того, как оконная процедура программы обрабатывает полученное сообщение, происходит нужная реакция внутри приложения или процесса. Это может быть изменение состояния окна, выполнение определенной функции, отображение информации на экране или любой другой вид взаимодействия с пользователем.

Таким образом, процесс отправки сообщения на окно Windows включает идентификацию нужного окна, создание и отправку сообщения, обработку сообщения оконной процедурой и выполнение нужных действий в приложении или процессе. Этот процесс позволяет нам взаимодействовать с различными окнами в операционной системе Windows и передавать им необходимую информацию или команды.

Отправка сообщений на окна Windows: классы и функции

Один из наиболее распространенных способов отправки сообщений на окна Windows — использование функций из библиотеки User32.dll. Эта библиотека содержит множество функций, которые предоставляют доступ к окнам и элементам интерфейса операционной системы.

Одна из наиболее часто используемых функций для отправки сообщений на окна Windows — SendMessage. Она позволяет отправить сообщение определенному окну, указав код сообщения и дополнительные параметры. Например, с помощью этой функции можно отправить сообщение о нажатии кнопки или обновлении содержимого окна.

Еще одним классом, который облегчает отправку сообщений на окна Windows, является PostMessage. Этот класс аналогичен SendMessage, однако у него есть одно отличие — он не блокирует выполнение программы до тех пор, пока сообщение не будет обработано. Вместо этого PostMessage добавляет сообщение в очередь операционной системы и продолжает выполнение программы.

Также существуют другие классы и функции для отправки сообщений на окна Windows, например, SendInput и SendMessageTimeout. Они предоставляют более разнообразные возможности для взаимодействия с окнами и элементами интерфейса операционной системы.

Пример использования функции SendMessage:

HWND hWnd = FindWindow(NULL, «Название окна»);

int nButtonID = 123;

// Отправка сообщения о нажатии кнопки

SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(nButtonID, BN_CLICKED), NULL);

В приведенном примере используется функция FindWindow для поиска окна по его названию. Затем, с помощью функции SendMessage, отправляется сообщение о нажатии кнопки с определенным идентификатором.

Пример использования функции PostMessage:

HWND hWnd = FindWindow(NULL, «Название окна»);

int nButtonID = 123;

// Отправка сообщения о нажатии кнопки

PostMessage(hWnd, WM_COMMAND, MAKEWPARAM(nButtonID, BN_CLICKED), NULL);

В данном примере также используется функция FindWindow для поиска окна по названию, а затем с помощью функции PostMessage отправляется сообщение о нажатии кнопки с определенным идентификатором. Однако, в отличие от SendMessage, выполнение программы не блокируется до обработки сообщения.

Таким образом, классы и функции для отправки сообщений на окна Windows облегчают взаимодействие между процессами и обмен данными в операционной системе. Они предоставляют разнообразные возможности для отправки и обработки сообщений, позволяя разработчикам создавать более функциональные и интерактивные приложения.

Основные классы и функции для отправки сообщений

Одним из классов, используемых для отправки сообщений, является класс «HWND», который представляет собой дескриптор окна. Этот класс позволяет программе определить окно, к которому будет отправлено сообщение. Для получения дескриптора окна используется функция «FindWindow», которая осуществляет поиск окна по его заголовку или классу.

Другой важный класс для отправки сообщений — это класс «WM_MESSAGES». Данный класс содержит множество константные значений, которые представляют собой различные типы сообщений. Например, для отправки простого текстового сообщения можно воспользоваться константой «WM_SETTEXT».

  • Класс «HWND» — дескриптор окна, представляет собой идентификатор окна;
  • Класс «WM_MESSAGES» — содержит различные типы сообщений;
  • Функция «FindWindow» — поиск окна по заголовку или классу.

Кроме классов и функций, существуют специальные функции для отправки сообщений, например «SendMessage» и «PostMessage». Функция «SendMessage» предназначена для отправки сообщения и блокирует выполнение программы до тех пор, пока не будет получен ответ от окна-получателя. Функция «PostMessage» отправляет сообщение и не блокирует выполнение программы, продолжая свою работу независимо от ответа.

В итоге, для отправки сообщений из одного процесса Windows в другой, необходимо использовать классы «HWND» и «WM_MESSAGES», а также функции «FindWindow», «SendMessage» и «PostMessage». Эти инструменты позволяют точно указать окно-получатель и тип сообщения, а также осуществить отправку и получение данных между процессами.

Пример использования функции SendMessage:

HWND MESSAGE WPARAM LPARAM
hwndReceiver WM_SETTEXT 0 TEXT(«Hello, Windows!»)

Примеры использования классов и функций для отправки сообщений

1. Класс SendMessage:

Класс SendMessage является одним из наиболее распространенных и полезных классов для отправки сообщений. Он позволяет отправлять сообщения указанному окну и получать ответ от него. Для использования этого класса вам необходимо указать идентификатор окна (HWND) и код сообщения, который вы хотите отправить. Например, вы можете использовать этот класс для отправки сообщения WM_CLOSE, чтобы закрыть указанное окно.

2. Функция PostMessage:

Функция PostMessage является еще одним способом отправки сообщений и отличается от класса SendMessage тем, что она не блокирует выполнение программы в ожидании ответа. Вместо этого, она помещает сообщение в очередь сообщений и продолжает выполнение программы. Это может быть полезно, если вам не требуется получать ответ от окна сразу же.

3. Использование пользовательских сообщений:

Помимо стандартных сообщений, таких как WM_CLOSE, вы также можете определить собственные пользовательские сообщения. Для этого вы можете использовать функцию RegisterWindowMessage, чтобы зарегистрировать уникальный идентификатор для вашего сообщения. Затем вы можете использовать этот идентификатор в функциях отправки сообщений, таких как SendMessage или PostMessage, для взаимодействия с другими процессами и окнами.

Безусловно, использование сообщений для общения с другими процессами является незаменимым инструментом в современных операционных системах. В данной статье мы рассмотрели несколько примеров использования сообщений, которые помогут вам более глубоко понять их потенциал.

Первым примером является использование сообщений для межпроцессного взаимодействия. С помощью сообщений можно передавать данные между различными процессами, обмениваться информацией и синхронизировать их работу. Это особенно полезно, когда необходимо передать большой объем данных или синхронизировать работу нескольких процессов.

Вторым примером является использование сообщений для реализации асинхронного взаимодействия. Когда процесс отправляет сообщение, он может продолжать свою работу, не дожидаясь ответа. Это позволяет эффективно использовать ресурсы и повысить производительность системы. Например, сообщения можно использовать для реализации асинхронной обработки запросов или для организации многопоточности.

Третьим примером является использование сообщений для управления процессами. С помощью сообщений можно отправлять команды или запросы на выполнение определенных действий другим процессам. Например, вы можете отправить сообщение остановки или перезапуска службы или программы, а также запросить информацию о состоянии процесса или получить результат выполнения определенного действия.

Таким образом, использование сообщений для общения с другими процессами предоставляет широкие возможности взаимодействия и управления процессами. Это надежный и эффективный способ передачи данных и команд между процессами, что может быть очень полезно в различных сценариях. Использование сообщений позволяет повысить гибкость и производительность вашей системы, а также упростить ее управление.

[Previous] [Next]

Window messages can be sent directly to a window procedure by using the SendMessage function:

 LRESULT SendMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

The window procedure will process the message. Only after the message has been processed will SendMessage return to the caller. Because of its synchronous nature, SendMessage is used more frequently than either PostMessage or PostThreadMessage. The calling thread knows that the window message has been completely processed before the next line of code executes.

Here is how SendMessage works. If the thread calling SendMessage is sending a message to a window created by the same thread, SendMessage is simple: it just calls the specified window’s window procedure as a subroutine. When the window procedure is finished processing the message, it returns a value back to SendMessage. SendMessage returns this value to the calling thread.

However, if a thread is sending a message to a window created by another thread, the internal workings of SendMessage are far more complicated.1 Windows requires that the thread that created the window process the window’s message. So if you call SendMessage to send a message to a window created by another process, and therefore to another thread, your thread cannot possibly process the window message because your thread is not running in the other process’s address space and therefore does not have access to the window procedure’s code and data. In fact, your thread is suspended while the other thread is processing the message. So in order to send a window message to a window created by another thread, the system must perform the actions I’ll discuss next.

First, the sent message is appended to the receiving thread’s send-message queue, which has the effect of setting the QS_SENDMESSAGE flag (which I’ll discuss later) for that thread. Second, if the receiving thread is already executing code and isn’t waiting for messages (on a call to GetMessage, PeekMessage, or WaitMessage), the sent message can’t be processed—the system won’t interrupt the thread to process the message immediately. When the receiving thread is waiting for messages, the system first checks to see whether the QS_SENDMESSAGE wake flag is set, and if it is, the system scans the list of messages in the send-message queue to find the first sent message. It is possible that several sent messages could pile up in this queue. For example, several threads could each send a message to a single window at the same time. When this happens, the system simply appends these messages to the receiving thread’s send-message queue.

When the receiving thread is waiting for messages, the system extracts the first message in the send-message queue and calls the appropriate window procedure to process the message. If no more messages are in the send-message queue, the QS_SENDMESSAGE wake flag is turned off. While the receiving thread is processing the message, the thread that called SendMessage is sitting idle, waiting for a message to appear in its reply-message queue. After the sent message is processed, the window procedure’s return value is posted to the sending thread’s reply-message queue. The sending thread will now wake up and retrieve the return value contained inside the reply message. This return value is the value that is returned from the call to SendMessage. At this point, the sending thread continues execution as normal.

While a thread is waiting for SendMessage to return, it basically sits idle. It is, however, allowed to perform one task: if another thread in the system sends a message to a window created by a thread that is waiting for SendMessage to return, the system will process the sent message immediately. The system doesn’t have to wait for the thread to call GetMessage, PeekMessage, or WaitMessage in this case.

Because Windows uses this method to handle the sending of interthread messages, it’s possible that your thread could hang. For example, let’s say that the thread processing the sent message has a bug and enters an infinite loop. What happens to the thread that called SendMessage? Will it ever be resumed? Does this mean that a bug in one application can cause another application to hang? The answer is yes!

Four functions—SendMessageTimeout, SendMessageCallback, SendNotifyMessage, and ReplyMessage—allow you to write code defensively to protect yourself from this situation. The first function is SendMessageTimeout:

 LRESULT SendMessageTimeout( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PDWORD_PTR pdwResult); 

The SendMessageTimeout function allows you to specify the maximum amount of time you are willing to wait for another thread to reply to your message. The first four parameters are the same parameters that you pass to SendMessage. For the fuFlags parameter, you can pass SMTO_NORMAL (defined as 0), SMTO_ABORTIFHUNG, SMTO_BLOCK, SMTO_NOTIMEOUTIFNOTHUNG, or a combination of these flags.

The SMTO_ABORTIFHUNG flag tells SendMessageTimeout to check whether the receiving thread is in a hung state2 and, if so, to return immediately. The SMTO_NOTIMEOUTIFNOTHUNG flag causes the function to ignore the timeout value if the receiving thread is not hung. The SMTO_BLOCK flag causes the calling thread not to process any other sent messages until SendMessageTimeout returns. The SMTO_NORMAL flag is defined as 0 in WinUser.h; this is the flag to use if you don’t specify any combination of the other flags.

Earlier in this section I said that a thread could be interrupted while waiting for a sent message to return so that it can process another sent message. Using the SMTO_BLOCK flag stops the system from allowing this interruption. You should use this flag only if your thread could not process a sent message while waiting for its sent message to be processed. Using SMTO_BLOCK could create a deadlock situation until the timeout expires—for example, if you send a message to another thread and that thread needs to send a message to your thread. In this case, neither thread can continue processing and both threads are forever suspended.

The uTimeout parameter specifies the number of milliseconds you are willing to wait for the reply message. If the function is successful, TRUE is returned and the result of the message is copied into the buffer whose address you specify in the pdwResult parameter.

By the way, this function is prototyped incorrectly in the header file of WinUser.h. The function should be prototyped simply as returning a BOOL since the LRESULT is actually returned via a parameter to the function. This raises some problems because SendMessageTimeout will return FALSE if you pass an invalid window handle or if it times out. The only way to know for sure why the function failed is by calling GetLastError. However, GetLastError will be 0 (ERROR_SUCCESS) if the function fails because of a timeout. If you pass an invalid handle, GetLastError will be 1400 (ERROR_INVALID_WINDOW_HANDLE).

If you call SendMessageTimeout to send a message to a window created by the calling thread, the system simply calls the window procedure and places the return value in pdwResult. Because all processing must take place with one thread, the code following the call to SendMessageTimeout cannot start executing until after the message has been processed.

The second function that can help send interthread messages is SendMessageCallback:

 BOOL SendMessageCallback( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwData); 

Again, the first four parameters are the same as those used by the SendMessage function. When a thread calls SendMessageCallback, the function sends the message off to the receiving thread’s send-message queue and immediately returns so that your thread can continue processing. When the receiving thread has finished processing the message, a message is posted to the sending thread’s reply-message queue. Later, the system notifies your thread of the reply by calling a function that you write using the following prototype:

 VOID CALLBACK ResultCallBack( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult); 

You must pass the address to this function as the pfnResultCallBack parameter of SendMessageCallback. When this function is called, it is passed the handle of the window that finished processing the message and the message value in the first two parameters. The third parameter, dwData, will always be the value that you passed in the dwData parameter to SendMessageCallback. The system simply takes whatever you specify here and passes it directly to your ResultCallBack function. The last parameter passed to your ResultCallBack function is the result from the window procedure that processed the message.

Because SendMessageCallback returns immediately when performing an interthread send, the callback function is not called as soon as the receiving thread finishes processing the message. Instead, the receiving thread posts a message to the sending thread’s reply-message queue. The next time the sending thread calls GetMessage, PeekMessage, WaitMessage, or one of the SendMessage* functions, the message is pulled from the reply-message queue and your ResultCallBack function is executed.

The SendMessageCallback function has another use. Windows offers a method by which you can broadcast a message to all the existing overlapped windows in the system by calling SendMessage and passing HWND_BROADCAST (defined as -1) as the hwnd parameter. Use this method only to broadcast a message whose return value you aren’t interested in, because the function can return only a single LRESULT. But by using the SendMessageCallback function, you can broadcast a message to every overlapped window and see the result of each. Your ResultCallBack function will be called with the result of every window processing the message.

If you call SendMessageCallback to send a message to a window created by the calling thread, the system immediately calls the window procedure, and then, after the message is processed, the system calls the ResultCallBack function. After the ResultCallBack function returns, execution begins at the line following the call to SendMessageCallback.

The third function that can help send interthread messages is SendNotifyMessage:

 BOOL SendNotifyMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

SendNotifyMessage places a message in the send-message queue of the receiving thread and returns to the calling thread immediately. This should sound familiar because it is exactly what the PostMessage function does. However, SendNotifyMessage differs from PostMessage in two ways.

First, if SendNotifyMessage sends a message to a window created by another thread, the sent message has higher priority than posted messages placed in the receiving thread’s queue. In other words, messages that the SendNotifyMessage function places in a queue are always retrieved before messages that the PostMessage function posts to a queue.

Second, when you are sending a message to a window created by the calling thread, SendNotifyMessage works exactly like the SendMessage function: SendNotifyMessage doesn’t return until the message has been processed.

As it turns out, most messages sent to a window are used for notification purposes; that is, the message is sent because the window needs to be aware that a state change has occurred so that it can perform some processing before you carry on with your work. For example, WM_ACTIVATE, WM_DESTROY, WM_ENABLE, WM_SIZE, WM_SETFOCUS, and WM_MOVE (to name just a few) are all notifications that are sent to a window by the system instead of being posted. However, these messages are notifications to the window; the system doesn’t have to stop running so that the window procedure can process these messages. In contrast, when the system sends a WM_CREATE message to a window, the system must wait until the window has finished processing the message. If the return value is -1, the window is not created.

The fourth function that can help in sending interthread messages is ReplyMessage:

 BOOL ReplyMessage(LRESULT lResult); 

This function is different from the three functions we just discussed. Whereas SendMessageTimeout, SendMessageCallback, and SendNotifyMessage are used by the thread sending a message to protect itself from hanging, ReplyMessage is called by the thread receiving the window message. When a thread calls ReplyMessage, it is telling the system that it has completed enough work to know the result of the message and that the result should be packaged up and posted to the sending thread’s reply-message queue. This allows the sending thread to wake up, get the result, and continue executing.

The thread calling ReplyMessage specifies the result of processing the message in the lResult parameter. After ReplyMessage is called, the thread that sent the message resumes, and the thread processing the message continues to process the message. Neither thread is suspended; both can continue executing normally. When the thread processing the message returns from its window procedure, any value that it returns is simply ignored.

The problem with ReplyMessage is that it has to be called from within the window procedure that is receiving the message and not by the thread that called one of the Send* functions. So you are better off writing defensive code by replacing your calls to SendMessage with one of the three Send* functions discussed previously instead of relying on the implementer of a window procedure to make calls to ReplyMessage.

You should also be aware that ReplyMessage does nothing if you call it while processing a message sent from the same thread. In fact, this is what ReplyMessage‘s return value indicates. ReplyMessage returns TRUE if you call it while you are processing an interthread send and FALSE if you are processing an intrathread send.

At times, you might want to know if you are processing an interthread or an intrathread sent message. You can find this out by calling InSendMessage:

The name of this function does not accurately explain what it does. At first glance, you would think that this function returns TRUE if the thread is processing a sent message and FALSE if it’s processing a posted message. You would be wrong. The function returns TRUE if the thread is processing an interthread sent message and FALSE if it is processing an intrathread sent or posted message. The return values of InSendMessage and ReplyMessage are identical.

There is another function that you can call to determine what type of message your window procedure is processing:

 DWORD InSendMessageEx(PVOID pvReserved); 

When you call this function, you must pass NULL for the pvReserved parameter. The function’s return value indicates what type of message you are processing. If the return value is ISMEX_NOSEND (defined as 0), the thread is processing an intrathread sent or posted message. If the return value is not ISMEX_NOSEND, it is a combination of the bit flags described in the following table.

Flag Description
ISMEX_SEND The thread is processing an interthread sent message sent using either the SendMessage or SendMessageTimeout function. If the ISMEX_REPLIED flag is not set, the sending thread is blocked waiting for the reply.
ISMEX_NOTIFY The thread is processing an interthread sent message sent using the SendNotifyMessage function. The sending thread is not waiting for a reply and is not blocked.
ISMEX_CALLBACK The thread is processing an interthread sent message sent using the SendMessageCallback function. The sending thread is not waiting for a reply and is not blocked.
ISMEX_REPLIED The thread is processing an interthread sent message and has already called ReplyMessage. The sending thread is not blocked.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Windows 7 starter изменить фон
  • Как удалить дистрибутивы обновлений windows 10
  • Системные программы для windows бесплатно
  • Как поставить кодировку windows 1251
  • Как сделать вход в папку по паролю windows 10