【问题标题】:Real-world use of X-MacrosX-Macros 的实际使用
【发布时间】:2011-10-01 22:14:08
【问题描述】:

我刚刚得知X-Macros。您见过 X-Macros 的哪些实际用途?它们什么时候是适合这项工作的工具?

【问题讨论】:

  • 一个 Embedded.com 最近的系列:Part 1Part 2Part 3 关于这个主题。这里的答案同样具有指导意义。
  • 对于那些正在考虑使用 X 宏的人,请考虑使用以下格式:(() () ()) 而不是:( , , )。这使得它们对于递归的、可变参数的宏情况更有用。有关原因/方式的更多信息,请参见此处:stackoverflow.com/a/66130832/1599699 此外,您可以避免在宏中使用那些丑陋的 \,只需将每个条目放在自己的行并包含文件即可;更多信息请看这里:quuxplusone.github.io/blog/2021/02/01/x-macros

标签: c macros c-preprocessor x-macros


【解决方案1】:

一些流行和大型项目对 X-Macros 的实际使用:

Java 热点

在 Java® 编程语言的 Oracle HotSpot 虚拟机中,有一个文件 globals.hpp,它以这种方式使用 RUNTIME_FLAGS

查看源代码:

list of network errors in net_error_list.h 是一长串这种形式的宏扩展:

NET_ERROR(IO_PENDING, -1)

被同一目录下的net_errors.h使用:

enum Error {
  OK = 0,

#define NET_ERROR(label, value) ERR_ ## label = value,
#include "net/base/net_error_list.h"
#undef NET_ERROR
};

这个预处理器魔法的结果是:

enum Error {
  OK = 0,
  ERR_IO_PENDING = -1,
};

我不喜欢这种特殊用途的是常量的名称是通过添加ERR_ 动态创建的。在本例中,NET_ERROR(IO_PENDING, -100) 定义了常量 ERR_IO_PENDING

使用ERR_IO_PENDING 的简单文本搜索,无法查看它定义的这个常量的位置。相反,要找到定义,必须搜索IO_PENDING。这使得代码难以导航,因此添加到整个代码库的obfuscation

【讨论】:

【解决方案2】:

X-Macros 本质上是参数化的模板。因此,如果您需要多种形式的类似东西,它们是完成这项工作的正确工具。它们允许您创建抽象形式并根据不同的规则对其进行实例化。

我使用 X 宏将枚举值输出为字符串。自从遇到它,我非常喜欢这种形式,它需要一个“用户”宏来应用于每个元素。使用多个文件包含要痛苦得多。

/* x-macro constructors for error and type
   enums and string tables */
#define AS_BARE(a) a ,
#define AS_STR(a) #a ,

#define ERRORS(_) \
    _(noerror) \
    _(dictfull) _(dictstackoverflow) _(dictstackunderflow) \
    _(execstackoverflow) _(execstackunderflow) _(limitcheck) \
    _(VMerror)
enum err { ERRORS(AS_BARE) };
char *errorname[] = { ERRORS(AS_STR) };
/* puts(errorname[(enum err)limitcheck]); */

我还将它们用于基于对象类型的函数调度。再次通过劫持我用来创建枚举值的同一个宏。

#define TYPES(_) \
    _(invalid) \
    _(null) \
    _(mark) \
    _(integer) \
    _(real) \
    _(array) \
    _(dict) \
    _(save) \
    _(name) \
    _(string) \
/*enddef TYPES */

#define AS_TYPE(_) _ ## type ,
enum { TYPES(AS_TYPE) };

使用宏可以保证我的所有数组索引都与关联的枚举值匹配,因为它们使用宏定义(TYPES 宏)中的裸标记来构造各种形式。

typedef void evalfunc(context *ctx);

void evalquit(context *ctx) { ++ctx->quit; }

void evalpop(context *ctx) { (void)pop(ctx->lo, adrent(ctx->lo, OS)); }

void evalpush(context *ctx) {
    push(ctx->lo, adrent(ctx->lo, OS),
            pop(ctx->lo, adrent(ctx->lo, ES)));
}

