Date: | November 22, 2013 / year-entry #309 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20131122-00/?p=2593 |
Comments: | 37 |
Summary: | A customer wanted to know how to get the effect of CW_USEDEFAULT positioning on a window that already exists. In particular, they wanted to be able to reposition a dialog box to get the CW_USEDEFAULT cascade effect, but since you can't actually pass CW_USEDEFAULT in a dialog template, the repositioning has to be done after... |
A customer wanted to know how to get the effect of The solution here is simple: Create a temporary invisible window with |
Comments (37)
Comments are closed. |
I dare say the customer thought of that, but just wanted to know if there's a way that isn't a blood-ugly hack.
IF the custoomer, as A C suggests, thinks this is a blodd-ugly hack, he has my full support.
If that's the solution, I'd rather do without the feature.
As hacks go, it's not that bad at all: it only relies on putting a string of documented features together (CreateWindow -> GetWindowRect -> DestroyWindow). They're even being used for their intended purposes! If I recall, this is sometimes also known as "programming."
Interesting. I guess since usually dialog boxes have an associated owner window and aren't usually used like normal overlapped windows, it looks like there simply aren't any parameters in any of the dialog creation functions to control/direct dialog-box positioning; it looks like you are basically expected to explicitly reposition the dialog yourself in your own code.
The temporary window solution may be a little hacky, but then again, Windows already use hidden windows for all manner of things like receiving cross-apartment STA COM calls, so how bad is a temporary window really?
@Another AC: my guess is the customer wants to use a dialog as their application's main window, in which case it makes perfect sense that they'd want to support as much of the "normal" window-positioning behaviors as feasible.
@ErikF — I don't see how creating & destroying a window without ever displaying it qualifies as "being used for their intended purposes"
As hmm mentioned there are numerous reasons to create a window without displaying it. Windows to receive broadcast messages. Windows for STA COM calls. Windows for other types of simple IPC.
While creating a window just to see where it is positioned is a bit hokey, it is hardly the worst thing in the world, and as Erik pointed out — totally documented.
Hey guys – welcome to programming. Putting some blocks together, in this case a hidden window and a bunch of documented APIs. It is certainly not a hack. It would take about 5 minutes to write a wrapper function that does all of this, i.e. CreateDefaultWindowPosition(…), and now whatever might have the potential to look ugly is tucked away elegantly.
Clever. For some reason, I am cursing myself for not realizing it.
I hope the customer has great reasons for doing this. But IMHO, with big displays and multimonitor setups, the only window that should be left at WM_DEFAULT position is the application's main window, and only if the user hasn't resized or moved it in a previous session (in which case, we should infer the user wants it to be *there*). Dialog boxes should be placed over their owner, maybe centered on it, but I have come to prefer them just bellow the title bar's center. Palettes and other secondary windows should be placed relative to the main window, too, in coherent places (for example, next to the left or right border). I know this is not exactly what Windows UI guidelines say, but these rules aren't incoherent with them, and I think they can greatly help the user.
All of this is my own opinion, grown over the years out of watching my customers using my applications, and watered with a bit of common sense.
It *is* a hack, because it's doing a very large amount of work in order to get information that is trivially exposed for use by the Window Manager in kernel-mode, but hidden from view for user-mode programs.
Create window class -> syscall -> add an entry to the atom table -> create Window -> syscall -> create window handle -> create preemptive caches of everything a window might want -> ask the window manager to come up with a CW_USEDEFAULT position -> inform users over RDP that a new window has been made -> register window with window station -> begin window pump -> attach to thread -> return to user some time later -> syscall to get window position -> Destroy Window -> tear everything down -> window pump a WM_DESTROY and WM_NCDESTROY -> destroy window handle -> destroy window class -> tell RDP that the window has been destroyed.
It's maybe a hundred transitions from user to kernel if we include the windows pump, huge amounts of state being constructed and then immediately destroyed – when all you wanted was to ask the window manager to come up with a CW_USEDEFAULT position for your window.
I once wanted to create an application which consisted entirely of a modal dialog but in such a way that it would appear in Windows 95's task list.
I ended up creating a visible minimised window which owned an invisible window which was the owner of the dialog. This meant that the dialog was associated with the minimised window in the task list, but opened at the "default" position.
Right, because that REALLY slows down the application, compared to the other 30 windows the application is going to create anyway.
I do think this is an ugly hack.
It should be possible to pass CW_USEDEFAULT to MoveWindow (or call it MW_USEDEFAULT for that matter).
@Matt (and others): If calling an additional function to get a value is considered hacky, then you must absolutely hate the "allocate until the buffer is large enough" approach used by functions like RegQueryValueEx! :-)
@James Curran
> @ErikF — I don't see how creating & destroying a window without ever displaying it qualifies as "being used for their intended purposes".
Invisible windows are used for all sorts of purposes. I think you're focussing too much on one use of a window – something having a shape to restrict a device context, when it's also very useful as something to associate a message queue with on a finer scale than a process.
*Cough* COM, *cough* Marshalling…
I'm sure whoever wrote some of that code found it non-trivial too, but I'm sure they didn't call it an ugly hack.
@ErikF: You don't? There's something inherently hacky about dual-purpose functions and the Win32 API has some of the worst offenders in that regard (to name MultiByteToWideChar as just one example that immediately comes to mind).
On the other hand I don't see anything inherently wrong about the proposed approach here – that's not really something people need every day is it and the work around is well documented and hardly that much additional work.
Using GUI objects for IPC is a hack. Although many do it because there's no better (convenient, performant, simpel) way to do it. Still a hack though.
Doesn't this solution have concurrency issues when other processes create dialogs?
E.g.: Your process creates its dummy window and it goes to e.g. position (100,100). But before you can create the real window, the task scheduler switches to a second process which creates a dialog the normal way, which is cascaded down to e.g. position 130,130. After that, your process gets CPU time and creates its window at 100,100.
In the end, the window at 100,100 is on top of the window at 130,130. Although it was supposed to be the other way around.
WM_INITDIALOG
handler and another thread creates a dialog at 130,130. You then show your initialized dialog, and it appears at 100,100. -Raymond]@ErikF The problem with those "Provide us buffer large enough" functions is that they introduce races. While it's okay for MultiByteToWideChar — you pass your own string in, so don't be your own enemy and don't change it between two calls, — it's not okay for, say, GetTcpTable: the number of connections can easily grow up before the second call to it. Really, can't the system allocate some internal buffer on its own?
And do you how netstat circumvents that problem with GetTcpTable? Oh, it calls some un-documented API function that basically calls GetTcpTable three more times on ERROR_INSUFFICIENT_BUFFER until it gives up. Yeaaaaaaah, totally not a hack.
I'm not saying that the "provide your own buffer and hope it's big enough" method isn't wonderful (it's not, and I'd rather have the system create the buffer for me.) I was just trying to argue that compared with something like my example, having to create a hidden window to get dimensions for a dialogue box is not nearly as bad!
@Joker_vD: There's no race condition or bug in any of those "first ask for input size then call it again with that buffer" functions. Assuming that you don't want to let the Kernel/library allocate memory for you (for a low-level API such as Win32 that makes sense to some degree although it complicates the interface naturally) there's not many ways around a loop to solve the problem (at least not if the kernel's involved, you can't it a function pointer and expect it to execute the code there after all). Low level but not ugly given the constraints.
Higher level libraries abstract all those details away anyhow and at least there are good reasons to do so. But using the same function to ask for the necessary buffer size and populating it? Ugly and unnecessary.
"Doesn't this solution have concurrency issues when other processes create dialogs?"
Yes, and this happens right now with different processes, so it's not really a big deal. It's no different than the process being swapped out after the window is created but before it is shown.
@voo: The kernel knows the exact amount of memory it needs when a function is called. It can allocate the buffer and copy the needed data right there, in one pass. Instead we do two passes, and it does open a window to DoS attacks.
"But using the same function to ask for the necessary buffer size and populating it? Ugly and unnecessary." — do you really propose doubling the amount of functions in public API? What would be the benefit? Okay, assume I have both DWORD GetTcpTableSize() and BOOL GetTcpTableData(PMIB_TCPTABLE pTcpTable, _In_ DWORD pdwSize, _In_ BOOL bOrder). So, if the GetTcpData fails, I would have to call GetTcpTableSize() again, because GetTcpTableData must not convey the needed size (that's the whole point of separation, otherwise GetXxxCount() is redundant), so I still do two function calls in normal scenario, but twice as many for a re-try. Where is the gain?
@Eric Brown: Who is going to free it? Either a) the kernel itself, for example the next time your the thread calls this function again; b) you when you're done with it. As for the "from where" question — oh, there are TONS of function just to allocate memory: GlobalAlloc, LocalAlloc, HeapAlloc, SysAllocSomething, pick yours.
Really, the question "but who's gonna call CloseHandle on the returned handle?" has pretty much the same nature yet somehow people manage to solve it.
CloseHandle
has exactly the same problem, and you solve it by makingHANDLE
a fixed size so that everybody knows exactly how much space to preallocate. -Raymond]Maurits: The problem is that somebody could change the registry value between the two calls to RegQueryValueEx, causing even the second call to fail.
@Joker_vD: Sure the kernel can allocate memory for you.. now the question is – where? Even the Win32 API had historically more than one heap, then there's malloc, new, if you use a higher level language it may want to do its own memory allocation and so on. Now if it's purely on the user side you could just pass a function pointer for allocation around, but clearly that's impossible if you call the Kernel.
Yes you do twice as many function calls in the generally exceedingly rare scenario where you have to retry – designing your APIs in such a way that they're a bit more efficient in some rare cornercases is the best way how you end up with horrible APIs. Go ahead and measure in some large program how often you actually have to retry, for the vast majority of functions I'll guarantee you that that will be exceedingly rare.
@Joker_vD: And who, exactly, is going to *free* that memory that the API allocated? And *where* is that memory going to be allocated *from*? The kernel doesn't allocate user-mode memory, and kernel-mode memory isn't accessible from user-mode…
@ErikF: You don't need more than two calls to RegQueryValueEx.
msdn.microsoft.com/…/ms724911(v=vs.85).aspx
If the buffer specified by lpData parameter is not large enough to hold the data, the function returns ERROR_MORE_DATA ***and stores the required buffer size in the variable pointed to by lpcbData***
@voo: "Even the Win32 API had historically more than one heap, then there's malloc, new, if you use a higher level language it may want to do its own memory allocation and so on. Now if it's purely on the user side you could just pass a function pointer for allocation around, but clearly that's impossible if you call the Kernel."
Where? Somewhere, I don't quite care: all I want to do is to have read permission for this area of memory. Every language's runtime somehow has to cope with using memory it doesn't own, so it's not a huge problem that you can't pass your own "malloc" arounf. Also consider this scheme: the API returns you the pointer to the data AND the pointer to the function which you have to call on the data pointer when you're done with it. And the best thing is, you don't even need to return the second pointer — just write in the docs that "The returned pointer must be freed with the call to LocalFree". FormatMessage follows this, though it's arguably the function which doesn't suffer from "data may change their size between two calls".
Also, just yesterday evening I stumbled upon "Identifying and Exploiting Windows Kernel Race Conditions via Memory Access Patterns" by M. Jurczyk and G. Coldwind. Which tells me that I can trick the kernel into believing the buffer provided to it is bigger than it actually is, and exploit it to log keypresses or elevate rights. Hmmm.
Damn. The KeUserModeCallback is a lie.
Speaking seriously, I really wish I knew the perfect solution. The kernel knows how much memory it will need, but it can't allocate it. The user can allocate memory, but doesn't know how much he has to. And when the kernel reports the size needed, that information, in general case, becomes stale immediately. Making two consecutive calls {GetDataSize();GetActualSize();} an atomic operation is prohibitively expensive, as I'd imagine. What's the answer? I don't know it.
In theory, the system could have been designed in such a way that kernel mode could ask user mode for memory. I believe this should in theory be possible in NT-based Windows, but I'd be happy to be proven wrong.
Lacking that, the retry loop could have been hidden by the API. I've seen a lot of code with potential bugs involving memory allocation for return data, so I think it would have been much better if Microsoft had implemented this the right way once and for all.
Regardless, from the perspective of an application developer these look the same. And remind us of IMalloc.
@Anonymous "the retry loop could have been hidden by the API.":
At the moment, the user can pass a buffer that's usually big enough in the first try and just reallocating in the rare case data doesn't fit. That may be an important performance optimization in some cases.
All the kernel has to do is create a snapshot of the data in paged pool, then call out to your user-mode callback to allocate the memory, copy the data from the paged pool into the pointer returned from the callback, and free the paged pool allocation.
It's so simple it makes you wonder why all APIs aren't implemented that way!
Of course, you'd still have to have the current mechanism that lets you get the data without memory allocation, an additional copy, and another kernel transition. Do you add a memory allocation callback function pointer to every call or do you create a separate version of each API that requires it?
Wait, that mechanism won't work if your data is too big for a paged pool allocation. In that case maybe the kernel should just map pages into your address space so you can do the allocation and copy yourself if need be, then call VirtualFree when you're done. But your address space may be taken up by your heap and there may not be enough contiguous addresses to allocate, in which case you'd have to fallback to the heap allocation anyway.
Hmm…maybe it's not so simple afterall.
ExitThread
? Yay! Kernel pool memory leak! -Raymond]>That may be an important performance optimization in some cases.
I have yet to see them. But I've seen many many sometimes subtle API return value allocation errors.
>Yay! Kernel pool memory leak!
Exiting a thread will always land you back in kernel mode, and there is no reason to believe the kernel couldn't clean up after itself.
Alternatively, have the kernel ask for user mode memory.
> AllocateMemoryForKernelApi(size_t size) … woe unto you…
AllocateMemoryForKernelApi should take an LPVOID * parameter for the allocated buffer and instead return an error code. That way if your callback cannot satisfy the kernel's requested memory allocation size you can return an error (and maybe set the output parameter to nullptr if that's your thang). Then the kernel can retry your function in chunks with a smaller requested buffer size, or assume that you've taken corrective action after the first call and retry with the (maybe same if nothing else changed) size, or just fail out.
In order for that to be an issue, the main continuation of the thread would have to be the memory allocation callback, which means the return value of the original function is never used (and it is hence only called in order to initiate the callback). A rather contrived scenario since it assumes that the system would allow this in the first place (and that your stack is infinite and some other weirdities). It won't be a problem.
I still prefer my idea of allocating user memory though. It's simpler since you have less to take into account since any screw up will only affect the user process.
void *AllocateMemoryForKernelApi(size_t size) {
if (logging_enabled) {
auto logfile_name = RegQueryValueAlloc(hkey, AllocateMemoryForKernelApiNoLogging);
blah blah;
delete[] (char*)logfile_name;
}
return new char[size];
}
Notice that while kernel is allocating memory, we call back into kernel to allocate more memory. So now you need to keep a list of "all the memory temporarily allocated by kernel waiting to be copied back into user space" just in case a stack overflow or bad_alloc occurs. Plus there's the difficulty of getting this right on the kernel side since you now cannot simply get the answer and copy it to the buffer, then release your locks. Now you have to get the answer, copy it to a temporary buffer, drop all locks, allocate memory from user space, copy the result, the free the temporary buffer. And woe unto you if you have to do this more than once in a single call. -Raymond]