The WM_ message can be used to send a blob of data from one window to another. The window manager does the work of copying the data from the sending process to the receiving process, but how does the receiving process send data back?
If the only information that needs to come back is a success/failure, the recipient can return TRUE on success or FALSE on failure.
But if you need to return more information, then you have a few choices.
One is to have the receiving window send the results back to the sending window by sending the WM_ message back to the sending window. (The sending window passes its handle in the wParam.) The data blob can contain a transaction ID or some other way to distinguish which WM_ the recipient is responding to.
Another way is for the sending window to create a shared memory block, duplicate the shared handle into the receiving window’s process,¹ and then pass the duplicated handle in the WM_ payload. The receiving window can use MapViewOfFile to access the shared memory block and write its results there. Of course, if you’re going to do it this way, then you don’t really need WM_; you can just use a custom message and pass the handle in, say, the wParam.
A customer said that if they created a shared memory block with CreateFileMapping, they were worried because memory would become visible to all other processes, not just the two processes trying to talk to each other.
Maybe they were thinking about named shared memory blocks, which are accessible to anybody who knows (or can guess) the name, and for whom access is granted by the shared memory block’s access control list.
So don’t use a named shared memory block. Use an anonymous one. The only way to get access to an anonymous shared memory block is to get access to its handle.
So your exposure is not to all processes but just processes which have “duplicate handle” permission. And somebody has “duplicate handle” permission on your process, then they already pwn your process: They can duplicate the GetCurrentProcess() handle out of your process, and that gives them a handle with full access to your process. Your exposure is only to people who are already on the other side of the airtight hatchway.
¹ This assumes that the sending process is running at equal or higher integrity level than the recipient. If the roles are reversed, with a low integrity process sending to a high integrity process, you can delegate the duplication to the recipient. The low integrity sending process allocates the shared memory and puts the handle into the WM_ memory block. The recipient can then call DuplicateHandle function to duplicate the handle out of the sending process, using GetWindowThreadProcessId to get the sender’s process ID. You can include information in the WM_ memory block to indicate that you are in this reverse case.
Wouldn’t sending back WM_COPYDATA also run into problems if the two programs are running at different integrity levels, due to UIPI?
That was W 3.1, and it worked. UIPI was introduced much later, I think at W 8.
I mentioned it because Raymond’s post already mentioned integrity levels in the footnote.
That said, I guess it’s not that much of a problem since a process can use ChangeWindowMessageFilterEx() to allow WM_COPYDATA on a window basis.
Back in 1994 I needed to make an RPC between Windows 3.1 16-bit application and an 32 bit app running under Win32s subsystem.
This message saved my day. It probably would have worked under Windows NT too, but I I never tested it there.
I prefer passing handles in lParam, since it has the same signedness.
Also it always struck me as kind of wasteful, that there’s this whole data transfer mechanism for window messages, and just one user-facing message is using 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.
If a message takes a pointer, the pointer is usually passed in the LPARAM, whereas if the message takes a handle or an integer, then it is passed in the WPARAM.
Alright. If there’s a convention, there’s a convention. It’s not like HANDLEs use upper bit anyway. In fact they can fit in 3 bytes.
Can the recipient return an LRESULT value to the process that sent the WM_COPYDATA message or is it truly limited to TRUE (non-zero) and FALSE (zero) values?
Who knows, the only real thing is that this is documented. WM_COPYDATA explicitly states that it should return TRUE or FALSE.