evalfunc *evalinvalid = evalquit;
evalfunc *evalmark = evalpop;
evalfunc *evalnull = evalpop;
evalfunc *evalinteger = evalpush;
evalfunc *evalreal = evalpush;
evalfunc *evalsave = evalpush;
evalfunc *evaldict = evalpush;
evalfunc *evalstring = evalpush;
evalfunc *evalname = evalpush;

evalfunc *evaltype[stringtype/*last type in enum*/+1];
#define AS_EVALINIT(_) evaltype[_ ## type] = eval ## _ ;
void initevaltype(void) {
    TYPES(AS_EVALINIT)
}

void eval(context *ctx) {
    unsigned ades = adrent(ctx->lo, ES);
    object t = top(ctx->lo, ades, 0);
    if ( isx(t) ) /* if executable */
        evaltype[type(t)](ctx);  /* <--- the payoff is this line here! */
    else
        evalpush(ctx);
}

以这种方式使用 X 宏实际上有助于编译器提供有用的错误消息。我从上面省略了 evalarray 函数,因为它会分散我的注意力。但是,如果您尝试编译上述代码(当然,注释掉其他函数调用,并为上下文提供一个虚拟 typedef),编译器会抱怨缺少函数。对于我添加的每个新类型,我都会在重新编译此模块时提醒我添加一个处理程序。因此,X-macro 有助于确保并行结构在项目发展时保持不变。

编辑:

这个答案使我的声誉提高了 50%。所以这里还有一点。下面是一个反例,回答问题:什么时候使用X-Macros?

这个例子展示了将任意代码片段打包到 X-“记录”中。我最终放弃了项目的这个分支,并没有在以后的设计中使用这种策略(也不是因为不想尝试)。不知何故,它变得笨拙。事实上,宏被命名为 X6,因为在某一时刻有 6 个参数,但我厌倦了更改宏名称。

/* Object types */
/* "'X'" macros for Object type definitions, declarations and initializers */
// a                      b            c              d
// enum,                  string,      union member,  printf d
#define OBJECT_TYPES \
X6(    nulltype,        "null",     int dummy      ,            ("<null>")) \
X6(    marktype,        "mark",     int dummy2      ,           ("<mark>")) \
X6( integertype,     "integer",     int  i,     ("%d",o.i)) \
X6( booleantype,     "boolean",     bool b,     (o.b?"true":"false")) \
X6(    realtype,        "real",     float f,        ("%f",o.f)) \
X6(    nametype,        "name",     int  n,     ("%s%s", \
        (o.flags & Fxflag)?"":"/", names[o.n])) \
X6(  stringtype,      "string",     char *s,        ("%s",o.s)) \
X6(    filetype,        "file",     FILE *file,     ("<file %p>",(void *)o.file)) \
X6(   arraytype,       "array",     Object *a,      ("<array %u>",o.length)) \
X6(    dicttype,        "dict",     struct s_pair *d, ("<dict %u>",o.length)) \
X6(operatortype,    "operator",     void (*o)(),    ("<op>")) \

#define X6(a, b, c, d) #a,
char *typestring[] = { OBJECT_TYPES };
#undef X6

// the Object type
//forward reference so s_object can contain s_objects
typedef struct s_object Object;

// the s_object structure:
// a bit convoluted, but it boils down to four members:
// type, flags, length, and payload (union of type-specific data)
// the first named union member is integer, so a simple literal object
// can be created on the fly:
// Object o = {integertype,0,0,4028}; //create an int object, value: 4028
// Object nl = {nulltype,0,0,0};
struct s_object {
#define X6(a, b, c, d) a,
    enum e_type { OBJECT_TYPES } type;
#undef X6
unsigned int flags;
#define Fread  1
#define Fwrite 2
#define Fexec  4
#define Fxflag 8
size_t length; //for lint, was: unsigned int
#define X6(a, b, c, d) c;
    union { OBJECT_TYPES };
#undef X6
};

一个大问题是 printf 格式字符串。虽然它看起来很酷,但它只是一个骗局。由于它只用在一个函数中,过度使用宏实际上分离了应该在一起的信息;它使函数本身不可读。在像这样的调试功能中,混淆是双重不幸的。

