Creating IOCP and associating it with the file handles is done via the CreateloCompletionPort API function:
HANDLEWINAPICreateIoCompletionPort(_In_ HANDLE FileHandle,_In_opt_ HANDLE ExistingCompletionPort,_In_ ULONG_PTR CompletionKey,_In_ DWORD NumberOfConcurrentThreads);
Handle from the FileHandle parameter is associated with a new or existing completion port.
When the asynchronous I/O request for the IOCP-related file is completed, I/O manager creates an I/O completion packet and places it in the queue.
Packets are extracted from the queue when a worker thread calls the GetQueuedCompletionStatus API function:
BOOLWINAPIGetQueuedCompletionStatus(_In_ HANDLE CompletionPort,_Out_ LPDWORD lpNumberOfBytesTransferred,_Out_ PULONG_PTR lpCompletionKey,_Out_ LPOVERLAPPED * lpOverlapped,_In_ DWORD dwMilliseconds);