Arlleex 178 21 февраля Опубликовано 21 февраля · Жалоба Набор команд для работы железяки с сервисной программной на ПК весьма обширный и требует различных структур при обмене. И нередки ситуации, когда ортогональные группы команд имеют одинаковый формат структур, но совершенно разное предназначение. А ломать голову "подходит ли мне эта структура для отправки такого-то запроса" совершенно не продуктивно. В случае с кучей псевдонимов интуитивно понятно, для какого типа запроса какую структуру заполнять. Пример. Вот упрощенный список разносортных команд для управления железкой через сервисную ПО-шку на компе enum eUID : u32 { // системные команды и запросы SYS_CMD_RST_MCU = 0, // сброс МК SYS_REQ_GET_INFO, // запрос общесистемной инфы (версии ПО, железки и т.д.) ... // команды и запросы для работы с CAN WORK_CMD_CLEAR_RX_FIFO_CAN = 10000, WORK_CMD_CLEAR_TX_FIFO_CAN, WORK_REQ_RECV_CAN, WORK_CMD_SEND_CAN, ... // команды и запросы для работы с логами WORK_REQ_READ_FLASH_LOG, ... }; В кадре обмена одним из первых обязательных полей служит идентификатор команды/запроса: eUID uid. Почти все вышеперечисленные UID (кроме WORK_CMD_SEND_CAN) весьма просты, т.е. не требуют аргументов, их структура одинакова, и, казалось бы, можно было их упрятать в одну какую-нибудь struct sTxSimpleCmd { eUID uid; u32 crc; }; Но в самом прикладном коде нужно напрягать голову и думать, а подходит ли эта структура для той или иной UID, особенно когда этих команд уже под сотню. Потому что некоторые UID требуют формата структур совершенно другого "наполнения", например, для WORK_CMD_SEND_CAN struct sTxSendCAN { eUID uid; sCANFrame frame; u32 crc; }; и, как видно, уже образуются отдельные структуры sTxSimpleCmd и sTxSendCAN. Потом окажется, что sTxSendCAN по наполнению подходит какой-нибудь другой команде для другого сервиса, и что, в точке использования заводить эту структуру? Нет конечно. Вот я и создаю псевдонимы структурам, чтобы сразу по названию желаемой команды было понятно, какую структуру создавать // команды и запросы без аргументов typedef struct { eUID uid; u32 crc; } sSysCmd, sSysReq, // для UID из списка SYS_... sWorkCANCmd, sWorkCANReq, // для UID работы с CAN sWorkLogCmd; // для UID работы с логами // для команды WORK_CMD_SEND_CAN struct sWorkCANSendCmd { eUID uid; sCANFrame frame; u32 crc; }; Мне и самому до сих пор не очень все это нравится, но лучшего способа не запутать себя в точке заполнения и вызова для отправки конкретной структуры я не нашел, т.к. хочешь не хочешь, надо как-то связывать тип сообщения с его форматом, и не налету, а еще и расшаривая получившиеся структуры на прием с другой стороны - со стороны железки. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 18 26 февраля Опубликовано 26 февраля · Жалоба В 21.02.2024 в 22:03, Arlleex сказал: Потом окажется, что sTxSendCAN по наполнению подходит какой-нибудь другой команде для другого сервиса, и что, в точке использования заводить эту структуру? Нет конечно. Вот я и создаю псевдонимы структурам Почему "нет конечно". Конечно да:) Потому что потом, при необходимости изменить структуру sTxSendCAN не придётся искать все её псевдонимы и дорабатывать их. Лучше уж сразу для каждой команды прописать подходящую структуру. Можно использовать плюсовый механизм traits. Типа такого: // перечень всех команд enum Command : uint32_t { Reset = 0, GetInfo = 1, SendData = 2, }; // шаблон структуры, содержащей информацию о команде template<Command cmd> struct CommandInfo; // специализации шаблона для каждой команды (можно туда добавлять всё что угодно: тайм-аут обработки, структуры-параметры и так далее) template <> struct CommandInfo<Command::Reset> { struct Params {}; }; template <> struct CommandInfo<Command::GetInfo> { struct Params { uint32_t param1; }; }; template <> struct CommandInfo<Command::SendData> { struct Params { uint32_t param1; uint32_t param2; }; }; void send(...) { } // и вот как будет выглядеть выбор нужной структуры для заданной команды: void sendData() { using CommandData = CommandInfo<Command::SendData>; using Params = typename CommandData::Params; Params params; send(Command::SendData, ¶ms, sizeof(Params)); } При добавлении новой команды для неё сразу же создаём специализацию шаблона, содержащего инфу о команде. И далее оперируем этой информацией (на обоих концах протокола обмена). 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 26 февраля Опубликовано 26 февраля · Жалоба 33 минуты назад, AHTOXA сказал: При добавлении новой команды для неё сразу же создаём специализацию шаблона, содержащего инфу о команде. И далее оперируем этой информацией (на обоих концах протокола обмена). А каковы преимущества специализаций тут? Ведь и в Вашем, и в моем варианте так или иначе появляются эти "отдельные" структурки: struct CommandReset { ... }; struct CommandGetInfo { ... }; struct CommandSendData { ... }; которые, если они одинаковые, можно описать один раз и раздать псевдонимы: typedef struct { ... } CommandReset, CommandGetInfo, CommandSendData; Смотрите, насколько запись короче стала🙂 При этом не потеряла однозначностей: ну да, тут надо всегда "следить" за логической связью между структурой и командой. Зато представьте, а вдруг таких команд с одинаковым логическим смыслом будет не одна, не две, а три десятка: enum Command { CMD_RESET_MCU, CMD_RESET_RTC, CMD_RESET_SDRAM, CMD_RESET_EXPANDER, ... }; В случае шаблонизации получится "раздувание" одинаковых структур на ровном месте, т.к. каждой из команд нужно будет специализировать шаблон. А в случае с обыкновенной структурой все это "схлопнется" в одну единственную структуру struct CommandReset {...}. Ну, тут можно, конечно, возразить: разбить enum с командами на отдельные энумы, но вот только это весьма громоздко. Придется следить за согласованностью значений всех команд в разных местах - не красиво. Но за пример со специализацией спасибо, я определенно попробую поиграться с этим: очень удобно, когда инстанцируется нужная структура по названию команды. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 18 26 февраля Опубликовано 26 февраля · Жалоба 28 минут назад, Arlleex сказал: А каковы преимущества специализаций тут? Ведь и в Вашем, и в моем варианте так или иначе появляются эти "отдельные" структурки: Я как раз и агитирую за отдельные структурки. Чтобы для каждой команды было чётко указано, какова структура параметров. Всё это можно вынести в один *.h - файл, и получить самодокументированный код. Кстати, и в этом подходе можно вынести одинаковые структуры "за скобки": struct OneParam { uint32_t param1; } template <> struct CommandInfo<Command::GetInfo> { using Params = OneParam; }; Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 24 26 февраля Опубликовано 26 февраля · Жалоба Если уж наделять структуры такими заумными возможностями, то, что мешает их надели уже неким "функционалом", некими действиями? 😉 Ведь структура - это по сути класс, а значит может не только хранить данные, но и выполнять разные действия с их передачей, приемом, контролем целостности. Для этого им нужно дать методы передачи своих команд. Например, "дать" во владение некий порт обмена или передать ей метод для работы с тем или иным портом или даже списком портов Таким образом рождается некая иерархия команд: есть базовые, а есть те, которые построены на их основе и дают больше возможностей. Такие команды должны уметь работать с итераторами (списками), чтобы не "перебирать" их вручную. Другой вопрос - насколько это нужно в том или ином проекте. Чем плоха самая обычная структура с безымянным union и т.п? Зачем так усложнять? Ведь должна быть цель, оправдывающая такие усложнения ) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 18 26 февраля Опубликовано 26 февраля · Жалоба 24 минуты назад, Forger сказал: Другой вопрос - насколько это нужно в том или ином проекте. Чем плоха самая обычная структура с безымянным union и т.п? Зачем так усложнять? Ведь должна быть цель, оправдывающая такие усложнения ) То, что вы описали, почти всегда не нужно. А нужно - чёткое и консистентное описание протокола обмена. Можно составить документ, таблицу, типа код команды - структура параметров запроса - структура параметров ответа. И потом надеяться, что не забудешь внести в неё изменения при добавлении/изменении команды. А можно сделать самодокументирующийся код, как я описал выше. Там всё сразу в одном месте. И никак не получится при добавлении команды "забыть" добавить реализацию шаблона. Будет ошибка компиляции. Меня такой подход многократно выручал. Особенно он удобен, когда приходится возвращаться к проекту после длительного перерыва, и всё уже полностью выветрилось из головы. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 26 февраля Опубликовано 26 февраля · Жалоба Цель, как обычно, самая нехитрая: писать обмен с железкой, попутно раздавая получившиеся структуры коллегам для внедрения их в их код клиентской части. Чтобы механизм был отлажен так, что не пришлось придумывать имена очередным структурам, и так, чтобы уже существующие имена не вводили в конфликт с уже реализованными. Только что, AHTOXA сказал: А можно сделать самодокументирующийся код, как я описал выше. Меня в этом подходе устраивает все, кроме раздувания специализаций: template <> struct CommandInfo<Command::GetInfo> { using Params = OneParam; }; Вот было бы здорово, если в этих скобках <> помимо Command::GetInfo можно было через запятую добавлять и другие команды. Иначе получается для каждой команды нужно писать одну и ту же 4-строчную текстулю. Это единственный минус. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 18 26 февраля Опубликовано 26 февраля · Жалоба 7 минут назад, Arlleex сказал: Вот было бы здорово, если в этих скобках <> помимо Command::GetInfo можно было через запятую добавлять и другие команды. Тут согласен, иногда хочется иметь какой-то базовый класс со значениями по умолчанию. В принципе, можно сделать вот так: struct CommandInfoTwoParam { struct Params { uint32_t param1; uint32_t param2; }; }; template <> struct CommandInfo<Command::SendData> : public CommandInfoTwoParam {}; Это почти идеально: лишнее дублирование (почти) отсутствует, но тем не менее каждая команда явно описана. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 26 февраля Опубликовано 26 февраля · Жалоба 16 минут назад, AHTOXA сказал: Тут согласен, иногда хочется иметь какой-то базовый класс со значениями по умолчанию. Нет-нет, я не об этом🙂 Я об этом (это не скомпилится, но так это выглядит в моей голове): template <> struct CommandInfo<Command::Reset, Command::GetInfo, Command::SendData> { using Params = OneParam; }; Т.е. одному телу шаблона назначить сразу 3 команды списком, через запятую. И пусть этим самым знает, что для всех трех этих команд нужно инстанцировать структуру OneParam. А то щас получается один и тот же шаблон 3 раза - просто для разных команд. Глазами потом бегать по описаниям - такое себе🙂 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 1 марта Опубликовано 1 марта · Жалоба Продолжая на эту же тему... А можно ли в C++ каким-то образом создать что-то наподобие FAM в Си? Т.е. я хочу описать структуру, в которой есть сопроводительный хедер, а вот количество данных после этого хедера может быть разным. Пример: комп шлет команду "вычитать CAN-сообщения из входящего FIFO". И железяка должна выгрести FIFO, и сколько там смогла выгрести - столько и отправить в ответном кадре. А то сейчас получается, для такого вида фрейма описывается структура с хедером, без этих самых данных, а наличие данных лишь "предполагается" строго за структурой хедера в памяти принятого кадра. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dimka76 60 1 марта Опубликовано 1 марта · Жалоба On 3/1/2024 at 12:44 PM, Arlleex said: А можно ли в C++ каким-то образом создать что-то наподобие FAM в Си? Я ставлю в качестве последнего поля структуры безразмерный массив и не парюсь ))) Компилятор конечно сыплет предупреждения, но все работает. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 1 марта Опубликовано 1 марта · Жалоба 24 минуты назад, dimka76 сказал: Я ставлю в качестве последнего поля структуры безразмерный массив и не парюсь ))) Компилятор конечно сыплет предупреждения, но все работает. Ну я тоже так могу)) Но хотелось понять, что взамен этого предлагают создатели плюсов. Какие альтернативы, это же должно быть более стильно/модно/понятно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 1 марта Опубликовано 1 марта · Жалоба 2 часа назад, dimka76 сказал: Компилятор конечно сыплет предупреждения Я ставлю массив размером 0. Тогда предупреждений нет. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 83 1 марта Опубликовано 1 марта · Жалоба Указатель на буфер? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 178 1 марта Опубликовано 1 марта · Жалоба 37 минут назад, EdgeAligned сказал: Указатель на буфер? Нет, неудобно при сериализации данных. Нужны именно псевдомассивы. В C++ массивы нулевой длины, вообще говоря, запрещены, и их поддержка и реализация отдается на откуп компилятору. Поэтому думал есть гарантировано правильный с точки зрения стандарта способ. Пока что я тоже, разумеется, забиваю на это и рисую пустые []. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться