Библиотека com_ports

com_ports — это небольшая статическая библиотека на языке С++, над которой я работал последние несколько месяцев. Она предназначена для работы с именами доступных в системе COM портов (RS-232).

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

Предыстория

На предыдущей работе мне часто приходилось иметь дело с COM портами (RS-232). В то время я занимался разработкой программного обеспечения для проверки устройств, которые подключались к персональному компьютеру через COM.

Тогда же я написал несколько статей, в которых делился своим опытом работы с этими портами. Одна из них — Получение списка доступных COM портов [1]. В ней показано как получить список доступных COM портов путём анализа содержимого реестра Windows. Судя по комментариям, она оказалась востребованной. Многие стали копировать это решение (ни разу не против).

Несколько месяцев назад (14.11.2025) в комментариях к этой статье задали вопрос о получении дружелюбных имён. С такой задачей я никогда не сталкивался, но нашел обсуждение этого вопроса на StackOverflow [2]. Эту ссылку я приложил к своему ответу автору вопроса.

Спустя некоторое время автор вопроса, Александр Вячеславович Рощупкин, прислал мне на почту рабочее решение, которое у него получилось, и ссылку на свой блог, в котором оно опубликовано [3]. Я подумал, неплохо было бы тоже опубликовать пост на эту тему. Тогда же я вспомнил о другой своей статье о COM портах — О важности префикса «\\\\?\\\\» [4].

И тут мне пришла мысль, а почему бы не объединить это всё ([1], [3] и [4]) в одну небольшую библиотеку? Так появилась идея библиотеки com_ports. В пользу неё был еще один аргумент.

Вот уже несколько лет я вынашиваю идею крупной библиотеки (точнее набора библиотек) для C++. Я обдумываю разные аспекты этой библиотеки, и одним из важных аспектов является её сборка средствами CMake. К сожалению, мои знания CMake оставляют желать лучшего. Исходя из этого, библиотека com_ports могла бы выступить в роли подопытного кролика, на котором, во-первых, можно безболезненно потренироваться в работе с CMake, и, во-вторых, проверить и отладить ряд идей, которые, возможно в будущем войдут в состав другой моей библиотеки (если она появится).

Все эти обстоятельства и побудили меня создать библиотеку com_ports. Это был интересный путь, на котором пришлось столкнуться с рядом интересных и порой нетривиальных решений. Я подробно расскажу о них в своих следующих постах. Вполне возможно, что они неправильные и существуют другие, более правильные и простые способы достижения тех же целей.

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

Сама библиотека уже находится в открытом доступе. Вот ссылка на её репозиторий: https://gitflic.ru/project/sergey_norseev/com_ports [5].

Прежде чем перейти к описанию самой библиотеки нужно сделать ряд замечаний.

  1. Библиотека не является кроссплатформенной. Она рассчитана только на операционные системы семейства Windows. Это объясняется тем, что весь её функционал построен на функциях Windows API. К сожалению, у меня нет опыта работы с COM портами в среде Linux.
  2. Библиотека написана на языке C++. Минимально необходимый стандарт — C++17.
  3. Библиотека не предоставляет возможностей по работе с самими COM портами (их открытие, настройка, запись в порт, чтение из порта и т.д.). Она работает только с их именами. Точнее говоря она предоставляет ряд функций, позволяющих получить список имён доступных портов в том или ином виде. Объясняется это двумя причинами. Во-первых, уже более 4 лет (с момента моего трудоустройства в нынешнюю компанию) я никак не взаимодействую с COM портами. Более того у меня нет ни одного устройства где был бы реальный COM порт. Поэтому у меня нет возможности провести полноценную проверку и отладку работы с портом. Да, можно использовать виртуальные порты, но это менее надежный вариант по сравнению с реальным оборудованием. Во-вторых, я не хотел слишком усложнять библиотеку. Напомню, сейчас для меня это скорее тестовый проект. Тем не менее, если вдруг он окажется востребованным, я готов рассмотреть вопрос о расширении функционала.
  4. Библиотека зависит от библиотеки setupapi. Последняя обычно уже входит в состав используемой Вами IDE.
  5. Тестирование и отладка библиотеки осуществлялись только в виртуальной машине (Virtual Box). При этом сами порты тоже были виртуальными. Поэтому я не могу гарантировать правильную работу библиотеки во всех случаях и на любом оборудовании.

