Tu peux utiliser les mêmes API Win32 qu'avec
les ports COM i.e. CreateFile, WriteFile, ReadFile et CloseHandle.
La difficulté consiste à trouver le nom du port USB à utiliser lorsque tu fais le CreateFile. Il te faut récupérer le SymbolicName dans la registry sous la clé HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB.
Chacune des subkeys de la clé ci-dessus correspond à une entrée USB (devices, hubs...) et il te faut rechercher celle que tu veux. Une fois que tu l'as trouvé, tu peux ouvrir le port avec CreateFile.

Tu peux également utiliser la SetupApi.dll pour rendre ce processus automatique une fois que tu sais quelles informations tu cherches mais ce n'est pas nécessaire dans un premier temps et tout n'est pas accessible (du moins je n'ai pas pu accéder à toutes les infos que je cherchais...)
J'ai dû modifié le source code donc ça risque de ne pas compiler dès le premier coup... Je te conseille de lire l'article sur les Communications avec un port série (lien au début du post).
class CComm
{
protected:
// Handle du port
HANDLE m_hPort;
// Structure pour les IO
OVERLAPPED m_olWrite, m_olRead;
public:
BOOL ConnectComm (CString strPortName)
{
// Return value
BOOL hResult = FALSE;
// Initialize the structures for asynchronous Read/Write operations
::ZeroMemory(&m_olWrite, sizeof(OVERLAPPED));
::ZeroMemory(&m_olRead, sizeof(OVERLAPPED));
// Initializes asynchronous events
m_olWrite.hEvent= CreateEvent(0, TRUE, FALSE, 0);
m_olRead.hEvent = CreateEvent(0, TRUE, FALSE, 0);
// Flags
DWORD dwAttrib = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
m_hPort = ::CreateFile(strPortName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
dwAttrib,
NULL);
if ( m_hPort != INVALID_HANDLE_VALUE )
{
hResult = TRUE;
}
else
{
_ASSERT(FALSE);
m_hPort = NULL;
}
return hResult;
};
void DisconnectComm()
{
if (m_hPort)
{
::CloseHandle(m_hPort);
m_hPort= NULL;
}
if (m_olWrite.hEvent)
{
::CloseHandle(m_olWrite.hEvent);
m_olWrite.hEvent= NULL;
}
if (m_olRead.hEvent)
{
::CloseHandle(m_olRead.hEvent);
m_olRead.hEvent= NULL;
}
};
BOOL Write (LPBYTE lpData, DWORD dwLength,DWORD& dwTotalWritten)
{
DWORD dwWritten = 0, dwResult = 0, dwError;
BOOL hr = FALSE;
BOOL bTimeOut = TRUE;
// Lors de la lecture ou l'écriture sur un device,
// le seul élément utilisé est le hEvent
ResetOVERLAPPEDStruct(&m_olWrite);
while (dwLength)
{
if (!::WriteFile(m_hPort, pData, dwLength, &dwWritten, &m_olWrite))
{
dwError = ::GetLastError();
if ( dwError == ERROR_IO_PENDING )
{
dwResult = ::WaitForSingleObject(m_olWrite.hEvent,5000);
switch(dwResult)
{
case WAIT_OBJECT_0:
::GetOverlappedResult(m_hPort, &m_olWrite, &dwWritten, FALSE);
if ( dwWritten )
hr = TRUE;
break;
case WAIT_TIMEOUT:
case WAIT_ABANDONED:
case WAIT_FAILED:
default:
_ASSERT(FALSE);
break;
}
}
else
{
_ASSERT(FALSE);
}
}
else
{
if (dwWritten == dwLength)
hr = TRUE;
}
}
return hr;
};
BOOL Read(LPBYTE pData, DWORD nMax, DWORD& dwRead)
{
BOOL hr = FALSE;
DWORD dwErr, dwLen = (DWORD)nMax, dwError, dwResult;
COMSTAT CS;
DWORD oldtime;
bool bContinue = true;
// Lors de la lecture ou l'écriture sur un device,
// le seul élément utilisé est le hEvent
ResetOVERLAPPEDStruct(&m_olRead);
if (dwLen)
{
// Lecture du port
if (!::ReadFile(m_hPort, pData, dwLen, &dwRead, &m_olRead))
{
dwError = ::GetLastError();
if ( dwError == ERROR_IO_PENDING )
{
dwResult = ::WaitForSingleObject(m_olRead.hEvent, 5000);
switch(dwResult)
{
case WAIT_OBJECT_0:
::GetOverlappedResult(m_hPort, &m_olRead, &dwRead, FALSE);
hr = TRUE;
break;
case WAIT_TIMEOUT:
case WAIT_ABANDONED:
case WAIT_FAILED:
default:
_ASSERT(FALSE);
break;
}
}
else
{
_ASSERT(FALSE);
}
}
else
{
dwError = ::GetLastError();
hr = TRUE;
}
}
}
return hr;
};
};
void ResetOVERLAPPEDStruct(OVERLAPPED *pStruct)
{
if ( pStruct == NULL )
return;
pStruct->Internal = 0;
pStruct->InternalHigh = 0;
pStruct->Offset = 0;
pStruct->OffsetHigh = 0;
}