Blog. Just Blog

Как получить список COM-портов на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Как получить список COM-портов на Ассемблере
Как получить список COM-портов на Ассемблере

COM-порт (последовательный порт) - это аппаратный интерфейс, предназначенный для последовательной передачи данных. С его помощью к устройству можно подключать внешние периферийные компоненты для обмена информацией или расширения функциональности основного оборудования. В современных системах чаще используется виртуальный COM-порт - программный интерфейс, эмулирующий поведение традиционного последовательного порта. Он позволяет приложениям обмениваться данными между собой или с виртуальными устройствами без необходимости в физическом оборудовании. Независимо от того, физический это порт или виртуальный, с точки зрения программного взаимодействия они выглядят одинаково. Поэтому в этой статье мы узнаем, как получить список доступных COM-портов с использованием Ассемблера.

Самый простой способ получить список всех COM-портов - обратиться к ветке реестра HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. В ней каждая запись содержит имя устройства (в качестве имени параметра) и соответствующее ему обозначение порта, например "COM3". Достаточно последовательно прочитать все значения из этого раздела, чтобы получить полный перечень доступных последовательных портов. Такой подход работает как для физических, так и для виртуальных COM-портов и не требует установки дополнительных драйверов или библиотек.
  1.         ; Открываем ключ HKLM\HARDWARE\DEVICEMAP\SERIALCOMM
  2.         invoke  RegOpenKeyEx,HKEY_LOCAL_MACHINE,\
  3.                 subkey,0,KEY_READ,hKey
  4.         cmp     eax,ERROR_SUCCESS
  5.         jne     loc_error
  6.  
  7.         ; Начать перебор с первого устройства
  8.         mov     [index],0
  9. loc_loop:
  10.         ; Очистить строки
  11.         invoke  RtlZeroMemory,devname,LEN_STRING
  12.         invoke  RtlZeroMemory,portname,LEN_STRING
  13.  
  14.         ; Нужно сбрасывать размеры буферов
  15.         mov     [devsize],LEN_STRING
  16.         mov     [portsize],LEN_STRING
  17.  
  18.         ; Читаем значение по индексу
  19.         invoke  RegEnumValue,[hKey],[index],\
  20.                 devname,devsize,\
  21.                 0,tmp,\
  22.                 portname,portsize
  23.  
  24.         ; Портов больше нет
  25.         cmp     eax,ERROR_NO_MORE_ITEMS
  26.         je      loc_done
  27.  
  28.         ; Проверяем на другие ошибки
  29.         cmp     eax,ERROR_SUCCESS
  30.         jne     loc_done
  31.  
  32.         ; devname -> драйвер, например, "Serial0" или типа такого
  33.         ; portname -> порт, например, "COM3" или "COM10"
  34.         ...
  35.  
  36.         ; Увеличиваем индекс
  37.         inc     [index]
  38.         jmp     loc_loop
  39. loc_done:
  40.         ; Закрываем ключ
  41.         invoke  RegCloseKey,[hKey]
  42.         ; Done
  43.         ...
  44.  
  45. loc_error:
  46.         ; ERROR
  47.         ...
В зависимости от задачи, можно остановиться и на этом. Однако если требуется получить расширенную информацию о COM-портах, понадобится использовать функции из SetupAPI. Но перед этим традиционная подготовка: набор необходимых структур, констант и GUID’ов, без которых работа с SetupAPI невозможна. Главное, не перепутать эти COM-порты с COM-объектами (Component Object Model), так как они не имеют друг с другом ничего общего.
  1. ; Структура SetupAPI
  2. struct SP_DEVINFO_DATA
  3.         cbSize      dd ?  ; Размер структуры в байтах
  4.         ClassGuid   rb 16 ; GUID класса устройства
  5.         DevInst     dd ?  ; Дескриптор экземпляра устройства
  6.         Reserved    dd ?  ; Зарезервировано
  7. ends
  8.  
  9. ; Константы SetupAPI
  10. DIGCF_PRESENT      = 0x00000002
  11. DIGCF_PROFILE      = 0x00000008
  12. SPDRP_HARDWAREID   = 0x00000001
  13. SPDRP_FRIENDLYNAME = 0x0000000C
  14.  
  15. ERROR_ACCESS_DENIED = 5
  16.  
  17. ; GUID {4D36E978-E325-11CE-BFC1-08002BE10318}
  18. GUID_DEVCLASS_PORTS \
  19.         dd 04D36E978h
  20.         dw 0E325h
  21.         dw 011CEh
  22.         db 0BFh, 0C1h, 008h, 000h, 02Bh, 0E1h, 003h, 018h
Теперь немного теории. Как уже упоминалось, для поиска COM-портов наиболее правильным подходом является использование SetupAPI. Сначала вызывается функция SetupDiGetClassDevs, в которую передается GUID, соответствующий классу COM-портов. Затем с помощью SetupDiEnumDeviceInfo перебирается список устройств, предварительно определив их количество. После этого для каждого устройства можно получить подробную информацию, вызвав SetupDiGetDeviceRegistryProperty: указав нужный индекс свойства, мы извлекаем соответствующие данные.