Ниже я приведу краткое описание самой библиотеки. Подробности её внутреннего устройства будут приведены в следующих статьях.

Содержимое библиотеки

Скачать исходные тексты библиотеки можно из её репозитория [5]. Сделать это можно либо со страницы репозитория (в виде архива выбранного типа, его нужно будет распаковать), либо с помощью утилиты Git. Ниже приводится пример команды на скачивание исходных текстов библиотеки с помощью утилиты Git.

git clone git@gitflic.ru:sergey_norseev/com_ports.git .

Если всё сделано правильно, то в текущем каталоге Вы должны увидеть следующие подкаталоги и файлы.

  • cmake/ содержит вспомогательные CMake файлы.
  • doc/ содержит файлы документации к библиотеке в формате reStructuredText.
  • examples/ содержит исходные тексты примеров приложений, использующих библиотеку.
  • include/ содержит заготовку заголовочного файла, который должен использоваться при работе с библиотекой.
  • src/ содержит исходные тексты библиотеки на языке C++.
  • CMakeLists.txt списковый файл (термин взят из книги [6]) для конфигурирования и сборки библиотеки средствами CMake.
  • LICENSE текст лицензии MIT [7].
  • LICENSE_RUS неофициальный перевод текста лицензии MIT на русский язык.
  • README.md файл README с кратким описанием библиотеки в формате Markdown.

В простейшем случае Вы можете скопировать файлы из подкаталогов include/ и src/ в свой проект и подкорректировать их нужным образом. Но я не рекомендую так делать.

Во-первых, заголовочный файл в подкаталоге include/ является лишь заготовкой. Для полноценного использования в нём нужно прописать все макросы с версиями (раскрыть строки вида «@…@»). Это делается средствами CMake.

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

Компиляцию библиотеки и примеров к ней я опишу в следующих постах. Если Вы уже знакомы с CMake, то информации в файле README.md будет достаточно для её сборки. Более подробная информация приведена в документации к библиотеке (подкаталог doc/). Если же Вы не знакомы с CMake, дождитесь моих следующих постов.

Теперь поговорим о том, какие возможности предоставляет библиотека.

API библиотеки

В текущей реализации библиотека экспортирует 6 макросов (о них я расскажу в одной из следующих статей), 3 типа данных и 8 функций. Все экспортируемые типы данных и функции находятся в пространстве имён com_ports.

Ниже приводится краткое описание экспортируемых типов данных.

  • Exception — класс исключения, которое бросается функциями библиотеки в том случае, если та или иная вызываемая функция Windows API завершается ошибкой. Класс Exception является потомком класса std::exception. Текст описания ошибки имеет кодировку UTF-8.
  • Names — коллекция строк в кодировке Unicode. По сути псевдоним для std::vector<std::wstring>.
  • NamesANSI — коллекция строк в произвольной однобайтовой кодировке или в кодировке UTF-8. По сути псевдоним для std::vector<std::string>.

Все экспортируемые функции имеют 2 версии. Одну для Unicode (имя функции не имеет суффикса) и одну для произвольной однобайтовой кодировки или UTF-8 (имя функции имеет суффикс _ansi). Функции для однобайтовой кодировки имеют один необязательный числовой параметр, который задает желаемую кодировку символов. Если он не указан, строки возвращаются в кодировке Win1251.

Ниже приводится краткое описание экспортируемых функций.

inline std::wstring get_prefix();
inline std::string get_prefix_ansi();

Возвращают префикс "\\?\\". Я рассказывал о нём в статье О важности префикса «\\\\?\\\\» [4].

