目录
1.简述
前几天需要写一个发送数据的Application,要在命令后面加几个数据,所以写了一个demo先测试一下Application中加参数的相关函数。
EDK2版本:edk2-stable/202011(所有代码均基于开源代码)
2.代码
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/ShellCommandLib.h>
#include <Library/DebugLib.h>
STATIC CONST SHELL_PARAM_ITEM ParamList[] = {
{L"-aa", TypeValue},
{L"-cc", TypeValue},
{NULL, TypeMax}
};
//临时debug用
// typedef struct {
// LIST_ENTRY Link;
// CHAR16 *Name;
// SHELL_PARAM_TYPE Type;
// CHAR16 *Value;
// UINTN OriginalPosition;
// } SHELL_PARAM_PACKAGE;
//临时debug用
/**
The user Entry Point for Application. The user code starts with this function
as the real entry point for the application.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS The entry point is executed successfully.
@retval other Some error occurs when executing this entry point.
**/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
Print(L"Hello,this is Entry of UefiMain!\n");
EFI_STATUS Status;
LIST_ENTRY *Package;
CHAR16 *ProblemParam;
// SHELL_STATUS ShellStatus;
// UINT8 Level;
CONST CHAR16 *ParamAA ; //?????
CONST CHAR16 *ParamCC ;
UINT16 intParamAA = 0;
UINT16 intParamCC = 0;
// Level = PcdGet8(PcdShellSupportLevel);
ProblemParam = NULL;
// ShellStatus = SHELL_SUCCESS;
//
// initialize the shell lib (we must be in non-auto-init...)
//
Status = ShellInitialize();
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_INFO, "[ZK] !!!ShellInitialize Fail!,Status :%r.\n", Status));
}
Status = CommandInit();
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_INFO, "[ZK] !!!CommandInit Fail!,Status :%r.\n", Status));
}
//
// parse the command line
//
Status = ShellCommandLineParse (ParamList, &Package, &ProblemParam, TRUE);
//临时debug
// ParamAA = ShellCommandLineGetValue(Package, L"-a");
// LIST_ENTRY *Node;
// for ( Node = GetFirstNode(Package)
// ; !IsNull (Package, Node)
// ; Node = GetNextNode(Package, Node)
// ){
// DEBUG ((EFI_D_INFO, "-------------------------------------------------\n"));
// DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Name = %s\n",((SHELL_PARAM_PACKAGE*)Node)->Name));
// DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Value = %s\n",((SHELL_PARAM_PACKAGE*)Node)->Value));
// DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Type = %d\n",((SHELL_PARAM_PACKAGE*)Node)->Type));
// // }
// }
// DEBUG ((EFI_D_INFO, "OVER"));
//临时debug
//如果ShellCommandLineParse失败
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_INFO, "[ZK] !!!ShellCommandLineParse Fail!,Status :%r.\n", Status));
} else {
if (ShellCommandLineGetFlag(Package, L"-aa")) { //-a
ParamAA = ShellCommandLineGetValue(Package, L"-aa");
if(ParamAA != NULL){
DEBUG ((EFI_D_INFO, "ParamAA = %s\n", ParamAA));
intParamAA = (UINT8)ShellStrToUintn(ParamAA);
}
}
if (ShellCommandLineGetFlag(Package, L"-cc")) { //-b
ParamCC = ShellCommandLineGetValue(Package, L"-cc");
if(ParamAA != NULL){
DEBUG ((EFI_D_INFO, "ParamCC = %s\n", ParamCC));
intParamCC = (UINT8)ShellStrToUintn(ParamCC);
}
}
// free the command line package
ShellCommandLineFreeVarList (Package);
}
Print(L"intParamAA = %d,intParamCC = %d\n", intParamAA, intParamCC);
Print(L"-----------------------------------\n");
// DEBUG ((EFI_D_INFO, "ParamAA = %s\n", ParamAA));
// DEBUG ((EFI_D_INFO, "ParamCC = %s\n", ParamCC));
return EFI_SUCCESS;
}
3.重要数据结构和函数解释
3.1数组 ParamList[]
STATIC CONST SHELL_PARAM_ITEM ParamList[] = {
{L"-aa", TypeValue},
{L"-cc", TypeValue},
{NULL, TypeMax}
};
ParamList[]
数组中:"-aa"
和"-cc"
就是我设置的参数,TypeValue
是指形式如:"-aa 1"
, "-cc 2"
,关于TypeValue
的解释可以在<shelllib.h>
中看到,其中的注释也写得非常明白:
typedef enum {
TypeFlag = 0, ///< A flag that is present or not present only (IE "-a").
TypeValue, ///< A flag that has some data following it with a space (IE "-a 1").
TypePosition, ///< Some data that did not follow a parameter (IE "filename.txt").
TypeStart, ///< A flag that has variable value appended to the end (IE "-ad", "-afd", "-adf", etc...).
TypeDoubleValue, ///< A flag that has 2 space seperated value data following it (IE "-a 1 2").
TypeMaxValue, ///< A flag followed by all the command line data before the next flag.
TypeTimeValue, ///< A flag that has a time value following it (IE "-a -5:00").
TypeMax,
} SHELL_PARAM_TYPE;
3.2函数 CommandInit()和ShellInitialize()
两个初始化函数,要把相关库放到inf文件中,dsc文件也要相应做配置
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
ShellLib
DebugLib
HandleParsingLib
ShellCommandLib
IpmiBaseLib
其中 HandleParsingLib
这个lib我们其实没用到,是包在ShellCommandLib
中的,但是在编译过程中一直报HandleParsingLib
这个库"not found"的错误,所以我把它也移到这里了。
3.3函数 ShellCommandLineParse()
这个函数用来解析命令行,有一个宏定义:
#define ShellCommandLineParse(CheckList,CheckPackage,ProblemParam,AutoPageBreak) ShellCommandLineParseEx(CheckList,CheckPackage,ProblemParam,AutoPageBreak,FALSE)
所以看一下函数ShellCommandLineParseEx
的声明:
EFI_STATUS
EFIAPI
ShellCommandLineParseEx (
IN CONST SHELL_PARAM_ITEM *CheckList,
OUT LIST_ENTRY **CheckPackage,
OUT CHAR16 **ProblemParam OPTIONAL,
IN BOOLEAN AutoPageBreak,
IN BOOLEAN AlwaysAllowNumbers
)
{
...
}
看一下这个函数的参数
参数 | 解释 |
---|---|
CheckList | 这个就是3.1中的数组,存储参数的flag |
CheckPackage | 这里面存的是解析出来的参数 |
CheckPackage | 将非CheckList数组中的参数保存到此 ,认为是有问题的参数 |
AutoPageBreak | TRUE |
AlwaysAllowNumbers | 宏中定义设置为了False |
3.4函数 ShellCommandLineGetFlag()和ShellCommandLineGetValue()
ShellCommandLineGetFlag
用来检查是否有你输入的参数,有就返回True,否则就返回False,比如:
if (ShellCommandLineGetFlag(Package, L"-aa"))
就是判断是否有“-aa”
这个Flag出现,出现就返回True
ShellCommandLineGetValue()
用来获取某个Flag后面的值,如:
ShellCommandLineGetValue(Package, L"-aa")
用来获取"-aa"
这个Flag后输入的值。
3.5函数 ShellStrToUintn()
ShellStrToUintn()
用于string和Uint的类型转换,返回是UINT64的值,程序里强制转换成了UNIT8
intParamCC = (UINT8)ShellStrToUintn(ParamCC);
4.问题与解决
遇到了一个非常坑爹的问题,本来是打算做一个demo,初始的flag就很随意的设置成了"-a"
和"-b"
,结果一直报错,我就去看了一下解析出来的CheckPackage
里面到底是什么,就有了被注释掉的代码。
typedef struct {
LIST_ENTRY Link;
CHAR16 *Name;
SHELL_PARAM_TYPE Type;
CHAR16 *Value;
UINTN OriginalPosition;
} SHELL_PARAM_PACKAGE;
这个结构体中就是CheckPackage
的结构,"Name"
就是Flag,"Value"
就是Flag后面跟的值。用如下代码输入打印所有CheckPackage
里面的东西:
//临时debug
ParamAA = ShellCommandLineGetValue(Package, L"-a");
LIST_ENTRY *Node;
for ( Node = GetFirstNode(Package)
; !IsNull (Package, Node)
; Node = GetNextNode(Package, Node)
){
DEBUG ((EFI_D_INFO, "-------------------------------------------------\n"));
DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Name = %s\n",((SHELL_PARAM_PACKAGE*)Node)->Name));
DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Value = %s\n",((SHELL_PARAM_PACKAGE*)Node)->Value));
DEBUG ((EFI_D_INFO, "((SHELL_PARAM_PACKAGE*)Node)->Type = %d\n",((SHELL_PARAM_PACKAGE*)Node)->Type));
}
}
DEBUG ((EFI_D_INFO, "OVER"));
//临时debug
最后输出的的flag和value根本对不上,我就看了一下,在函数ShellCommandLineParse()
的最后中看到了如下code:
//
// support for AutoPageBreak
//
if (AutoPageBreak && ShellCommandLineGetFlag(*CheckPackage, L"-b")) {
ShellSetPageBreakMode(TRUE);
}
AutoPageBreak
我设置的是TRUE,而且我用的flag是"-b"
,就是这个导致了最后的输出不对。
所以教训就是:AutoPageBreak
设置为TRUE的时候不要用"-b"
的flag,否则结果会很惨。无论你数组ParamList[]
里面有没有设置"-b",都会有"-b"这个flag存在。
5.补充
1.对于函数ShellCommandLineParse()
的返回值的判断:
Status = ShellCommandLineParse (ParamList, &Package, &ProblemParam, TRUE);
//如果ShellCommandLineParse失败
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_INFO, "[ZK] !!!ShellCommandLineParse Fail!,Status :%r.\n", Status));
} else {
…
ShellCommandLineParse()
这个函数针对不同情况有好几个返回值,看一下注释:
@retval EFI_SUCCESS The operation completed sucessfully.
@retval EFI_OUT_OF_RESOURCES A memory allocation failed.
@retval EFI_INVALID_PARAMETER A parameter was invalid.
@retval EFI_VOLUME_CORRUPTED The command line was corrupt.
@retval EFI_DEVICE_ERROR The commands contained 2 opposing arguments. One
of the command line arguments was returned in
ProblemParam if provided.
@retval EFI_NOT_FOUND A argument required a value that was missing.
The invalid command line argument was returned in
ProblemParam if provided.
建议是针对不同的返回值做判断,特别是@retval EFI_INVALID_PARAMETER
这个返回值,如果你输入的flag是数组ParamList[]
中没有的,就会返回这一值。比如我的Flag是"-aa"
,"-cc"
,但是如果我在shell下面用的时候输入了"-dd"
,这时候就会返回VOLUME_CORRUPTED
,而且"-dd"
以及后面跟的值会进入到ProblemParam
中,从而可以做判断。
2.数组ParamList[]
中的类型一定要对,比如我是"-aa 1"
这种就要用TypeValue,你如果是"-a",就要用TypeFlag
,如果混用就会出问题。一旦有问题建议用我注释掉的代码中的方法进行debug,可以看到他解析出来的命令到底是什么,再一步一步找问题所在。
3.许多程序都用到了ShellPrintHiiEx()
这个函数,我没有用,因为我所有的信息都通过Print和Debug打印出来了,这个函数应该是一个高级打印函数吧。
6.结果
正常输入结果:
从下面图中可以看到:
第二个中我没有设置"-b"
这个flag,但是输入-b
没有报错,这就是上面提到的问题。
第三个中没有我没有设置"-a"
这个flag,所以输入-a
会报错。
参考
1.https://blog.csdn.net/Lq19880521/article/details/112294014
2.源码:edk2-202011\edk2\ShellPkg\Library\UefiShellLevel3CommandsLib\Ver.c