//print the object using the type's format specifier from the macro
//used by O_equal (ps: =) and O_equalequal (ps: ==)
void printobject(Object o) {
    switch (o.type) {
#define X6(a, b, c, d) \
        case a: printf d; break;
OBJECT_TYPES
#undef X6
    }
}

所以不要得意忘形。和我一样。

【讨论】:

  • 我一直在研究几个不同的库来处理 C 中的“对象”——比如 Cello 和 GObject,但它们都对我的口味有点远。这篇文章和你的 Github 代码另一方面 - 很棒的东西,谢谢你的灵感。 :)
  • 很高兴听到这个消息。我也研究了这些,并查看了 Lisp 1.1 手册。我最近制作的一组对象是parser combinators。我在那里得到了非常小而简单的 GC。请务必让我知道您正在构建什么。这种东西似乎总是会产生一些很酷的东西。 :)
【解决方案3】:

几年前,当我开始在我的代码中使用函数指针时,我发现了 X-macros。我是一名嵌入式程序员,经常使用状态机。我经常会写这样的代码:

/* declare an enumeration of state codes */
enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX};

问题在于,我认为必须维护函数指针表的顺序以使其与状态枚举的顺序相匹配,这很容易出错。

我的一个朋友向我介绍了 X-macros,这就像一个灯泡在我脑海中熄灭了。说真的,我这辈子 x 宏你都去哪儿了!

所以现在我定义下表:

#define STATE_TABLE \
        ENTRY(STATE0, func0) \
        ENTRY(STATE1, func1) \
        ENTRY(STATE2, func2) \
        ...
        ENTRY(STATEX, funcX) \

我可以按如下方式使用它:

enum
{
#define ENTRY(a,b) a,
    STATE_TABLE
#undef ENTRY
    NUM_STATES
};

p_func_t jumptable[NUM_STATES] =
{
#define ENTRY(a,b) b,
    STATE_TABLE
#undef ENTRY
};

作为奖励,我还可以让预处理器构建我的函数原型,如下所示:

#define ENTRY(a,b) static void b(void);
    STATE_TABLE
#undef ENTRY

另一种用法是声明和初始化寄存器

#define IO_ADDRESS_OFFSET (0x8000)
#define REGISTER_TABLE\
    ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)\
    ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)\
    ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)\
    ...
    ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)\

/* declare the registers (where _at_ is a compiler specific directive) */
#define ENTRY(a, b, c) volatile uint8_t a _at_ b:
    REGISTER_TABLE
#undef ENTRY

/* initialize registers */
#define ENTRY(a, b, c) a = c;
    REGISTER_TABLE
#undef ENTRY

然而,我最喜欢的用法是在通信处理程序方面

首先我创建一个 comms 表,其中包含每个命令名称和代码:

#define COMMAND_TABLE \
    ENTRY(RESERVED,    reserved,    0x00) \
    ENTRY(COMMAND1,    command1,    0x01) \
    ENTRY(COMMAND2,    command2,    0x02) \
    ...
    ENTRY(COMMANDX,    commandX,    0x0X) \

我在表中同时使用了大写和小写的名称,因为大写将用于枚举,而小写将用于函数名。

然后我还为每个命令定义结构来定义每个命令的样子:

typedef struct {...}command1_cmd_t;
typedef struct {...}command2_cmd_t;

etc.

同样,我为每个命令响应定义结构:

typedef struct {...}command1_resp_t;
typedef struct {...}command2_resp_t;

etc.

然后我可以定义我的命令代码枚举:

enum
{
#define ENTRY(a,b,c) a##_CMD = c,
    COMMAND_TABLE
#undef ENTRY
};

我可以定义我的命令长度枚举:

enum
{
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t);
    COMMAND_TABLE
#undef ENTRY
};

我可以定义我的响应长度枚举:

enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t);
    COMMAND_TABLE
#undef ENTRY
};

我可以确定有多少命令如下:

typedef struct
{
#define ENTRY(a,b,c) uint8_t b;
    COMMAND_TABLE
#undef ENTRY
} offset_struct_t;

#define NUMBER_OF_COMMANDS sizeof(offset_struct_t)

注意:我从未实际实例化 offset_struct_t,我只是将它用作编译器为我生成命令数量定义的一种方式。

注意,我可以按如下方式生成函数指针表:

