【问题标题】:Processing ASCII commands in a more robust and type safe way以更健壮和类型安全的方式处理 ASCII 命令
【发布时间】:2019-02-05 22:20:05
【问题描述】:

我有一个模块,它接收 ASCII 命令,然后对它们做出相应的反应。我想知道是否有可能采用更健壮和类型安全的方式来调用处理程序函数。

过去,我有类似下面的代码,这也与这个答案非常相似:Processing ASCII commands via RS232 in embedded c

struct Command commands[] = {
{"command1", command1Handler}
{"command2", command2Handler}
 ...
};

//gets called when a new string has been received
void parseCmd(const char *input) {
//find the fitting command entry and call function pointer
}

bool command1Handler(const char *input) { }
bool command2Handler(const char *input) { }

我不喜欢所有处理函数都必须进行自己的解析。这似乎是不必要的重复且容易出错。

如果我们可以通过以下方式来代替,那就太酷了,所有解析都在 parseCmd 函数中完成:

struct Command commands[] = {
{"command1", command1HandlerSafe}
{"command2", command2HandlerSafe}
 ...
};

void parseCmd(const char *input) {
//1. find fitting command entry
//2. check that parameter number fits the expected number for the target function
//3. parse parameters and validate the types
//4. call function with parameters in their correct types
}

bool command1HandlerSafe(bool param1, const char *param2) { }
bool command2HandlerSafe(int param1) {}

我认为使用旧的 C 风格的可变参数可以在中心函数中进行解析,但这不会带来类型安全。

编辑: 同时我想出了以下解决方案,我认为这在一定程度上平衡了hackiness和模块化:

class ParameterSet{

struct Param{
  const char *paramString;
  bool isInt();
  int toInt();
  float toFloat();
  ..
}

ParameterSet(const char *input);
Param at(size_t index);
size_t length();
char m_buffer[100];
Param m_params[10];
}

bool command1HandlerMoreSafe(const ParameterSet *paramSet);

【问题讨论】:

  • 您可能会发现std::map<std::string, HandlerFunctionType> commands = { {"command1", command1Handler}, { ...}, ... } 很有用。您可以使用commands.at(command)(params) 调用它。 Documentation for std::map.
  • 如果命令处理程序具有不同的签名,我认为这是不可能的。我在这方面更新了帖子。不过,std::map 可能仍然比普通数组有用。
  • 如果处理程序有不同的类型,struct Command 也会有问题。函数指针std::function 或您在Command 中使用的任何内容都将要求所有处理程序具有相同的原型。除非任何东西有一些时髦的魔法,否则我真的希望你让我参与。
  • 这绝妙的魔法正是我所寻找的。可以存储参数的数量和参数类型,然后使用可变参数,但这不是类型安全的。 :(
  • 切勿将可变参数函数用于任何目的。这是不应该添加到语言中的那些东西之一。

标签: c++ serial-port embedded ascii


【解决方案1】:

围绕这个构建一个抽象层可能会使事情变得更复杂,从而容易出错。我不会这样做,除非您应该处理的命令数量很大,需要维护,这是您的应用程序的主要任务之一。

有了保持类型安全和将解析与算法分开的先决条件,您可以构建类似于以下类似 C 的伪代码:

typedef enum
{
  INT,
  STR
} type_t;                 // for marking which type that is supported by the command

typedef struct
{
  type_t type;
  const char* text;       // what kind of text that is expected in case of strings
} arg_t;

typedef struct
{
  const char* name;       // the name of the command
  arg_t*      args;       // list of allowed arguments
  size_t      args_n;     // size of that list
  void (*callback)(void); // placeholder for callback functions of different types
} command_t;

然后您可以创建不关心解析而只关心其专用任务的回调处理函数:

void cmd_branch (const char* str);
void cmd_kill   (int n);

命令数组可能如下所示:

const command_t commands[] = 
{
  { // BRANCH [FAST][SLOW]
    .name="BRANCH", 
    .args=(entry_t[]){ {STR,"FAST"}, {STR,"SLOW"} },
    .args_n=2,
    .callback= (void(*)(void)) cmd_branch,
  },

  { // KILL [7][9]
    .name="KILL",
    .args=(entry_t[]){ {INT, NULL} },
    .args_n=1,
    .callback= (void(*)(void)) cmd_kill,
  }
};

解析函数会这样做:

  • 通过搜索上述列表查找收到的命令(如果列表较大,则搜索)。
  • 检查接收到的命令支持什么类型的参数
  • 相应地解析参数
  • 使用适当类型的参数调用相关函数

由于此示例仅使用了一些虚拟类型函数指针(void(*)(void)),因此您必须强制转换为正确的类型。可以通过例如 C11 _Generic 来完成:

call(commands[i], int_val);

扩展为:

#define call(command, arg) _Generic((arg), \
                                    int:         (void(*)(int))         command.callback, \
                                    const char*: (void(*)(const char*)) command.callback )(arg)

【讨论】:

  • 很酷的东西,直到现在才知道_Generic。唯一的限制似乎是处理函数的参数数量是固定的?
  • @locali 你可以使用结构体。
【解决方案2】:

保持命令处理接口不变的一种方法是使用 main() 接收的古老的 argv / argc 接口。假设接收到的命令有一些 words 的概念(也许是空格分隔),它可能是这样的:

  • 接收输入字符串。
  • 将输入解析为单词,其中第一个单词是命令的名称,其余单词是其参数。
  • 随着解析的进行,在数组中放置一个指向包含每个单词的字符串的指针,并记录数组中元素的数量。
  • 使用第一个字查找命令函数指针。如果这些命令在编译时都是已知的,则可以使用 bsearch() 之类的东西。也许哈希表也可能是合适的。无论您如何实现映射,结果都是一个指向函数的指针,该函数接受一个指向参数的指针数组和指针数组中元素的计数。
  • 通过其指针调用命令函数并传递已解析的单词数组和计数,就像启动代码调用main()一样。
  • 从那里,每个命令函数都可以处理其参数的具体含义,并根据需要将字符串表示形式转换为内部形式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-04
    • 2011-11-12
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    相关资源
    最近更新 更多