It's better to make an AxDll (apartment threading) and create an object for each IOCP thread. I wouldn't recommend to use Std-EXE threading because you lose the debugging ability (each IOCP thread sleeps in most time). Of course you could use a small asm thunk (the cycle with GetQueuedCompletionStatus call) to transmit the data from an IOCP thread to the main thread using my module as well. Regarding to AxDll approach, i had the experience with such approach when i needed to run several instances of ActiveScripting engines simultaneously:

Code:
Public Function ThreadProc( _
                ByRef tData As tThreadData) As Long
    Dim hWnd    As Long
    Dim tMsg    As MSG
    Dim lRet    As Long
    Dim pObj    As Long
    
    If tData.hr < 0 Then Exit Function
    
    hWnd = tData.hWnd
    pObj = tData.pObjectRaw
    
    Do
    
        lRet = GetMessage(tMsg, 0, 0, 0)
        
        If lRet = -1 Then
            ' // Error
            tData.hr = E_FAIL
            Exit Do
        ElseIf lRet = 0 Then
            ' // Exit message queue
            Exit Do
        Else
            TranslateMessage tMsg
            DispatchMessage tMsg
        End If
        
    Loop While True
        
    If tData.pObjectRaw Then
        vbaObjSetAddref pObj, ByVal 0&
    End If
        
End Function

Public Function ThreadInitProc( _
                ByRef tData As tThreadData) As Long
    Dim hr      As Long
    Dim tClsId  As UUID
    
    ' // Create communication window
    tData.hWnd = CreateWindowEx(0, THREAD_WND_CLASS, vbNullString, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, tData.hInstance, ByVal 0&)
    
    If tData.hWnd = 0 Then
        hr = E_FAIL
        GoTo exit_proc
    End If
    
    tData.lThreadID = GetCurrentThreadId()
    tData.hThread = OpenThread(SYNCHRONIZE, 0, tData.lThreadID)
    
    hr = CLSIDFromProgID(tData.sProgID, tClsId)
    If hr < 0 Then GoTo exit_proc
    
    ' // Create object
    hr = CoCreateInstance(tClsId, Nothing, CLSCTX_INPROC_SERVER, IID_IUnknown, tData.pObjectRaw)
    If hr < 0 Then GoTo exit_proc
    
    ' // Marshal
    hr = CoMarshalInterThreadInterfaceInStream(tData.tIID, ByVal tData.pObjectRaw, tData.pStream)
    If hr < 0 Then GoTo exit_proc
    
    tData.bInitialized = True
    
exit_proc:
    
    If hr < 0 Then
        
        If tData.pStream Then
            vbaObjSetAddref tData.pStream, ByVal 0&
        End If
        
        If tData.hThread Then
            CloseHandle tData.hThread
            tData.hThread = 0
        End If
        
        If tData.lThreadID Then
            tData.lThreadID = 0
        End If
        
        If tData.pObjectRaw Then
            vbaObjSetAddref tData.pObjectRaw, ByVal 0&
        End If
        
        tData.bInitialized = False
        
        If tData.hWnd Then
            DestroyWindow tData.hWnd
            tData.hWnd = 0
        End If
        
    End If
    
    tData.hr = hr
    
End Function


...

Public Sub CreateObjectInThread( _
           ByRef sProgID As String)
    Dim hr      As Long
    Dim cObj    As Object
    
    If m_tThreadData.hThread Then
        Err.Raise 5, "CThreadItem::CreateObjectInThread", "There is an active object in the thread"
    End If

    With m_tThreadData
    
        .sProgID = sProgID
        .tIID = IID_IDispatch
        .hInstance = App.hInstance
        .hr = 0
        .hThread = 0
        .hWnd = 0
        .lThreadID = 0
        .pObjectRaw = 0
        .pStream = 0

    End With
    
    hr = SHCreateThread(AddressOf ThreadProc, m_tThreadData, CTF_COINIT_STA, AddressOf ThreadInitProc)
    If hr < 0 Then Err.Raise hr
    
    If m_tThreadData.hr Then Err.Raise m_tThreadData.hr
    
    hr = CoGetInterfaceAndReleaseStream(ByVal m_tThreadData.pStream, IID_IDispatch, cObj)
    
    If hr < 0 Then Err.Raise hr
    
    Set m_cObjInThread = cObj
    
End Sub