我有一个模块,该模块接收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型varargs可以在中央函数中进行解析,但这不会带来类型安全。
编辑: 同时,我想出了以下解决方案,我认为在某种程度上可以兼顾黑客性和模块化:
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);
答案 0 :(得分:0)
保持命令处理界面相同的一种方法是退回到main()
收到的古老的 argv / argc 接口。假设收到的命令具有 words 的概念(可能是空格分隔),则可能是这样的:
bsearch()
的名称。哈希表也许也合适。无论您实现映射如何,结果都是一个指向函数的指针,该函数采用指向参数的指针数组和该指针数组中元素数量的计数。main()
一样。答案 1 :(得分:0)
围绕它构建一个抽象层可能会使事情变得更复杂,从而容易发生错误。除非应该处理的命令数量庞大,需要维护,并且这是应用程序的主要任务之一,否则我不会这样做。
有了保持类型安全和使分析与算法分离的先决条件,您可以构建类似于以下类似于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)