in src/main/c/Windows/SerialPort_Windows.c [119:609]
static void enumeratePorts(JNIEnv *env)
{
// Reset the enumerated flag on all non-open serial ports
for (int i = 0; i < serialPorts.length; ++i)
serialPorts.ports[i]->enumerated = (serialPorts.ports[i]->handle != INVALID_HANDLE_VALUE);
// Enumerate all serial ports present on the current system
wchar_t *deviceID = NULL;
DWORD deviceIdLength = 0;
const struct { GUID guid; DWORD flags; } setupClasses[] = {
{ .guid = GUID_DEVCLASS_PORTS, .flags = DIGCF_PRESENT },
{ .guid = GUID_DEVCLASS_MODEM, .flags = DIGCF_PRESENT },
{ .guid = GUID_DEVCLASS_MULTIPORTSERIAL, .flags = DIGCF_PRESENT },
{ .guid = GUID_DEVINTERFACE_COMPORT, .flags = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE },
{ .guid = GUID_DEVINTERFACE_MODEM, .flags = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE }
};
for (int i = 0; i < (sizeof(setupClasses) / sizeof(setupClasses[0])); ++i)
{
HDEVINFO devList = SetupDiGetClassDevsW(&setupClasses[i].guid, NULL, NULL, setupClasses[i].flags);
if (devList != INVALID_HANDLE_VALUE)
{
// Iterate through all devices
DWORD devInterfaceIndex = 0;
DEVPROPTYPE devInfoPropType;
SP_DEVINFO_DATA devInfoData;
SP_DRVINFO_DATA_W driverInfoData;
devInfoData.cbSize = sizeof(devInfoData);
driverInfoData.cbSize = sizeof(driverInfoData);
while (SetupDiEnumDeviceInfo(devList, devInterfaceIndex++, &devInfoData))
{
// Attempt to determine the device's Vendor ID and Product ID
DWORD deviceIdRequiredLength;
int vendorID = -1, productID = -1;
wchar_t *serialNumberString = NULL;
if (!SetupDiGetDeviceInstanceIdW(devList, &devInfoData, NULL, 0, &deviceIdRequiredLength) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER) && (deviceIdRequiredLength > deviceIdLength))
{
wchar_t *newMemory = (wchar_t*)realloc(deviceID, deviceIdRequiredLength * sizeof(wchar_t));
if (newMemory)
{
deviceID = newMemory;
deviceIdLength = deviceIdRequiredLength;
}
}
if (SetupDiGetDeviceInstanceIdW(devList, &devInfoData, deviceID, deviceIdLength, NULL))
{
wchar_t *vendorIdString = wcsstr(deviceID, L"VID_"), *productIdString = wcsstr(deviceID, L"PID_");
if (vendorIdString && productIdString)
{
serialNumberString = (wchar_t*)malloc(128*sizeof(wchar_t));
vendorID = wcstoul(vendorIdString + 4, NULL, 16);
productID = wcstoul(productIdString + 4, NULL, 16);
wchar_t *serialEnd = wcspbrk(productIdString + 9, L"\\\r\n");
if (serialEnd)
*serialEnd = L'\0';
wcscpy_s(serialNumberString, 128, productIdString + 9);
}
}
// Fetch the corresponding COM port for this device
DWORD comPortLength = 0;
wchar_t *comPort = NULL, *comPortString = NULL;
char friendlyNameMemory = 0, portDescriptionMemory = 0;
HKEY key = SetupDiOpenDevRegKey(devList, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (key != INVALID_HANDLE_VALUE)
{
if ((RegQueryValueExW(key, L"PortName", NULL, NULL, NULL, &comPortLength) == ERROR_SUCCESS) && (comPortLength < 32))
{
comPortLength += sizeof(wchar_t);
comPort = (wchar_t*)malloc(comPortLength);
if (comPort && (RegQueryValueExW(key, L"PortName", NULL, NULL, (LPBYTE)comPort, &comPortLength) == ERROR_SUCCESS))
comPortString = (comPort[0] == L'\\') ? (wcsrchr(comPort, L'\\') + 1) : comPort;
}
RegCloseKey(key);
}
if (!comPortString || wcsstr(comPortString, L"LPT"))
{
if (comPort)
free(comPort);
if (serialNumberString)
free(serialNumberString);
continue;
}
// Fetch the friendly name for this device
DWORD friendlyNameLength = 0;
wchar_t *friendlyNameString = NULL;
SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_FRIENDLYNAME, NULL, NULL, 0, &friendlyNameLength);
if (friendlyNameLength && (friendlyNameLength < 256))
{
friendlyNameLength += sizeof(wchar_t);
friendlyNameString = (wchar_t*)malloc(friendlyNameLength);
if (!friendlyNameString || !SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_FRIENDLYNAME, NULL, (BYTE*)friendlyNameString, friendlyNameLength, NULL))
{
if (friendlyNameString)
free(friendlyNameString);
friendlyNameString = comPortString;
friendlyNameLength = comPortLength;
}
else
{
friendlyNameMemory = 1;
friendlyNameString[(friendlyNameLength / sizeof(wchar_t)) - 1] = 0;
}
}
else
{
friendlyNameString = comPortString;
friendlyNameLength = comPortLength;
}
// Fetch the manufacturer of this device
DWORD manufacturerLength = 0;
wchar_t *manufacturerString = NULL;
SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_MFG, NULL, NULL, 0, &manufacturerLength);
if (manufacturerLength && (manufacturerLength < 256))
{
manufacturerLength += sizeof(wchar_t);
manufacturerString = (wchar_t*)malloc(manufacturerLength);
if (!manufacturerString || !SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_MFG, NULL, (BYTE*)manufacturerString, manufacturerLength, NULL))
{
if (manufacturerString)
{
free(manufacturerString);
manufacturerString = NULL;
}
}
}
// Fetch the device driver loaded for this device
wchar_t *driverString = SetupDiGetSelectedDriverW(devList, &devInfoData, &driverInfoData) ? driverInfoData.Description : NULL;
// Fetch the bus-reported device description
DWORD portDescriptionLength = 0;
wchar_t *portDescriptionString = NULL;
if (SetupDiGetDevicePropertyW && (SetupDiGetDevicePropertyW(devList, &devInfoData, &DEVPKEY_Device_BusReportedDeviceDesc, &devInfoPropType, NULL, 0, &portDescriptionLength, 0) || (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) && portDescriptionLength && (portDescriptionLength < 256))
{
portDescriptionLength += sizeof(wchar_t);
portDescriptionString = (wchar_t*)malloc(portDescriptionLength);
if (!portDescriptionString || !SetupDiGetDevicePropertyW(devList, &devInfoData, &DEVPKEY_Device_BusReportedDeviceDesc, &devInfoPropType, (BYTE*)portDescriptionString, portDescriptionLength, NULL, 0))
{
if (portDescriptionString)
free(portDescriptionString);
portDescriptionString = friendlyNameString;
portDescriptionLength = friendlyNameLength;
}
else
{
portDescriptionMemory = 1;
portDescriptionString[(portDescriptionLength / sizeof(wchar_t)) - 1] = 0;
}
}
else
{
portDescriptionString = friendlyNameString;
portDescriptionLength = friendlyNameLength;
}
// Fetch the physical location for this device
DWORD locationLength = 0;
wchar_t *locationString = NULL;
unsigned long busNumber = 0, hubNumber = 0, portNumber = 0;
if (!SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_BUSNUMBER, NULL, (BYTE*)&busNumber, sizeof(busNumber), NULL))
busNumber = 0;
if (!SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_ADDRESS, NULL, (BYTE*)&portNumber, sizeof(portNumber), NULL))
portNumber = 0;
SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_LOCATION_INFORMATION, NULL, NULL, 0, &locationLength);
if (locationLength && (locationLength < 256))
{
locationLength += sizeof(wchar_t);
locationString = (wchar_t*)malloc(locationLength);
if (locationString && SetupDiGetDeviceRegistryPropertyW(devList, &devInfoData, SPDRP_LOCATION_INFORMATION, NULL, (BYTE*)locationString, locationLength, NULL))
{
locationString[(locationLength / sizeof(wchar_t)) - 1] = 0;
if (wcsstr(locationString, L"Hub_#"))
hubNumber = _wtoi(wcschr(wcsstr(locationString, L"Hub"), L'#') + 1);
if (portNumber == -1)
{
if (wcsstr(locationString, L"Port_#"))
{
wchar_t *portString = wcschr(wcsstr(locationString, L"Port"), L'#') + 1;
if (portString)
{
wchar_t *end = wcschr(portString, L'.');
if (end)
*end = L'\0';
portNumber = _wtoi(portString);
}
}
else if (wcsstr(locationString, L"Port_") && wcschr(locationString, L'-') && wcschr(locationString, L']'))
{
wchar_t *portString = wcschr(wcsstr(locationString, L"Port"), L'_') + 1;
if (portString)
{
wchar_t *end = wcschr(portString, L' ');
if (end)
*end = L'\0';
portNumber = _wtoi(portString);
if (end)
*end = L' ';
}
if (hubNumber == -1)
{
wchar_t *hubString = wcsrchr(locationString, L'-') + 1;
if (hubString)
{
wchar_t *end = wcschr(hubString, L']');
if (end)
*end = L'\0';
hubNumber = _wtoi(hubString);
if (end)
*end = L']';
}
}
if (!serialNumberString && wcschr(locationString, L'[') && wcschr(locationString, L']'))
{
serialNumberString = (wchar_t*)malloc(32*sizeof(wchar_t));
wchar_t *serialBegin = wcschr(locationString, L'[') + 1;
wchar_t *serialEnd = wcspbrk(serialBegin, L"-]");
*serialEnd = L'\0';
wcscpy_s(serialNumberString, 32, serialBegin);
}
}
}
}
if (locationString)
free(locationString);
}
locationString = (wchar_t*)malloc(32*sizeof(wchar_t));
if (locationString)
_snwprintf_s(locationString, 32, _TRUNCATE, L"%lu-%lu.%lu", busNumber, hubNumber, portNumber);
else
{
free(comPort);
if (serialNumberString)
free(serialNumberString);
if (manufacturerString)
free(manufacturerString);
if (friendlyNameMemory)
free(friendlyNameString);
if (portDescriptionMemory)
free(portDescriptionString);
continue;
}
// Check if port is already enumerated
serialPort *port = fetchPort(&serialPorts, comPortString);
if (port)
replaceDetails(port, friendlyNameString, portDescriptionString, locationString, serialNumberString ? serialNumberString : L"Unknown", manufacturerString ? manufacturerString : L"Unknown", driverString ? driverString : L"Unknown", vendorID, productID);
else
pushBack(&serialPorts, comPortString, friendlyNameString, portDescriptionString, locationString, serialNumberString ? serialNumberString : L"Unknown", manufacturerString ? manufacturerString : L"Unknown", driverString ? driverString : L"Unknown", vendorID, productID);
// Clean up memory and reset device info structure
free(comPort);
free(locationString);
if (serialNumberString)
free(serialNumberString);
if (manufacturerString)
free(manufacturerString);
if (friendlyNameMemory)
free(friendlyNameString);
if (portDescriptionMemory)
free(portDescriptionString);
devInfoData.cbSize = sizeof(devInfoData);
}
SetupDiDestroyDeviceInfoList(devList);
}
}
// Attempt to locate any FTDI-specified port descriptions
HINSTANCE ftdiLibInstance = LoadLibrary(TEXT("ftd2xx.dll"));
if (ftdiLibInstance != NULL)
{
FT_OpenFunction FT_Open = (FT_OpenFunction)GetProcAddress(ftdiLibInstance, "FT_Open");
FT_CloseFunction FT_Close = (FT_CloseFunction)GetProcAddress(ftdiLibInstance, "FT_Close");
FT_CreateDeviceInfoListFunction FT_CreateDeviceInfoList = (FT_CreateDeviceInfoListFunction)GetProcAddress(ftdiLibInstance, "FT_CreateDeviceInfoList");
FT_GetDeviceInfoListFunction FT_GetDeviceInfoList = (FT_GetDeviceInfoListFunction)GetProcAddress(ftdiLibInstance, "FT_GetDeviceInfoList");
FT_EEPROM_ReadFunction FT_EEPROM_Read = (FT_EEPROM_ReadFunction)GetProcAddress(ftdiLibInstance, "FT_EEPROM_Read");
unsigned char allowOpenForEnumeration = (*env)->GetStaticBooleanField(env, serialCommClass, allowOpenForEnumerationField);
if (FT_CreateDeviceInfoList && FT_GetDeviceInfoList && FT_EEPROM_Read)
{
DWORD numDevs;
if ((FT_CreateDeviceInfoList(&numDevs) == FT_OK) && (numDevs > 0))
{
FT_DEVICE_LIST_INFO_NODE *devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);
if (devInfo && (FT_GetDeviceInfoList(devInfo, &numDevs) == FT_OK))
{
for (int i = 0; i < numDevs; ++i)
{
// Determine if the port is currently enumerated and already open
char isOpen = ((devInfo[i].Flags & FT_FLAGS_OPENED) || (devInfo[i].SerialNumber[0] == 0)) ? 1 : 0;
if (!isOpen)
for (int j = 0; j < serialPorts.length; ++j)
if ((memcmp(serialPorts.ports[j]->ftdiSerialNumber, devInfo[i].SerialNumber, sizeof(serialPorts.ports[j]->ftdiSerialNumber)) == 0) && (serialPorts.ports[j]->handle != INVALID_HANDLE_VALUE))
{
serialPorts.ports[j]->enumerated = 1;
isOpen = 1;
break;
}
// Update the port description if not already open
const int comPortLength = 16;
wchar_t *comPort = (wchar_t*)malloc(comPortLength);
devInfo[i].Description[sizeof(devInfo[i].Description)-1] = 0;
devInfo[i].SerialNumber[sizeof(devInfo[i].SerialNumber)-1] = 0;
if (!isOpen && comPort && getPortPathFromSerial(comPort, comPortLength, devInfo[i].SerialNumber))
{
// Check if actually connected and present in the port list
for (int j = 0; j < serialPorts.length; ++j)
if ((wcscmp(serialPorts.ports[j]->portPath + 4, comPort) == 0) && strlen(devInfo[i].Description))
{
// Check whether we are allowed to open the port to complete enumeration
unsigned char successfullyEnumerated = 0;
if (allowOpenForEnumeration)
{
// Open the port and read its configuration from EEPROM
FT_HANDLE ftHandle;
FT_EEPROM_HEADER ftEepromHeader = { .deviceType = devInfo[i].Type };
char manufacturer[64], manufacturerId[64], description[64], serialNumber[64];
if (FT_Open(0, &ftHandle) == FT_OK)
{
switch (devInfo[i].Type)
{
case FT_DEVICE_2232C:
{
FT_EEPROM_2232 ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
case FT_DEVICE_232R:
{
FT_EEPROM_232R ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
case FT_DEVICE_2232H:
{
FT_EEPROM_2232H ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
case FT_DEVICE_4232H:
{
FT_EEPROM_4232H ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
case FT_DEVICE_232H:
{
FT_EEPROM_232H ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
case FT_DEVICE_X_SERIES:
{
FT_EEPROM_X_SERIES ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
default:
{
FT_EEPROM_232B ftDeviceHeader = { .common = ftEepromHeader };
successfullyEnumerated = (FT_EEPROM_Read(ftHandle, &ftDeviceHeader, sizeof(ftDeviceHeader), manufacturer, manufacturerId, description, serialNumber) == FT_OK);
break;
}
}
FT_Close(ftHandle);
}
// Update port details if enumeration was successful
if (successfullyEnumerated)
{
// Update the port description
serialPorts.ports[j]->enumerated = 1;
size_t descLength = 8 + strlen(description);
wchar_t *newMemory = (wchar_t*)realloc(serialPorts.ports[j]->portDescription, descLength*sizeof(wchar_t));
if (newMemory)
{
serialPorts.ports[j]->portDescription = newMemory;
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, description, -1, serialPorts.ports[j]->portDescription, descLength);
}
// Update the port serial number
size_t serialNumLength = 1 + strlen(serialNumber);
newMemory = (wchar_t*)realloc(serialPorts.ports[j]->serialNumber, serialNumLength*sizeof(wchar_t));
if (newMemory)
{
serialPorts.ports[j]->serialNumber = newMemory;
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, serialNumber, -1, serialPorts.ports[j]->serialNumber, serialNumLength);
}
// Update the port manufacturer
size_t manufacturerLength = 1 + strlen(manufacturer);
newMemory = (wchar_t*)realloc(serialPorts.ports[j]->manufacturer, manufacturerLength*sizeof(wchar_t));
if (newMemory)
{
serialPorts.ports[j]->manufacturer = newMemory;
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, manufacturer, -1, serialPorts.ports[j]->manufacturer, manufacturerLength);
}
}
}
// Take what we can get if unable to enumerate by opening the port
if (!successfullyEnumerated)
{
// Update the port description
serialPorts.ports[j]->enumerated = 1;
size_t descLength = 8 + strlen(devInfo[i].Description);
wchar_t *newMemory = (wchar_t*)realloc(serialPorts.ports[j]->portDescription, descLength*sizeof(wchar_t));
if (newMemory)
{
serialPorts.ports[j]->portDescription = newMemory;
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, devInfo[i].Description, -1, serialPorts.ports[j]->portDescription, descLength);
}
// Update the port serial number
size_t serialNumLength = 1 + strlen(devInfo[i].SerialNumber);
newMemory = (wchar_t*)realloc(serialPorts.ports[j]->serialNumber, serialNumLength*sizeof(wchar_t));
if (newMemory)
{
serialPorts.ports[j]->serialNumber = newMemory;
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, devInfo[i].SerialNumber, -1, serialPorts.ports[j]->serialNumber, serialNumLength);
}
memcpy(serialPorts.ports[j]->ftdiSerialNumber, devInfo[i].SerialNumber, sizeof(serialPorts.ports[j]->ftdiSerialNumber));
}
}
}
if (comPort)
free(comPort);
}
}
if (devInfo)
free(devInfo);
}
}
FreeLibrary(ftdiLibInstance);
}
// Attempt to locate any non-registered virtual serial ports (e.g., from VSPE)
HKEY key, paramKey;
DWORD keyType, numValues, maxValueLength, maxComPortLength;
if ((RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) &&
(RegQueryInfoKeyW(key, NULL, NULL, NULL, NULL, NULL, NULL, &numValues, &maxValueLength, &maxComPortLength, NULL, NULL) == ERROR_SUCCESS))
{
// Allocate memory
++maxValueLength;
++maxComPortLength;
WCHAR *valueName = (WCHAR*)malloc(maxValueLength*sizeof(WCHAR));
WCHAR *comPort = (WCHAR*)malloc(maxComPortLength*sizeof(WCHAR));
// Iterate through all COM ports
for (DWORD i = 0; i < numValues; ++i)
{
// Get serial port name and COM value
DWORD valueLength = maxValueLength;
DWORD comPortLength = maxComPortLength;
memset(valueName, 0, valueLength*sizeof(WCHAR));
memset(comPort, 0, comPortLength*sizeof(WCHAR));
if ((RegEnumValueW(key, i, valueName, &valueLength, NULL, &keyType, (BYTE*)comPort, &comPortLength) == ERROR_SUCCESS) && (keyType == REG_SZ))
{
// Set port name and description
wchar_t* comPortString = (comPort[0] == L'\\') ? (wcsrchr(comPort, L'\\') + 1) : comPort;
wchar_t* friendlyNameString = wcsrchr(valueName, L'\\') ? (wcsrchr(valueName, L'\\') + 1) : valueName;
// Add new SerialComm object to vector if it does not already exist
serialPort *port = fetchPort(&serialPorts, comPortString);
if (port)
port->enumerated = 1;
else
pushBack(&serialPorts, comPortString, friendlyNameString, L"Virtual Serial Port", L"X-X.X", L"Unknown", L"Unknown", L"Unknown", -1, -1);
}
}
// Clean up memory
free(valueName);
free(comPort);
RegCloseKey(key);
}
// Clean up memory
if (deviceID)
free(deviceID);
// Remove all non-enumerated ports from the serial port listing
for (int i = 0; i < serialPorts.length; ++i)
if (!serialPorts.ports[i]->enumerated)
{
removePort(&serialPorts, serialPorts.ports[i]);
i--;
}
portsEnumerated = 1;
}