代码来自WebRTC中的rtc_base基础模块, flags.h 、 flags.cc
支持的功能
- 每个Flag有一个默认的值,不指定时使用默认值,指定时使用指定的值
- Flag不需要通过函数去传递,允许在任意一个文件定义
- 选择能同时支持
-
和--
,支持注释,支持打印所有的Flag - 自动解析命令行参数,并能检查参数类型是否正确
如何实现
- 定义一个union类型的FlagValue,支持bool、int、double、const char*类型
// Internal use only.
union FlagValue {
// Note: Because in C++ non-bool values are silently converted into
// bool values ('bool b = "false";' results in b == true!), we pass
// and int argument to New_BOOL as this appears to be safer - sigh.
// In particular, it prevents the (not uncommon!) bug where a bool
// flag is defined via: DEFINE_bool(flag, "false", "some comment");.
static FlagValue New_BOOL(int b) {
FlagValue v;
v.b = (b != 0);
return v;
}
static FlagValue New_INT(int i) {
FlagValue v;
v.i = i;
return v;
}
static FlagValue New_FLOAT(float f) {
FlagValue v;
v.f = f;
return v;
}
static FlagValue New_STRING(const char* s) {
FlagValue v;
v.s = s;
return v;
}
bool b;
int i;
double f;
const char* s;
};
- 定义Flag类,类中保存了当前Flag的定义的文件位置,名字,注释,类型,默认值,用户通过命令行设置的值,并加入到链表中
// Each flag can be accessed programmatically via a Flag object.
class Flag {
public:
enum Type { BOOL, INT, FLOAT, STRING };
// Internal use only.
Flag(const char* file, const char* name, const char* comment,
Type type, void* variable, FlagValue default_) : file_(file), name_(name), comment_(comment), type_(type), variable_(reinterpret_cast<FlagValue*>(variable)),
default_(default__) { FlagList::Register(this); }
// General flag information
const char* file() const { return file_; }
const char* name() const { return name_; }
const char* comment() const { return comment_; }
// Flag type
Type type() const { return type_; }
// Flag variables
bool* bool_variable() const { RTC_DCHECK_EQ(BOOL, type_); return &variable_->b; }
int* int_variable() const { RTC_DCHECK_EQ(INT, type_); return &variable_->i; }
double* float_variable() const { RTC_DCHECK_EQ(FLOAT, type_); return &variable_->f; }
const char** string_variable() const { RTC_DCHECK_EQ(STRING, type_); return &variable_->s; }
// Iteration support
Flag* next() const { return next_; }
// Prints flag information. The current flag value is only printed
// if print_current_value is set.
void Print(bool print_current_value) {
printf(" --%s (%s) type: %s default: ", name_, comment_, Type2String(type_));
PrintFlagValue(type_, &default_);
if (print_current_value) {
printf(" current value: "); PrintFlagValue(type_, variable_);
}
printf("\n");
}
private:
const char* file_;
const char* name_;
const char* comment_;
Type type_;
FlagValue* variable_;
FlagValue default_;
Flag* next_;
friend class FlagList; // accesses next_
};
- 定义了数据结构之后,可以使用宏来自动设置文件名和一个固定类型的全局静态变量,变量名格式是FLAG_XXX,这个XXX就是我们自己设置的
// Internal use only.
#define DEFINE_FLAG(type, c_type, name, default, comment) \
/* define and initialize the flag */ \
c_type FLAG_##name = (default); \
/* register the flag */ \
static rtc::Flag Flag_##name(__FILE__, #name, (comment), \
rtc::Flag::type, &FLAG_##name, \
rtc::FlagValue::New_##type(default))
// Use the following macros to define a new flag:
#define DEFINE_bool(name, default, comment) \
DEFINE_FLAG(BOOL, bool, name, default, comment)
#define DEFINE_int(name, default, comment) \
DEFINE_FLAG(INT, int, name, default, comment)
#define DEFINE_float(name, default, comment) \
DEFINE_FLAG(FLOAT, double, name, default, comment)
#define DEFINE_string(name, default, comment) \
DEFINE_FLAG(STRING, const char*, name, default, comment)
- 完成了上面步骤之后我们定义的所以Flag都会存到FlagList结构的
list_
链表中,我们需要在main函数中把所有的argc传递给FlagList去解析,当匹配到用户设置的值时取代默认值
Flag* FlagList::Lookup(const char* name) {
Flag* f = list_;
while (f != nullptr && strcmp(name, f->name()) != 0)
f = f->next();
return f;
}
void FlagList::SplitArgument(const char* arg, char* buffer, int buffer_size, const char** name, const char** value, bool* is_bool) {
*name = nullptr;
*value = nullptr;
*is_bool = false;
if (*arg == '-') {
// find the begin of the flag name
arg++; // remove 1st '-'
if (*arg == '-')
arg++; // remove 2nd '-'
if (arg[0] == 'n' && arg[1] == 'o' && Lookup(arg + 2)) {
arg += 2; // remove "no"
*is_bool = true;
}
*name = arg;
// find the end of the flag name
while (*arg != '\0' && *arg != '=')
arg++;
// get the value if any
if (*arg == '=') {
// make a copy so we can NUL-terminate flag name
int n = static_cast<int>(arg - *name);
RTC_CHECK_LT(n, buffer_size);
memcpy(buffer, *name, n * sizeof(char));
buffer[n] = '\0';
*name = buffer;
// get the value
*value = arg + 1;
}
}
}
int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv, bool remove_flags) {
// parse arguments
for (int i = 1; i < *argc; /* see below */) {
int j = i; // j > 0
const char* arg = argv[i++];
// split arg into flag components
char buffer[1024];
const char* name;
const char* value;
bool is_bool;
SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
if (name != nullptr) {
// lookup the flag
Flag* flag = Lookup(name);
if (flag == nullptr) {
fprintf(stderr, "Error: unrecognized flag %s\n", arg);
return j;
}
// if we still need a flag value, use the next argument if available
if (flag->type() != Flag::BOOL && value == nullptr) {
if (i < *argc) {
value = argv[i++];
} else {
fprintf(stderr, "Error: missing value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
}
// set the flag
char empty[] = { '\0' };
char* endp = empty;
switch (flag->type()) {
case Flag::BOOL:
*flag->bool_variable() = !is_bool;
break;
case Flag::INT:
*flag->int_variable() = strtol(value, &endp, 10);
break;
case Flag::FLOAT:
*flag->float_variable() = strtod(value, &endp);
break;
case Flag::STRING:
*flag->string_variable() = value;
break;
}
// handle errors
if ((flag->type() == Flag::BOOL && value != nullptr) ||
(flag->type() != Flag::BOOL && is_bool) || *endp != '\0') {
fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
// remove the flag & value from the command
if (remove_flags)
while (j < i)
argv[j++] = nullptr;
}
}
// shrink the argument list
if (remove_flags) {
int j = 1;
for (int i = 1; i < *argc; i++) {
if (argv[i] != nullptr)
argv[j++] = argv[i];
}
*argc = j;
}
// parsed all flags successfully
return 0;
}
void FlagList::Register(Flag* flag) {
RTC_DCHECK(flag);
RTC_DCHECK_GT(strlen(flag->name()), 0);
// NOTE: Don't call Lookup() within Register because it accesses the name_
// of other flags in list_, and if the flags are coming from two different
// compilation units, the initialization order between them is undefined, and
// this will trigger an asan initialization-order-fiasco error.
flag->next_ = list_;
list_ = flag;
}
如何使用
// 定义Flag
DEFINE_bool(h, false, "Prints this message.");
DEFINE_bool(help, false, "Prints this message.");
DEFINE_bool(log, false, "Enable log output to stdout.");
DEFINE_string(logfile, "log.txt", "Set log storage file.");
DEFINE_string(ip, "0.0.0.0", "Set media service IP.");
DEFINE_int(port, 8800, "Set media service port.");
// 读取命令行参数
通过FLAG_xxx去使用我们定义的flag,例如FLAG_h、FLAG_help
int32_t main(int32_t argc, char* argv[])
{
if (FlagList::SetFlagsFromCommandLine(&argc, argv, true) || FLAG_help || FLAG_h) {
if (FLAG_help || FLAG_h) {
rtc::FlagList::Print(nullptr, false);
return 0;
}
return 1;
}
// TODO
return 0;
}