How do I convert a synchronous file handle into an asynchronous one?


Say you opened a file in synchronous mode, and then you realize that you want to issue asynchronous I/O on it, too. One way to do this is to call Create­File a second time with the FILE_FLAG_OVERLAPPED, but this requires you to know the file name, and the file name may not be readily available to the function that wants to do the conversion, or it may not even be valid any longer if the file has been renamed in the meantime.

Enter Re­Open­File. This basically lets you do a Create­File based on another handle rather than a file name. It differs from Duplicate­Handle because it actually goes and opens the file again (as opposed to merely creating another reference to the same file object in the kernel). This means that you have the opportunity to choose new handle attributes, like whether you want the handle to be synchronous or asynchronous.

#include <windows.h>
#include <stdio.h>

int __cdecl main(int, char **)
{
 HANDLE h = CreateFile("test", GENERIC_WRITE, FILE_SHARE_READ, NULL,
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 HANDLE h2 = ReOpenFile(h, GENERIC_READ, FILE_SHARE_READ |
                        FILE_SHARE_WRITE, FILE_FLAG_OVERLAPPED);
 DWORD cbResult;
 WriteFile(h, "!", 1, &cbResult, NULL);
 OVERLAPPED o = { 0 };
 o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 char ch = 0;
 BOOL fRc = ReadFile(h2, &ch, 1, &cbResult, &o);
 if (fRc) {
  printf("read completed synchronously\n");
 } else if (GetLastError() == ERROR_IO_PENDING) {
  printf("read proceeding asynchronously\n");
 } else {
  printf("read failed\n");
 }
 GetOverlappedResult(h2, &o, &cbResult, TRUE);
 printf("Result was %c\n", ch);

 CloseHandle(o.hEvent);
 CloseHandle(h2);
 CloseHandle(h);
 return 0;
}

The program opens a test file for writing, and then uses the Re­Open­File function to open the same file for reading. (Since you are opening the file twice, be careful to choose compatible sharing modes.) We synchronously write an exclamation point to the file via the first handle, and then we asynchronously read it back with the second handle.

It's really not that exciting.

Comments (20)
  1. WndSks says:

    ReOpenFile is Vista+ but can be emulated downlevel with the NT API. The only problem with this API is that the "has been renamed in the meantime" part only applies to the local system, on a a network share you might end up opening the wrong file…

  2. I use to consider renaming/moving/deleting files that are under use of a program undefined behavior, the equivalent of pulling a rug from under one's feet. Locking a file when in use is a good practice because it helps to avoid doing it accidentally, but there is always a point where you can do nothing (for example, the file is renamed in a file server, as skSdnW points, the user ejects the CD or pendrive, or the hard drive breaks). In that case, the only thing you can do is fail quickly and safely (at least, as safely as such a traumatic event allows you to).

  3. dave says:

    (Snark)

    ReOpen file also demonstrates the strange programmer habit of putting capitals in the middle or words.

    I think they should at least be consistent about it; if we're going to have ReOpen, why not ReName and ReMove?

  4. Hm says:

    The MSDN page for ReOpenFile states "In Windows 8 and Windows Server 2012, this function is supported by the following technologies: Server Message Block (SMB) 3.0 protocol". So it should really reopen the correct file even on a network share, instead of reopening the wrong file as stated by the last "Community Addition". This is a serious bug.

    Sadly, MSDN failes to document the expected behavior before Windows 8, and how it behaves with mixed versions of client and server.

  5. Mh says:

    @Antonio Grijan:

    I doubt that "Locking a file when in use" will prevent the renaming of a file. You can only lock ranged of bytes within the file, but not the directory entry.

    "In that case, the only thing you can do is fail quickly and safely": Why and how? If someone has renamed the log file your process is still writing to, nothing is wrong with that, and your process will not even notice.

  6. Gabe says:

    Mh: While you are techinically correct about locking, in this case he meant locking in the colloquial sense of "not shared for write or delete". In other words, he meant "specify at most FILE_SHARE_READ only". That will prevent the renaming or deleting of a file in use.

    And in the case of "fail quickly and safely", I believe he meant that the ReOpenFile call should fail in the event that it finds itself reopening a different file that just happens to have the same name as the original.

  7. Nick says:

    Boy, this post was getting me really excited.  Like change my shirt kind of excited.  And then I got to the last line.

  8. Aaron Ballman says:

    Can ReOpenFile create a race condition internally, or is it "atomic" from the perspective of the calling application?  Eg) if I call ReOpenFile, can another process with impeccable timing delete the file out from under me while the call is operating?

  9. Someone says:

    @Aaron Ballman: What race do you mean? Your program owns a handle to a file, and ReOpenFile() will give you another handle to the exact same file (ignoring the issue with network shares mentioned above).

    The operating system has already determined the entry in the Master File Table, there is no reason to consult the orginal directory entry to create the new handle.

    Because of this, it doesn't matter if the orginal directory entry was renamed or even deleted in the meantime.

    [Renaming an open file might not be possible on same filesystem (i.e. FAT variants). Deleting an open file is currently not possible at all in Win32.]

  10. Aaron Ballman says:

    @Someone — I'm referring to the phrase "actually goes and opens the file again".  While it would certainly make sense that the MFT entry will be the same and that the file will remained locked while performing this operation, I'm wondering whether there is a subtle race condition.  "as opposed to merely creating another reference to the same file object in the kernel" coupled with the previous quote could be [mis]read as the file handle being closed (which means it is available to rename/delete/be opened elsewhere with different sharing modes/etc) and then opened a second time.

  11. Someone says:

    "could be [mis]read as the file handle being closed": After ReOpenFile() returns, you have two file handles (h and h2 in Raymonds example). I don't see how can you get the impression that the original file handle needs to be closed to create another one.

  12. [Renaming an open file might not be possible on same filesystem (i.e. FAT variants). Deleting an open file is currently not possible at all in Win32.]

    With FILE_SHARE_DELETE, you CAN delete an open file.

  13. ReOpenFile is simply using open file by ID, which guarantees opening the same file as the original, even if the file was renamed in the meantime.

  14. Someone says:

    "With FILE_SHARE_DELETE, you CAN delete an open file." ok. thats right. Just that executables and DLL are opened by the system without that flag.

  15. Danny says:

    "…One way to do this is to call Create­File a second time with the FILE_FLAG_OVERLAPPED…" … "Enter Re­Open­File. This basically lets you do a Create­File based on another handle rather"

    And internally ReOpenFile does exactly that. It calls CreateFile.

  16. WndSks says:

    @alegr1 @Danny It does not open by file id, it just sets the original handle in OBJECT_ATTRIBUTES and calls NtCreateFile

  17. Aaron Ballman says:

    @Someone — you're absolutely correct, I missed the fact that the original handle remained open after the call.  That's what I get for looking at Raymond's blog at midnight after a day of travel.  :-/

  18. Gabe says:

    I don't see any option in CreateFile or NtCreateFile to open a handle based on an existing handle. Does anybody have documentation on how this supposedly works?

    I do see how you can open a file by ID, but that's a persistent filesystem-assigned object ID used for link tracking, not a handle.

  19. Someone says:

    @Gabe: I think alegr1 is refererring to something at the driver level. But because this would cause ReOpenFile() not to work with filesystems other than NTFS (file IDs exists only on NTFS, right?), I don't believe his statement.

  20. John Doe says:

    Does ReOpenFile work with existing handles to symbolic links, alternate streams, directories, physical disks and volumes, changers, tapes, serial ports, consoles, mailslots and pipes?

Comments are closed.