p_func_t jump_table[NUMBER_OF_COMMANDS] = 
{
#define ENTRY(a,b,c) process_##b,
    COMMAND_TABLE
#undef ENTRY
}

还有我的函数原型:

#define ENTRY(a,b,c) void process_##b(void);
    COMMAND_TABLE
#undef ENTRY

现在终于有了最酷的用途,我可以让编译器计算我的传输缓冲区应该有多大。

/* reminder the sizeof a union is the size of its largest member */
typedef union
{
#define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)];
    COMMAND_TABLE
#undef ENTRY
}tx_buf_t

同样,这个联合就像我的偏移结构,它没有被实例化,相反我可以使用 sizeof 运算符来声明我的传输缓冲区大小。

uint8_t tx_buf[sizeof(tx_buf_t)];

现在我的传输缓冲区 tx_buf 是最佳大小,当我向此通信处理程序添加命令时,我的缓冲区将始终是最佳大小。酷!

另一个用途是创建偏移表: 由于内存通常是嵌入式系统的一个约束,所以当它是一个稀疏数组时,我不想为我的跳转表使用 512 个字节(每个指针 2 个字节 X 256 个可能的命令)。相反,我将为每个可能的命令提供一个 8 位偏移量表。然后使用这个偏移量来索引我的实际跳转表,现在只需要 NUM_COMMANDS * sizeof(pointer)。就我而言,定义了 10 个命令。我的跳转表是 20 字节长,我有一个 256 字节长的偏移表,总共是 276 字节而不是 512 字节。然后我像这样调用我的函数:

jump_table[offset_table[command]]();

而不是

jump_table[command]();

我可以像这样创建一个偏移表:

/* initialize every offset to 0 */
static uint8_t offset_table[256] = {0};

/* for each valid command, initialize the corresponding offset */
#define ENTRY(a,b,c) offset_table[c] = offsetof(offset_struct_t, b);
    COMMAND_TABLE
#undef ENTRY

其中 offsetof 是“stddef.h”中定义的标准库宏

作为附带的好处,有一种非常简单的方法可以确定是否支持命令代码:

bool command_is_valid(uint8_t command)
{
    /* return false if not valid, or true (non 0) if valid */
    return offset_table[command];
}

这也是我在 COMMAND_TABLE 中保留命令字节 0 的原因。我可以创建一个名为“process_reserved()”的函数,如果任何无效命令字节用于索引我的偏移表,该函数将被调用。

【讨论】:

  • 哇!我谦卑地接受这个优越的答案。 (但您应该考虑“用户宏”样式:无需取消定义任何内容,无需记住内部“变量”名称。)
  • 非常感谢,今天学到了新东西。现在,我可以执行以下操作,而不是我所有的 #define 和 #undef:REGISTERTABLE(AS_DECLARATION) REGISTERTABLE(AS_INITIALIZER) 非常酷!
  • “说真的,我这辈子 x 宏你都去哪儿了!”潜伏在地狱,等待一些毫无戒心的程序员召唤他们,很可能。在现代 C 中,您可以像这样在跳转表和枚举之间创建直接、紧密的耦合:p_func_t jumptable[] = { [STATE0] = func0, [STATE1] = func1 };。注意数组大小的[]。现在为确保没有任何项目丢失,请添加编译时检查:_Static_assert(NUM_STATES == sizeof jumptable/sizeof *jumptable, "error");。键入安全、易读,而不是一个宏。
  • 我的意思是,x 宏应该是最后的手段,而不是当您遇到一些程序设计问题时首先想到的。
【解决方案4】:

我喜欢使用 X 宏来创建“丰富的枚举”,它支持迭代枚举值以及获取每个枚举值的字符串表示:

#define MOUSE_BUTTONS \
X(LeftButton, 1)   \
X(MiddleButton, 2) \
X(RightButton, 4)

struct MouseButton {
  enum Value {
    None = 0
#define X(name, value) ,name = value
MOUSE_BUTTONS
#undef X
  };

  static const int *values() {
    static const int a[] = {
      None,
#define X(name, value) name,
    MOUSE_BUTTONS
#undef X
      -1
    };
    return a;
  }

