What's the difference between file descriptors and handles? Both are just opaque numeric identifiers. There's no particular reason why there can't be an API to associate a callback with a file descriptor for async I/O.
One small detail is that HANDLE covers more things with the same table. So for example, a process, a thread, a cross-process semaphore or mutex - those are handles, whereas Unix-like systems have different types for each of those (pid_t, pthread_t, sem_t, etc.).
But then Linux started introducing fds as synchronization primitives too - like signalfd(2) or eventfd(2).
One of the more annoying things about Windows handles is there is no equivalent of dup2(2) - you can't swap the kernel object of an existing HANDLE with another existing HANDLE. This makes imitating certain unix idioms involving redirecting I/O after the process has already started kind of clumsy.
[Worth noting in a discussion of the HANDLE typedef that the type is literally a void pointer, and some user-mode code abuses that to make them pointers to heap objects instead of a proper kernel handle - for example FindFirstFile()'s handle is not a true kernel handle, which is for example why you need to close it with FindClose and not the normal way to close handles.]
> What's the difference between file descriptors and handles? Both are just opaque numeric identifiers.
The main difference I know of is that file descriptors are sequential, while handles are random. That is, a function like open() will always return the lowest-numbered available file descriptor, while CreateFile() can return any available handle.
Other than that, both are basically identical as far as I know: both are just keys to an in-kernel table which points to the real object.
One small detail is that HANDLE covers more things with the same table. So for example, a process, a thread, a cross-process semaphore or mutex - those are handles, whereas Unix-like systems have different types for each of those (pid_t, pthread_t, sem_t, etc.).
But then Linux started introducing fds as synchronization primitives too - like signalfd(2) or eventfd(2).
One of the more annoying things about Windows handles is there is no equivalent of dup2(2) - you can't swap the kernel object of an existing HANDLE with another existing HANDLE. This makes imitating certain unix idioms involving redirecting I/O after the process has already started kind of clumsy.
[Worth noting in a discussion of the HANDLE typedef that the type is literally a void pointer, and some user-mode code abuses that to make them pointers to heap objects instead of a proper kernel handle - for example FindFirstFile()'s handle is not a true kernel handle, which is for example why you need to close it with FindClose and not the normal way to close handles.]