понедельник, 23 апреля 2012 г.

Список COM-портов в Qt.

При программировании под Windows есть очень простая, можно сказать, стандартная задача - построение списка COM-портов и вывод списка в комбобокс.

Ниже приведу два варианта построения такого списка и объясню, почему с использованием только Qt это сделать невозможно.

Вариант 1. С использованием Qt. Не работающий.

Для работы с реестром Windows средствами Qt можно использовать класс QSettings, который позволяет работать не только с ini-файлами, но и с несколькими другими форматами хранения настроек. В этом случае функция построения списка компортов могла бы выглядеть так.


#include <QSettings.h>

QStringList getComPortList()
{
    QString path = "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM";
    QSettings settings(path, QSettings::NativeFormat);

    QStringList keys = settings.childKeys();
    QStringList list;

    for (int i = 0; i < keys.count(); i++)
    {
        QVariant var = settings.value(keys.at(i));
        list.append(var.toString());
    }

    return list;
}

Список ключей keys будет выглядеть примерно так: ["/Device/Serial0" "/Device/VCP0"]

Как можно заметить, Qt заменяет бэкслэши (backslash) на слэши (slash), т.к. использует бэкслэши для разделения подключей. Как гласит документация на QSettings:

Do not use slashes ('/' and '\') in section or key names; the backslash character is used to separate sub keys (see below). On windows '\' are converted by QSettings to '/', which makes them identical.

Поэтому на Qt невозможно прочитать ключи с бэкслэшами --> пичалька. Приведенный вариант работать не будет.

Вариант 2. С использованием WINAPI. Готов к использованию.


#include <windows.h>
QStringList getComPortList()
{
    QStringList list;
    HKEY hKey = 0;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
    {
        DWORD cbMaxValueNameLen, cbMaxValueLen;

        if (RegQueryInfoKey(hKey, // дескриптор ключа
                            NULL, // адрес буфера для имени класса
                            NULL, // адрес размера буфер для имени класса
                            NULL, // зарезервировано
                            NULL, // адрес буфера для количества подключей
                            NULL, // адрес буфера для наибольшего размера имени подключа
                            NULL, // адрес буфера для наибольшего размера имени класса
                            NULL, // адрес буфера для количества вхождений значений
                            &cbMaxValueNameLen,       // адрес буфера для наибольшего размера имени значения
                            &cbMaxValueLen, // адрес буфера для наибольшего размера данных значения
                            NULL,       // адрес буфера для длины дескриптора безопасности
                            NULL        // адрес буфера для получения времени последней записи
            ) == ERROR_SUCCESS)
        {
            cbMaxValueNameLen++;
            cbMaxValueLen++;
            TCHAR ValueName[cbMaxValueNameLen];
            DWORD Type = REG_SZ;
            BYTE Data[cbMaxValueLen];
            char temp[cbMaxValueLen];
            DWORD cbValueNameLen, cbValueLen;

            DWORD index = 0;
            cbValueNameLen = cbMaxValueNameLen;
            cbValueLen = cbMaxValueLen;
            while (RegEnumValue(hKey,  // дескриптор запрашиваемого ключа
                            index++,  // индекс запрашиваемого значения
                            ValueName, // адрес буфера для имени значения
                            &cbValueNameLen, // адрес переменной с  размером буфера для имени значения
                            NULL, // зарезервировано
                            &Type,  // адрес переменной с типом данных
                            Data,  // адрес буфера для данных значения
                            &cbValueLen  // адрес переменной с  размером буфера для данных
                   ) == ERROR_SUCCESS)
            {
                wcstombs(temp, (WCHAR*)Data, cbValueLen/2);
                list.append(temp);
                cbValueNameLen = cbMaxValueNameLen;
                cbValueLen = cbMaxValueLen;
            }
        }
        RegCloseKey(hKey);
    }

    return list;
}

Это полностью готовый вариант. Можно использовать :)

3 комментария:

  1. Приятно когда можно тупо скопипастить и работает из коробки, а потом по живому разбираться. Спасибо

    ОтветитьУдалить
    Ответы
    1. Андрей, сейчас, когда QSerial уже есть в Qt5, всё гораздо проще.

      Удалить