In a comment on my discussion on how to return results back from the WM_ message, Jan Ringoš observed that it felt wasteful that there was this entire infrastructure for copying blocks of memory via a window message, yet only one message uses it! “I always thought something like EnableWindowMessageDataCopy (HWND, UINT, .) after RegisterWindowMessage and ChangeWindowMessageFilterEx to get application’s own private WM_COPYDATA would be a little more secure and convenient, should the programmer didn’t wish to bother with creating shared memory.”
The infrastructure for copying blocks of memory via a window message is used by far more than just one message! The WM_ and WM_ message use it for passing string buffers, the WM_ message uses it for passing the HELPINFO structure, the WM_ message uses it for passing the MDICREATSTRUCT structure, and plenty more where those came from. The infrastructure for copying blocks of memory had already existed; it wasn’t created just for the WM_ message. adding WM_ support was just adding a few lines of code to the common function whose job is to prepare messages to be sent between processes (including copying memory between processes).
Suppose there were a way for a program to declare that one of its custom messages should have (say) its lParam be a pointer to data and its wParam be the size of the data. That could be misleading because the only behavior would be copying the memory block and not the data inside it. For example, if the structure contained pointers, the pointers would just be copied as raw values, rather than adding the pointed-to-data to the memory block and adjusting the pointers to point to the copy. It also doesn’t handle the case of sending the message between programs with different pointer or handle sizes, say between a 32-bit program and a 64-bit program.¹ If you need to copy data structures that consists of anything more than scalars (or aggregates of scalars), you’ll have to do your own marshaling to convert your source data structure into a transfer buffer. In practice, this means that sending the message directly with an as-is buffer is unlikely to be the common case; some type of conversion would have to be made anyway.
Furthermore, the WM_ already knew that you wanted to do this, because it left room for it in the COPYDATASTRUCT:
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData; // ← here
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
In addition to describing the memory buffer, there is this extra guy called dwData. You can put your “message number” in there, allowing you to multiplex multiple “messages” into a single WM_ message.²
You don’t need EnableWindowMessageDataCopy because you already have it at home. The window manager is more concerned with enabling things that weren’t possible before, rather than making it easier to do things that are already possible. For that, you can use a helper library.
Bonus chatter: In addition to adding complexity to the window manager implementation, allowing programs to customize how messages are marshaled between processes would also make it harder to explain how inter-process marshaling works. Instead of the simple rule “The system marshals messages in the system range, but not messages in the user-defined range,” it would be a much more ambiguous rule: “The system marshals messages in the system range, but not messages in the user-defined range, unless those messages have been customized by a call to EnableWindowMessageDataCopy, in which case they marshal by this alternate set of rules.” So now when you look at a message, you can’t tell how it marshals. You’d have to go back to the documentation for the message and hope the person who wrote the documentation remembered to go back and add a section to each page to say whether it follows custom marshaling.
¹ Or between a 16-bit program and a 32-bit program, which was the more common case back in the days when WM_ was designed. In 16-bit code, an int is a 16-bit integer, whereas it’s a 32-bit value in 32-bit code.
² If the dwData was intended to be a message number, why is it pointer-sized? For the same reason timer IDs and dialog control IDs are 64-bit values: “Pointers are like weeds. Anywhere it’s possible to fit a pointer, a pointer will try to squeeze in there.” In this case, people were putting handles (which are pointer-sized) in the dwData, so we had to make it big enough to hold a handle.
0 comments
Be the first to start the discussion.