Поскольку префикс содержит только ASCII символы, то он записывается одинаково во всех однобайтовых кодировках и в UTF-8. Поэтому функция get_prefix_ansi не имеет входного параметра.

Names get_names();
NamesANSI get_names_ansi(unsigned int = 1251);

Возвращают коллекцию имён доступных COM портов. Например, ["COM1","COM2"]. Эти функции аналогичны решению, приводимому мной в статье Получение списка доступных COM портов [1], но с обработкой ошибок.

Names get_names_with_prefix();
NamesANSI get_names_with_prefix_ansi(unsigned int = 1251);

Возвращают коллекцию имён доступных COM портов, содержащих префикс "\\?\\". Например, ["\\?\\COM1","\\?\\COM2"]. Используют функции get_names и get_prefix.

Names get_friendly_names();
NamesANSI get_friendly_names_ansi(unsigned int = 1251);

Возвращают коллекцию дружелюбных имён доступных COM портов. Например, ["SerialTool - Virtual COM Port VCPA0 (COM3)"]. Аналогичны решению, реализованному Александром Рощупкиным [2,3], но с обработкой ошибок.

Пример использования

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

#include <com_ports/com_ports.hpp>

#include <iostream>

#include <windows.h>

int main()
{
    try
    {
        ::std::wcout << L"COM ports:\n";

        auto names = com_ports::get_names();

        for(const auto &name : names)
            ::std::wcout << name << L'\n';

        ::std::wcout << L"\nCOM ports with prefix:\n";

        auto names_with_prefix = com_ports::get_names_with_prefix();

        for(const auto &name : names_with_prefix)
            ::std::wcout << name << L'\n';

        ::std::wcout << L"\nFriendly names:\n";

        auto friendly_names = com_ports::get_friendly_names();

        for(const auto &name : friendly_names)
            ::std::wcout << name << L'\n';
    }
    catch(const ::std::exception &e)
    {
        auto prev_cp = ::GetConsoleOutputCP();
        ::SetConsoleOutputCP(CP_UTF8);

        ::std::cout << e.what() << '\n';

        ::SetConsoleOutputCP(prev_cp);
    }

    return 0;
}

Ниже приводится вывод этой программы на моей тестовой виртуальной машине (у Вас он будет другим).

COM ports:
COM3
COM4

COM ports with prefix:
\\?\\COM3
\\?\\COM4

Friendly names:
SerialTool - Virtual COM Port VCPA0 (COM3)
SerialTool - Virtual COM Port VCPB0 (COM4)

Дополнительная информация

Сюда я буду выкладывать ссылки на другие свои статьи посвященные библиотеке com_ports по мере их опубликования.

Заключение

com_ports — мой первый более или менее серьёзный open source проект. Получился ли он комом? Надеюсь, что нет.

Да, от него мало пользы. Но для меня он стал хорошим тренировочным полигоном и в части реализации сборки (установки) средствами CMake, и в части написания и оформления документации с помощью Sphinx.

Надеюсь, что и для Вас он окажется полезным.

Ссылки

  1. Моя статья «Получение списка доступных COM портов»: https://norseev.ru/2018/01/04/comportlist_windows/
  2. Обсуждение вопроса получения списка имён COM портов на StackOverflow: https://stackoverflow.com/questions/304986/how-do-i-get-the-friendly-name-of-a-com-port-in-windows?rq=3
  3. Решение задачи получения списка «дружественных имён» COM портов от Александра Рощупкина: https://www.cyberforum.ru/blogs/1083385/10679.html
  4. Моя статья «О важности префикса «\\\\?\\\\»: https://norseev.ru/2018/01/16/about_prefix_com_createfile/
  5. Репозиторий с библиотекой com_ports: https://gitflic.ru/project/sergey_norseev/com_ports
  6. Свидзиньски Р. «CMake для C++» / пер. с англ. А.А. Слинкина. — М.: ДМК Пресс, 2024
  7. Статья «Построчный разбор лицензии MIT» (перевод статьи Kyle E. Mitchell): https://habr.com/ru/articles/310976/

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *