COM asynchronous interfaces, part 6: Learning about completion without polling

Raymond Chen

So far, the way we’ve been waiting for an asynchronous call to complete is by polling for it. Is there a way to be notified directly when the operation completes? For example, while doing work, we may call out to other methods, and if the asynchronous call completes while those other methods are busy, we’d like to cancel those other calls so we can get back to the main work of dealing with the asynchronous call.

One way to do this is to peek at the kernel event handle that is hiding inside the ISynchronize interface. The ISynchronize­Handle interface lets you do that.

  HANDLE rawEventHandle = nullptr;
  call.as<::ISynchronizeHandle>()->GetHandle(&rawEventHandle);

Note that the handle that comes back is still owned by the call, so don’t close it. This is not usually a problem because you typically keep the call around since you will want to get the result of the asynchronous call, For example, you might want to suspend the current coroutine until the asynchronous call has completed.

  co_await winrt::resume_on_signal(rawEventHandle);
  // coroutine resumes when the asynchronous call completes

  call.Finish_Something();

Or you might initiate multiple asynchronous calls, and you want to process the results from whichever call finishes first.

  HANDLE readyEvents[2];

  call1.as<::ISynchronizeHandle>()->GetHandle(&readyEvents[0]);
  call2.as<::ISynchronizeHandle>()->GetHandle(&readyEvents[1]);

  DWORD index;
  auto hr = CoWaitForMultipleHandles(COWAIT_DEFAULT, INFINITE,
                                     2, readyEvents, &index);

  if (hr == S_OK) {
    if (index == 0) { /* deal with call1 */ }
    if (index == 1) { /* deal with call2 */ }
  }

If you want to use the handle in a way unrelated to the call it came from, then duplicate the handle, at which point you become responsible for the lifetime of the duplicate.

  winrt:handle eventHandle;
  winrt::check_bool(DuplicateHandle(
    GetCurrentProcess(), rawEventHandle,
    GetCurrentProcess(), eventHandle.put(),
    SYNCHRONIZE, FALSE, 0));

The duplicate handle eventHandle is your responsibility to close. You can hand it to some other component which uses it to know when the asynchronous method call has completed. Just remember to close it when you’re done.

Next time, we’ll look at a way of getting called back directly when the asynchronous call completes.

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Александр Давыдкин 0

    Back in 2014 you’ve written excellent explanation about default base address (0x00400000) in EXE PE for 32 bit: https://devblogs.microsoft.com/oldnewthing/20141003-00/?p=43923
    However I wonder why in 64 bit linker this address is 0x140000000 – notedly bigger than 4Gb which could (possibly) explain some backward compatibility for 32-bit virtual machines (or what?).
    Thanks a lot for this blog!

    • MGetz 0

      1. The default on x64 architectures should be relocatable in all cases

      2. Assuming that a DLL or EXE is marked otherwise choosing that address would deliberately break assumptions about loading thus allowing developers to fix their code to be relocatable.

      3. It allows tools to detect bad practices as a x64 binary shouldn’t have a specified base address for security reasons.

      4. The address is immaterial to 32bit code because that’s a different entry in the GDT with a different ‘address space’ (even if they are linearly laid over one another) so there is no way 32bit code can interact with that. It would have to switch the CS selector to the 64bit segment and enter long mode to do so. This only occurs in Wow64cpu.dll AFAIK.

      5. Thunking to 32bit (or back to 64bit) isn’t supported on win64 outside of Wow64 and only by the OS, so while it is allegedly possible it may or may not continue to work as it requires the loader to load the pages with the right flags in the page table.

      (( I expect Raymond to delete both of these comments as they are irrelevant to the post above))

      • Александр Давыдкин 0

        “(( I expect Raymond to delete both of these comments as they are irrelevant to the post above))”

        I’m sorry if I break rules, but I don’t know how to ask this question about very old entry in blog.

        Documentation ( here: https://docs.microsoft.com/en-us/cpp/build/reference/base-base-address?view=msvc-170 ) says MS linker in 64 bit will set this address to 0x140000000.
        After I asked question here I’ve got interesting answer in some programming forum: default address above 4Gb in 64bit could be useful to detect errors in transition of programs from 32 bit to 64 bit: if some code in older program saves pointers in 32bit integers it will be immediately broken and cause Access Violation in address space where lower 4Gb virtual memory are not marked as accessible.
        Good point.
        But why not just 0x100000000, but 0x140000000?
        Unpredictable relocations for security and protection reasons are good thing, but why linker and documentation insist on 0x140000000?
        Interesting…

    • Scarlet Manuka 0

      It seems pretty reasonable that the default address for a 64-bit image would be above the 4GB boundary.
      No 64-bit anything is going to be running in a 32-bit VM so I’m not sure what you’re getting at with the backwards compatibility comment.

Feedback usabilla icon