概述
1、简介
最近在移植letter-shell这个shell组件到MCU中,发现可以使用SHELL_EXPORT_CMD宏注册相关命令,而不需要先定义数组,这大大方便了代码的开发,进一步分析发现SHELL_EXPORT_CMD主要使用了__attribute__、##、#等参数,借此机会对这个几个参数做一下详细说明,本文以letter-shell中的SHELL_EXPORT_CMD宏为例展开描述,以keil编译环境(arm_cc)说明,gcc编译器有所差别。
2、语法解析
- 实际使用示例
void cmd_test(void)
{
printf("Hello world ! dn");
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), cmdTest, cmd_test, test cmd);
这个命令就注册了一个cmdTest命令,当我们输入cmdTest时,就会执行到void cmd_test(void)函数,也就是打印“Hello world !”;
SHELL_EXPORT_CMD(_attr, _name, _func, _desc) 定义如下:
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc)
const char shellCmd##_name[] = #_name;
const char shellDesc##_name[] = #_desc;
SHELL_USED const ShellCommand
shellCommand##_name SHELL_SECTION("shellCommand") =
{
.attr.value = _attr,
.data.cmd.name = shellCmd##_name,
.data.cmd.function = (int (*)())_func,
.data.cmd.desc = shellDesc##_name
}
以下说明以SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), cmdTest, cmd_test, test cmd);为例介绍:
_attr:这个参数代表cmdTest的执行权限;
_name:命令名,我们输入的时候输入的是“cmdTest”字符串,看看这个字符串是怎么由cmdTest转化来的;
_func:函数,这里指cmd_test;
_desc:命令描述,这里指“test cmd”;
在详细介绍这个命令之前,将SHELL_EXPORT_CMD(_attr, _name, _func, _desc)用到的宏或者结构体在下面贴出来;
- 使用到的数据结构体,ShellCommand结构体:
/**
* @brief shell command定义
*/
typedef struct shell_command
{
union
{
struct
{
unsigned char permission : 8; /**< command权限 */
ShellCommandType type : 4; /**< command类型 */
unsigned char enableUnchecked : 1; /**< 在未校验密码的情况下可用 */
unsigned char disableReturn : 1; /**< 禁用返回值输出 */
unsigned char readOnly : 1; /**< 只读 */
unsigned char reserve : 1; /**< 保留 */
unsigned char paramNum : 4; /**< 参数数量 */
} attrs;
int value;
} attr; /**< 属性 */
union
{
struct
{
const char *name; /**< 命令名 */
int (*function)(); /**< 命令执行函数 */
const char *desc; /**< 命令描述 */
} cmd; /**< 命令定义 */
struct
{
const char *name; /**< 变量名 */
void *value; /**< 变量值 */
const char *desc; /**< 变量描述 */
} var; /**< 变量定义 */
struct
{
const char *name; /**< 用户名 */
const char *password; /**< 用户密码 */
const char *desc; /**< 用户描述 */
} user; /**< 用户定义 */
struct
{
int value; /**< 按键键值 */
void (*function)(Shell *); /**< 按键执行函数 */
const char *desc; /**< 按键描述 */
} key; /**< 按键定义 */
} data;
} ShellCommand;
- 使用到的宏:
SHELL_USED
#define SHELL_USED __attribute__((used))
SHELL_SECTION(x)
#define SHELL_SECTION(x) __attribute__((section(x)))
- 命令详细解析
下面对SHELL_EXPORT_CMD(_attr, _name, _func, _desc)逐行介绍,先介绍第1、2行代码之前,先介绍##和#的作用:
## 连接符:在带参数的宏定义中, 用来将两个Token连接为一个Token ,从而形成一个新的子串。 注意这里连接的对象是Token就行,而不一定是宏的变量。
#符:是将其后面的宏参数进行字符串化操作(Stringfication),即把宏参数变为一个字符串,简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。#符,也就是把传递过来的参数当成字符串进行替代。
const char shellCmd##_name[] = #_name;
那么这行代码的就可以翻译为const char shellCmd_name[] = “_name”,当然需要注意的是_name需要将传进来的实际_name代替。第二行代码同理(const char shellDesc##_name[] = #_desc;);
下面介绍最后一部分:
SHELL_USED const ShellCommand
shellCommand##_name SHELL_SECTION("shellCommand") =
{
.attr.value = _attr,
.data.cmd.name = shellCmd##_name,
.data.cmd.function = (int (*)())_func,
.data.cmd.desc = shellDesc##_name
}
先介绍修饰符 SHELL_USED:
#define SHELL_USED __attribute__((used))
used意义如下:
unused:表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
used: 向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告
定义的变量ShellCommand :
const ShellCommand
shellCommand##_name
这个变量可以展开为 const ShellCommand shellCommand_name,注意ShellCommand 是结构体;
再介绍修饰符SHELL_SECTION:
#define SHELL_SECTION(x) __attribute__((section(x)))
这个宏代表可以将变量添加到某个输入段中,那么结合上面的代码,就可以理解为将shellCommand_name这个变量放入到了shellCommand这个输入段中,这个时候查看.map文件就可以发现文件里面多了一个名为shellCommand_name.shellCommand的输入段,可见shellCommand就是这个输入段后缀。
=
{
.attr.value = _attr,
.data.cmd.name = shellCmd##_name,
.data.cmd.function = (int (*)())_func,
.data.cmd.desc = shellDesc##_name
}
这一部分代码主要是对定义的常量shellCommand_name进行初始化操作;
3、 如何执行
介绍到这里,我们基本可以知道SHELL_EXPORT_CMD(_attr, _name, _func, _desc)宏定义了一个常量,并且将这个常量放入输入段中,那么这个输入段怎么才能被执行到呢?要做到这个,我们必须要获取到shellCommand这个输入段的起始地址,不同编译获取这个输入段的起始地址的方法如下:
#if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
shell->commandList.base = (ShellCommand *)(&shellCommand$$Base);
shell->commandList.count = ((unsigned int)(&shellCommand$$Limit)
- (unsigned int)(&shellCommand$$Base))
/ sizeof(ShellCommand);
#elif defined(__ICCARM__) || defined(__ICCRX__)
shell->commandList.base = (ShellCommand *)(__section_begin("shellCommand"));
shell->commandList.count = ((unsigned int)(__section_end("shellCommand"))
- (unsigned int)(__section_begin("shellCommand")))
/ sizeof(ShellCommand);
#elif defined(__GNUC__)
shell->commandList.base = (ShellCommand *)(&_shell_command_start);
shell->commandList.count = ((unsigned int)(&_shell_command_end)
- (unsigned int)(&_shell_command_start))
/ sizeof(ShellCommand);
#else
#error not supported compiler, please use command table mode
#endif
注意在keil使用的arm_cc编译器中的&shellCommand$$Base地址即为shellCommand这个输入段的起始地址。获取到了起始地址,根据结构体的偏移,我们就可以很方便的调用这个输入段中的所有注册的函数。就此我们就可以调用到所有SHELL_EXPORT_CMD注册的函数了;
参考文档:
《attribute((section(x))) 使用详解 https://www.cxyzjd.com/article/qq_42370291/103639349》
最后
以上就是诚心世界为你收集整理的C基础 -- SHELL_EXPORT_CMD(_attr, _name, _func, _desc)宏注册函数详解的全部内容,希望文章能够帮你解决C基础 -- SHELL_EXPORT_CMD(_attr, _name, _func, _desc)宏注册函数详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复