Нас интересует свойство SPDRP_FRIENDLYNAME, то есть "человеческое" имя устройства, отображаемое в системе. Следует помнить, что SetupAPI является универсальным интерфейсом и работает со всеми типами устройств, а не только с COM-портами. Поэтому в SPDRP_FRIENDLYNAME будет содержаться обобщенное описание, например: "USB Serial Device (COM3)" или "Prolific USB-to-Serial Comm Port (COM12)". Чтобы извлечь именно имя COM-порта, потребуется дополнительно распарсить эту строку и выделить подстроку вида "COM3" или "COM12". При работе с COM-портами в коде важно учитывать следующее: если имя порта COM1-COM9, его можно указывать в виде простой строки, например "COM3". Однако начиная с COM10 и выше стандартное обозначение уже не работает напрямую. В таких случаях необходимо использовать расширенный синтаксис Windows, например "\\.\COM11". Этот формат сообщает системе, что вы обращаетесь к устройству через диспетчер устройств Windows, что позволяет корректно работать с портами, имеющими номер 10 и выше. Поэтому рекомендуется использовать единообразный формат \\.\COMxx для всех COM-портов, независимо от их номера. Это гарантирует совместимость, избавляет от необходимости проверять номер порта и предотвращает ошибки при работе с портами начиная с COM10 и выше.

Чтобы найти именно интересующее вас устройство, следует получить его Hardware ID, запросив свойство по индексу SPDRP_HARDWAREID. Затем можно отфильтровать весь список устройств, сравнивая Hardware ID с нужным значением. Такой подход позволяет однозначно идентифицировать требуемое устройство и получить соответствующее ему имя COM-порта, обеспечивая точное сопоставление между портом и физическим (или виртуальным) устройством.

Последним шагом станет проверка доступности порта: для этого нужно попытаться открыть системный путь вида \\.\COMx с помощью функции CreateFile. Результат вызова покажет, существует ли порт, свободен ли он, недоступен или уже используется другим приложением. Это позволяет убедиться, что выбранный COM-порт действительно можно использовать.

Теперь можно переходить к программированию. Собираем все вместе и посмотрим, что у нас получается.
  1.         ; Получаем список устройств класса Ports
  2.         invoke  SetupDiGetClassDevsA,GUID_DEVCLASS_PORTS,\
  3.                 0,0,DIGCF_PRESENT or DIGCF_PROFILE
  4.         cmp     eax,-1
  5.         je      loc_error
  6.  
  7.         mov     [hDevInfo],eax
  8.         mov     [SPDID.cbSize],sizeof.SP_DEVINFO_DATA
  9.  
  10.         ; Начать перебор с первого устройства
  11.         mov     [index],0
  12. loc_loop:
  13.         ; Перебираем устройства одно за другим
  14.         invoke  SetupDiEnumDeviceInfo,[hDevInfo],[index],SPDID
  15.         test    eax,eax
  16.         jz      loc_done
  17.  
  18.         ; Получаем Hardware ID
  19.         mov     byte [szHWID],0
  20.         invoke  SetupDiGetDeviceRegistryPropertyA,[hDevInfo],SPDID,\
  21.                 SPDRP_HARDWAREID,tmp,szHWID,LEN_BUFFER,NULL
  22.  
  23.         ; Может быть HardwareID пустая строка
  24.         cmp     byte [szHWID],0
  25.         jne     @f
  26.         ; Unknown
  27.         invoke  lstrcpy,szHWID,szUnknown
  28. @@:
  29.         ; Получаем Friendly Name
  30.         mov     byte [szFriendly],0
  31.         invoke  SetupDiGetDeviceRegistryPropertyA,[hDevInfo],SPDID,\
  32.                 SPDRP_FRIENDLYNAME,tmp,szFriendly,LEN_BUFFER,NULL
  33.  
  34.         ; Может быть FriendlyName пустая строка
  35.         cmp     byte [szFriendly],0
  36.         jne     @f
  37.  
  38.         ; Если имени нет,пропускаем
  39.         invoke  lstrcpy,szFriendly,szUnknown
  40.         invoke  lstrcpy,szFull,szUnknown
  41.         mov     ebx,szUnknown
  42.         jmp     loc_log
  43. @@:
  44.         ; Парсинг "COMx" из строки
  45.         mov     esi,szFriendly
  46.  
  47. loc_com_parse:
  48.         lodsb
  49.         or      al,al
  50.         jnz     @f
  51.  
  52.         ; Конец строки, COM не найден
  53.         invoke  lstrcpy,szFull,szUnknown
  54.         mov     ebx,szUnknown
  55.         jmp     loc_log
  56. @@:
  57.         dec     esi
  58.         ; Ищем сигнатуру "(COM"
  59.         cmp     dword [esi],'(COM'
  60.         jne     skip_char
  61.  
  62.         ; Нашли, ESI указывает на '('
  63.         inc     esi
  64.         ; Сдвигаем на начало имени порта
  65.         mov     edi,szPort
  66.  
  67. loc_copy:
  68.         ; Копируем до закрывающей скобки
  69.         lodsb
  70.         cmp     al,')'
  71.         je      loc_done_parse
  72.         or      al,al
  73.         jz      loc_done_parse
  74.         stosb
  75.         jmp     loc_copy
  76.  
  77. skip_char:
  78.         inc     esi
  79.         jmp     loc_com_parse
  80.  
  81. loc_done_parse:
  82.         ; Null-terminator
  83.         mov     al,0
  84.         stosb
  85.  
  86.         ; Формируем системный путь \\.\COMx
  87.         invoke  wsprintf,szFull,mask_com,szPort
  88.         add     esp,12
  89.  
  90.         ; Проверяем доступность порта
  91.         invoke  CreateFile,szFull,GENERIC_READ or GENERIC_WRITE,\
  92.                 0,NULL,OPEN_EXISTING,0,NULL
  93.  
  94.         mov     ebx,eax
  95.         cmp     eax,INVALID_HANDLE_VALUE
  96.         je      loc_check
  97.  
  98.         ; Порт свободен
  99.         mov     ebx,szFree
  100.         invoke  CloseHandle,ebx
  101.         jmp     loc_log
  102.  
  103. loc_check:
  104.         invoke  GetLastError
  105.         cmp     eax,ERROR_ACCESS_DENIED
  106.         je      loc_busy
  107.  
  108.         ; Ошибка (не занят, а что-то другое)
  109.         invoke  wsprintf,szStatus,mask_er,eax
  110.         add     esp,12
  111.         mov     ebx,szStatus
  112.         jmp     loc_log
  113.  
  114. loc_busy:
  115.         mov     ebx,szBusy
  116.  
  117. loc_log:
  118.         ; Вывод в лог
  119.  
  120.         ; szHWID -> данные Hardware ID
  121.         ; szFriendly -> название устройства
  122.         ; szFull -> полное название порта
  123.         ...
  124.  
  125. loc_next:
  126.         ; Увеличиваем индекс
  127.         inc     [index]
  128.         jmp     loc_loop
  129.  
  130. loc_done:
  131.         invoke  SetupDiDestroyDeviceInfoList,[hDevInfo]
  132.         ; Done
  133.         ...
  134.  
  135. loc_error:
  136.         ; Error
  137.         ...
