Как получить дружественное имя COM-порта в Windows?

У меня есть модем GSM, подключенный через USB. Модем создает 2 последовательных порта. Первый автоматически подключается к модему, второй отображается в диспетчере устройств как "HUAWEI Mobile Connect - интерфейс ПК для ПК 3G (COM6)"

Второй порт используется для получения важной информации от модема, например, качества сигнала; отправлять и получать текстовые сообщения; и целый ряд других функций.

Я пишу приложение, которое завершает некоторые функции, предоставляемые вторым портом. То, что мне нужно, - это верный метод пожарной сигнализации, который указывает, какой COM-порт является запасным. Итерирование портов и проверка ответа на "ATE0" недостаточны. Порт модема, как правило, имеет более низкий номер, и когда соединение с коммутируемым соединением неактивно, оно будет отвечать на "ATE0" так же, как и второй порт.

То, что я собирался сделать, - это итерация портов и проверка их дружественного имени, как показано в диспетчере устройств. Таким образом, я могу связать порт в своем приложении с портом с надписью "HUAWEI Mobile Connect - интерфейс интерфейса ПК для ПК (COM6)" в диспетчере устройств. Я только что не нашел никакой информации, которая позволит мне получить это имя программно.

Ответ 1

Давным-давно я написал утилиту для клиента, чтобы сделать именно это, но для GPS, а не для модема.

Я только что посмотрел на него, и биты, которые могут быть полезны, - это:

    GUID guid = GUID_DEVCLASS_PORTS;

SP_DEVICE_INTERFACE_DATA interfaceData;
ZeroMemory(&interfaceData, sizeof(interfaceData));
interfaceData.cbSize = sizeof(interfaceData);

SP_DEVINFO_DATA devInfoData;
ZeroMemory(&devInfoData, sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);

if(SetupDiEnumDeviceInfo(
    hDeviceInfo,            // Our device tree
    nDevice,            // The member to look for
    &devInfoData
    ))
{
    DWORD regDataType;

    BYTE hardwareId[300];
    if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, hardwareId, sizeof(hardwareId), NULL))
    {
...

(Вы вызываете этот бит в цикле с приращением nDevice)

а затем

BYTE friendlyName[300];
        if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, friendlyName, sizeof(friendlyName), NULL))
        {
            strFriendlyNames += (LPCTSTR)friendlyName;
            strFriendlyNames += '\n';
        }

который находит имя устройства.

Надеюсь, это поможет вам в правильном направлении.

Ответ 2

После того, как вы определили устройство последовательного порта, вы хотите (взглянув на его дружественное имя, проверив его родительское устройство и т.д.), правильный способ получить имя порта, вероятно, будет следующим:

  • invoke SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ), чтобы получить HKEY к так называемому ключу устройства
  • запросить этот раздел реестра для значения REG_SZ "Имя порта"
  • не забудьте закрыть HKEY:)

Однако для этого может потребоваться так много взаимодействий на С#, что это даже не смешно, поэтому я не обвиняю вас, если вы будете придерживаться решения синтаксического анализа строк.

Ответ 3

Информация, представленная Will Dean, была наиболее полезной. Это код, который в конечном итоге работал у меня. Все в классе PInvoke было принято дословно из http://www.pinvoke.net. Мне пришлось изменить тип данных здесь или там, чтобы заставить его работать (например, при использовании перечисления вместо uint), но это должно быть легко выяснить.

internal static string GetComPortByDescription(string Description)
{
    string Result = string.Empty;
    Guid guid = PInvoke.GUID_DEVCLASS_PORTS;
    uint nDevice = 0;
    uint nBytes = 300;
    byte[] retval = new byte[nBytes];
    uint RequiredSize = 0;
    uint PropertyRegDataType = 0;

    PInvoke.SP_DEVINFO_DATA devInfoData = new PInvoke.SP_DEVINFO_DATA();
    devInfoData.cbSize = Marshal.SizeOf(typeof(PInvoke.SP_DEVINFO_DATA));

    IntPtr hDeviceInfo = PInvoke.SetupDiGetClassDevs(
        ref guid, 
        null, 
        IntPtr.Zero, 
        PInvoke.DIGCF.DIGCF_PRESENT);

    while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))
    {
        if (PInvoke.SetupDiGetDeviceRegistryProperty(
                hDeviceInfo, 
                ref devInfoData, 
                PInvoke.SPDRP.SPDRP_FRIENDLYNAME,
                out PropertyRegDataType, 
                retval, 
                nBytes, 
                out RequiredSize))
        {
            if (System.Text.Encoding.Unicode.GetString(retval).Substring(0, Description.Length).ToLower() ==
                Description.ToLower())
            {
                string tmpstring = System.Text.Encoding.Unicode.GetString(retval);
                Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
            } // if retval == description
        } // if (PInvoke.SetupDiGetDeviceRegistryProperty( ... SPDRP_FRIENDLYNAME ...
    } // while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))

    PInvoke.SetupDiDestroyDeviceInfoList(hDeviceInfo);
    return Result;
}

Я думаю, что строка Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM")); немного неуклюжая, предложения по ее очистке будут оценены.

Спасибо за вашу помощь в этом вопросе. Без вас я все равно буду искать google.

Ответ 4

Рад, что это сработало.

Вы можете попробовать:

Regex.Match(tmpstring, @ "COM\s\d +" ). ToString()

для соответствия вашей строки.

Как точки стиля .NET, я бы добавил "using System.Text", и я бы не стал запускать имена локальных переменных с помощью капиталов, и если бы я чувствовал себя действительно добродетельным, я бы, вероятно, поместил SetupDiDestroyDeviceInfoList в окончательный {}.

Ответ 5

Версия на С++, основанная на ответе @Will Dean.

#include <windows.h>
#include <initguid.h>
#include <devguid.h>
#include <setupapi.h>

void enumerateSerialPortsFriendlyNames()
{
    SP_DEVINFO_DATA devInfoData = {};
    devInfoData.cbSize = sizeof(devInfoData);

    // get the tree containing the info for the ports
    HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,
                                               0,
                                               nullptr,
                                               DIGCF_PRESENT
                                               );
    if (hDeviceInfo == INVALID_HANDLE_VALUE)
    {
        return;
    }

    // iterate over all the devices in the tree
    int nDevice = 0;
    while (SetupDiEnumDeviceInfo(hDeviceInfo,            // Our device tree
                                 nDevice++,            // The member to look for
                                 &devInfoData))
    {
        DWORD regDataType;
        DWORD reqSize = 0;

        // find the size required to hold the device info
        SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
        BYTE hardwareId[reqSize > 1 ? reqSize : 1];
        // now store it in a buffer
        if (SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, hardwareId, sizeof(hardwareId), nullptr))
        {
            // find the size required to hold the friendly name
            reqSize = 0;
            SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
            BYTE friendlyName[reqSize > 1 ? reqSize : 1];
            // now store it in a buffer
            if (!SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, friendlyName, sizeof(friendlyName), nullptr))
            {
                // device does not have this property set
                memset(friendlyName, 0, reqSize > 1 ? reqSize : 1);
            }
            // use friendlyName here
        }
    }
}

Ответ 6

Используется метод, отправленный LiGenChen. Метод ComPortSetupAPISetupDiClassGuids дал лучшее время и дружественное имя.