  static const char *valueAsString( Value v ) {
#define X(name, value) static const char str_##name[] = #name;
MOUSE_BUTTONS
#undef X
    switch ( v ) {
      case None: return "None";
#define X(name, value) case name: return str_##name;
MOUSE_BUTTONS
#undef X
    }
    return 0;
  }
};

这不仅定义了一个 MouseButton::Value 枚举,它还让我可以做类似的事情

// Print names of all supported mouse buttons
for ( const int *mb = MouseButton::values(); *mb != -1; ++mb ) {
    std::cout << MouseButton::valueAsString( (MouseButton::Value)*mb ) << "\n";
}

【讨论】:

    【解决方案5】:

    我使用一个非常庞大的 X 宏将 INI 文件的内容加载到配置结构中,以及围绕该结构旋转的其他内容。

    这就是我的“configuration.def”文件的样子:

    #define NMB_DUMMY(...) X(__VA_ARGS__)
    #define NMB_INT_DEFS \
       TEXT("long int") , long , , , GetLongValue , _ttol , NMB_SECT , SetLongValue , 
    
    #define NMB_STR_DEFS NMB_STR_DEFS__(TEXT("string"))
    #define NMB_PATH_DEFS NMB_STR_DEFS__(TEXT("path"))
    
    #define NMB_STR_DEFS__(ATYPE) \
      ATYPE ,  basic_string<TCHAR>* , new basic_string<TCHAR>\
      , delete , GetValue , , NMB_SECT , SetValue , *
    
    /* X-macro starts here */
    
    #define NMB_SECT "server"
    NMB_DUMMY(ip,TEXT("Slave IP."),TEXT("10.11.180.102"),NMB_STR_DEFS)
    NMB_DUMMY(port,TEXT("Slave portti."),TEXT("502"),NMB_STR_DEFS)
    NMB_DUMMY(slaveid,TEXT("Slave protocol ID."),0xff,NMB_INT_DEFS)
    .
    . /* And so on for about 40 items. */
    

    这有点令人困惑,我承认。很快就会清楚,我实际上并不想在每个字段宏之后编写所有这些类型声明。 (别担心,有一个很大的注释来解释我为简洁而省略的所有内容。)

    这就是我声明配置结构的方式:

    typedef struct {
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) TYPE ID;
    #include "configuration.def"
    #undef X
      basic_string<TCHAR>* ini_path;  //Where all the other stuff gets read.
      long verbosity;                 //Used only by console writing functions.
    } Config;
    

    然后,在代码中,首先将默认值读入配置结构:

    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,...) \
      conf->ID = CONSTRUCTOR(DEFVAL);
    #include "configuration.def"
    #undef X
    

    然后,使用库 SimpleIni 将 INI 读入配置结构如下:

    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,DEREF...)\
      DESTRUCTOR (conf->ID);\
      conf->ID  = CONSTRUCTOR( ini.GETTER(TEXT(SECT),TEXT(#ID),DEFVAL,FALSE) );\
      LOG3A(<< left << setw(13) << TEXT(#ID) << TEXT(": ")  << left << setw(30)\
        << DEREF conf->ID << TEXT(" (") << DEFVAL << TEXT(").") );
    #include "configuration.def"
    #undef X
    

    命令行标志的覆盖,也被格式化为相同的名称(在 GNU 长格式中),使用库 SimpleOpt 以如下方式应用:

    enum optflags {
    #define X(ID,...) ID,
    #include "configuration.def"
    #undef X
      };
      CSimpleOpt::SOption sopt[] = {
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) {ID,TEXT("--") #ID TEXT("="), SO_REQ_CMB},
    #include "configuration.def"
    #undef X
        SO_END_OF_OPTIONS
      };
      CSimpleOpt ops(argc,argv,sopt,SO_O_NOERR);
      while(ops.Next()){
        switch(ops.OptionId()){
    #define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,...) \
      case ID:\
        DESTRUCTOR (conf->ID);\
        conf->ID = STRCONV( CONSTRUCTOR (  ops.OptionArg() ) );\
        LOG3A(<< TEXT("Omitted ")<<left<<setw(13)<<TEXT(#ID)<<TEXT(" : ")<<conf->ID<<TEXT(" ."));\
        break;
    #include "configuration.def"
    #undef X
        }
      }
    

    等等,我也使用相同的宏来打印 --help -flag 输出和示例默认 ini 文件,configuration.def 在我的程序中包含 8 次。 “方钉成圆孔”,也许;一个真正称职的程序员将如何处理这个问题?很多很多循环和字符串处理?

    【讨论】:

      【解决方案6】:

      https://github.com/whunmr/DataEx

      我正在使用以下 xmacros 生成一个 C++ 类,内置序列化和反序列化功能。

      #define __FIELDS_OF_DataWithNested(_)  \
        _(1, a, int  )                       \
        _(2, x, DataX)                       \
        _(3, b, int  )                       \
        _(4, c, char )                       \
        _(5, d, __array(char, 3))            \
        _(6, e, string)                      \
        _(7, f, bool)
      
      DEF_DATA(DataWithNested);
      

      用法:

      TEST_F(t, DataWithNested_should_able_to_encode_struct_with_nested_struct) {
          DataWithNested xn;
          xn.a = 0xCAFEBABE;
          xn.x.a = 0x12345678;
          xn.x.b = 0x11223344;
          xn.b = 0xDEADBEEF;
          xn.c = 0x45;
          memcpy(&xn.d, "XYZ", strlen("XYZ"));
      
          char buf_with_zero[] = {0x11, 0x22, 0x00, 0x00, 0x33};
          xn.e = string(buf_with_zero, sizeof(buf_with_zero));
          xn.f = true;
      
          __encode(DataWithNested, xn, buf_);
      
          char expected[] = { 0x01, 0x04, 0x00, 0xBE, 0xBA, 0xFE, 0xCA,
                              0x02, 0x0E, 0x00 /*T and L of nested X*/,
                              0x01, 0x04, 0x00, 0x78, 0x56, 0x34, 0x12,
                              0x02, 0x04, 0x00, 0x44, 0x33, 0x22, 0x11,
                              0x03, 0x04, 0x00, 0xEF, 0xBE, 0xAD, 0xDE,
                              0x04, 0x01, 0x00, 0x45,
                              0x05, 0x03, 0x00, 'X', 'Y', 'Z',
                              0x06, 0x05, 0x00, 0x11, 0x22, 0x00, 0x00, 0x33,
                              0x07, 0x01, 0x00, 0x01};
      
          EXPECT_TRUE(ArraysMatch(expected, buf_));
      }
      

      另外,另一个例子是https://github.com/whunmr/msgrpc

      【讨论】:

        【解决方案7】:

        Chromium 在dom_code_data.inc 有一个有趣的 X 宏变体。除了它不仅仅是一个宏,而是一个完全独立的文件。 此文件用于不同平台的扫描码、USB HID 码和类似字符串的名称之间的键盘输入映射。

        该文件包含如下代码:

        DOM_CODE_DECLARATION {
        
          //            USB     evdev    XKB     Win     Mac   Code
          DOM_CODE(0x000000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NONE), // Invalid
        ...
        };
        

        每个宏调用实际上传入 7 个参数,宏可以选择使用哪些参数,忽略哪些参数。 One usage 是操作系统键码和平台无关的扫描码和 DOM 字符串之间的映射。在不同的操作系统上使用不同的宏来选择适合该操作系统的键码。

        // Table of USB codes (equivalent to DomCode values), native scan codes,
        // and DOM Level 3 |code| strings.
        #if defined(OS_WIN)
        #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
          { usb, win, code }
        #elif defined(OS_LINUX)
        #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
          { usb, xkb, code }
        #elif defined(OS_MACOSX)
        #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
          { usb, mac, code }
        #elif defined(OS_ANDROID)
        #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
          { usb, evdev, code }
        #else
        #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
          { usb, 0, code }
        #endif
        #define DOM_CODE_DECLARATION const KeycodeMapEntry usb_keycode_map[] =
        #include "ui/events/keycodes/dom/dom_code_data.inc"
        #undef DOM_CODE
        #undef DOM_CODE_DECLARATION
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-01-21
          • 2013-10-05
          • 2011-12-04
          • 2015-03-04
          • 2013-12-30
          • 1970-01-01
          相关资源
          最近更新 更多