В приложении примеры программ с исходными текстами, одна из них выводит полный список подключенных COM-портов, а вторая получает расширенную информацию о каждом из них.

Примеры программ с исходными текстами (FASM)Примеры программ с исходными текстами (FASM)

COM.Port.Demo.zip (7,470 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 357 | Комментариев: 8

Метки: Assembler

Комментарии

Отзывы посетителей сайта о статье
ManHunter (17.05.2026 в 20:25):
Да, именно так и должно быть. Это неведомая херня, которая никогда не будет правильно определяться и, соответственно, не будет работать как COM-порт. Большое спасибо за тестирование.
Maklen (16.05.2026 в 22:35):
ЦитатаИсходники и архив обновлен.

'''get_com.exe log
Device: \Device\Serial0
Port: COM1
-------------
Device: Winachsf0
Port: COM3
-------------
Done

'''get_info.exe log
Hardware ID: ACPI\VEN_PNP&DEV_0400
Friendly Name: Порт принтера (LPT1)
Port: Unknown
Status: Unknown
-------------
Hardware ID: ACPI\VEN_PNP&DEV_0501
Friendly Name: ?Последовательный порт (COM1)
Port: \\.\COM1
Status: Free
-------------
Done
ManHunter (15.05.2026 в 20:52):
Доработал. Будет показываться по крайней мере список устройств. Для странных устройств будет выдаваться "Unknown". Исходники и архив обновлен.
Maklen (15.05.2026 в 10:53):
Это старый https://www.dlink.ru/ru/products/11/186.html валялся под рукой. По TAPI выдал длинный лог.
ManHunter (15.05.2026 в 10:24):
Maklen, спасибо, теперь есть понимание. Осталось найти такой девайс :)
Проблема в том, что система возвращает поле FriendlyName как пустая строка, или оно есть в списке устройств как "Какой-то неведомой порт" без в строки "(COM3)" в названии. В тестовой программе get_info пропускает такие устройства. Я подумаю, что можно сделать.
Maklen (15.05.2026 в 08:19):
Win10x64
'''get_com.exe log
Get COM-port List
Device: \Device\Serial0
Port: COM1
-------------
Device: Winachsf0
Port: COM3
-------------
Done

'''get_info.exe log
Get COM-port List
Hardware ID: ACPI\VEN_PNP&DEV_0501
Friendly Name: Последовательный порт (COM1)
Port: \\.\COM1
Status: Free
-------------
Done
ManHunter (13.05.2026 в 13:47):
Слишком мало данных. Я так же могу сказать, что все прекрасно отображает и видит. Тестировал на Win7x86, Win7x64 и Win10x64, все работает. Пруф: https://ibb.co/5X549X3W
Maklen (12.05.2026 в 09:29):
get_info не отображает USB СОМ порт, get_com его видит..

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2026
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0033 сек.) / Память: 4.5 Mb
Наверх