Why does WaitForMultipleObjects return ERROR_INVALID_PARAMETER when all the parameters look valid to me?

A customer asked for assistance with the WaitForMultipleObjects function:

I am getting ERROR_INVALID_PARAMETER when calling Wait­For­Multiple­Objects even though all the parameters are valid as far as I can tell. I’ve narrowed it down to this simple program.

int main()
 int i;
 HANDLE Handles[4];

 // Create the events
 for (i = 0; i < 4; i++) {
  Handles[i] = CreateEvent(NULL, FALSE, FALSE, TEXT("Test"));
  if (Handles[i] == NULL) {
   printf("Failed to create event - test failed\n"); return 0;

 // Set them all to signaled
 for (i = 0; i < 4; i++) SetEvent(Handles[i]);

 // Wait for all of them - we expect this to return WAIT_OBJECT_0
 printf("WaitForMultipleObjects returned %d\n",
        WaitForMultipleObjects(4, Handles, TRUE, INFINITE));

 return 0;

First of all, thank you for narrowing the issue down to a minimal program that illustrates the problem. You’d be surprised how often a customer says, “I’m having problem with function X. Here’s a program that illustrates the problem.” And then attaches a huge project that doesn’t compile because it is written in some development environment different from the one you have on your machine.

The problem here is that you are passing four handles to the same event to Wait­For­Multiple­Objects with the bWait­All parameter set to TRUE. The Wait­For­Multiple­Objects function rejects duplicates if you ask it to wait for all of the objects. Why is that?

Well, consider this program: It creates a named auto-reset event (as is “obvious” from the FALSE second parameter passed to Create­Event) and stores a handle to it in Handles[0]. The second through fourth calls to Create­Event merely create new handles to the same auto-reset event because the name matches an existing event. The second loop sets that same event four times. And then the Wait­For­Multiple­Objects asks to wait for all of the handles to be signaled. But since all four handles refer to the same object, it’s being asked to wait until the event has reached the state where the wait can complete four times simultaneously. (Huh?)

Recall that Wait­For­Multiple­Objects does not alter the state of any of the waited objects until the wait completes. If you ask it to wait for both an event and a semaphore, and the event is signaled but the semaphore is not, then the function will leave the event signaled while it waits for the semaphore. Only when all the items being waited for are signaled will the Wait­For­Multiple­Objects function perform whatever actions are appropriate for acquiring a signaled object and return.

Okay, so we asked it to wait on the same auto-reset event four times. But that’s nonsense: An auto-reset event is just a stupid semaphore which can have at most one token. But in order for the wait to succeed, it needs four tokens. That’s never going to happen, so the wait is nonsensical.

More generally speaking, Wait­For­Multiple­Objects returns ERROR_INVALID_PARAMETER if you pass bWaitAll = TRUE and there are any duplicates in the handle array (either identical handles, or different handles to the same underlying object). It doesn’t try to puzzle out the objects and say, “Well, let me see if this is a reasonable combination of objects to wait on more than once”; it just sees the duplicate and says “Forget this!”

Going back to the customer’s original problem: We asked why they were creating four handles to the same object, and what they expected when waiting for an auto-reset event to have four available tokens (which it never will), and the customer admitted that it was just an error in their code. The original version of the code used a named event and waited on it with Wait­For­Single­Object, and when they modified the code to make it support multiple events, they forgot to give each event a different name.

Comments (14)
  1. Alex Grigoriev says:

    Besides from the customer's problem, use of a named event in 90% of the cases is a design error. In the remaining 10%, 9% are also highly suspicious and may not be well thought.

  2. ChrisW says:

    I wonder how a caller who gets some handles to use with this wait-api-function could check for this specific problem and produce a better error message?

    I have often wrapped up API functions to raise human readable exceptions, instead of general INVALID_PARAMETER errors. They check input parameters beforehand in DEBUG mode so a developer can find out easier what the problem may be.

  3. Rafael says:

    Alex could you provide more info about that? I use named events to signal it is time to do something or that something is already done, between already running applications. I can't think about a better IPC method to achieve that.

  4. LR says:

    The names are global (at least for all processes in the same session, and when you prefix the names with "Local"). Any program can grab your events and manipulate them. You need to make sure that there will never be any name collision, with your own software (when started more than once), and especially with other software.

  5. MarcT says:

    So weird, I just fixed a bug related to this last week.

    SetNotificationPositions is a DirectSound call that creates signal points in a hardware sound buffer, which we wait on using WaitForMultipleObjects. When the play head passes the notification point, we get a signal and can then refill a portion of the buffer. In our 10-year-old sound code, we divide the buffer into equal segments, with a notification position at the end of each, and also put a final notification position at the point where the file will end after X times filling the buffer.

    After 10 years and thousands of sound files, we finally happened to hit one where the "end position" coincided with one of the segment divisions, and SetNotificationPositions returned "INVALID_PARAMETER". And I tracked it down and realized exactly the fact this post explains.

    Really weird note for those not bored already: The call failed even though the two positions (expressed as bytes into the sound buffer) were 1 byte off. Playing with the debugger, I found that it failed when A = B – 1, and when A = B, but not A = B + 1. Maybe there's some sort of float conversion thing going on? I really hate sound drivers.

  6. Alex Grigoriev says:


    To see if your design falls into those 90% or 9% or the last 1%, you need to answer these questions:

    1. How do you make sure there is no name collision between your event name and third party app event name, if there is one?

    2. Do you need to communicate between applications in different security contexts (logon contexts) on the same desktop session?

    3. Do you need to isolate applications running in different desktop sessions?

    4. Do you need to run two instances of signalling or watching applications in the same session? Do you need to broadcast the signal?

  7. Anton says:


    My guess is that your sample size is more than 1 byte.

  8. Joshua says:

    Almost anytime I want an event I end up using a socket to simulate it.

    Why? So I can use select() to wait for it. WaitForMultipleObjects doesn't work correctly with socket objects, and select() doesn't work at all on event objects.

  9. Marc K says:
    1. How do you make sure there is no name collision between your event name and third party app

    event name, if there is one?

    When I've used a named event=, I've put a GUID in the name.  Intentional manipulation by third part applications? – Possible.  Accidental collision? – No.

  10. Rafael says:

    Alex, I think this is how windows programming is meant to be. Whatever synchronization object you use, there is a chance (in terms of possible/impossible) that there will be a collision (or another app trying to hijack us).

    IMHO, It is not a problem inherent from named objects per se. Imagine we could create an unnamed event object (or a pipe, mutex, memmappedfile, etc) and DuplicateHandle to our target process and work on the duplicated handle from there. Even this way, how could we know what our target process is? Checking its window caption? Checking its CRC? There is still a chance for collision or third party manipulation.

    Acidental collisions are mitigated by using a GUID like our friend Marc said. So we need to worry about third party app hijacks.

    This is what I meant when I said this is how windows programming is meant to be, there is no way to know what is a legit process since user mode processes are easily hijackable. Could you give an example of how would you do it to signal from process A that process B should do something?

  11. Gabe says:

    Name collisions are just an inherent property of the world. It can apply to anything that doesn't have a central naming authority. You can have collisions in window classes, ProgIDs, file names, registry keys, and everywhere else where you need to know names in advance without coordination.

  12. Alex Grigoriev says:

    @Mark, Rafael:

    Use of a GUID is what I hoped you're doing. That solves unintentional collision problem. Of course, named objects can be manipulated in Windows and "the other OS" by other processes; Windows is no worse here. "The other OS" also has peans to manipulate other process memory; no difference here as well.

    If you want to pass a handle to another process, your best chance is when you start that process. You could use lpReserved2 in STARTUPINFO to pass a blob of data, although you need to make sure you get to it before CRTL in the child process. Or you can use STARTUPINFOEX and pass the handles through it.

  13. Ben Voigt [Visual C++ MVP] says:

    @Joshua: There's WSAEventSelect for that.

  14. Gaddafi says:

    I use scottGUIDs to be safe.

Comments are closed.