3

以前,这是不可能的(您必须手动将其全部写出/创建一个静态数组/将所有值放入字典并读回它们......等等)

但是我注意到最新的 Xcode 的 lldb(4.6,也可能是更早的版本)会自动将枚举常量转换为字符串。

我的问题是我们使用了很多库——包括苹果自己的!- 使用恼人的公共枚举,没有提供“值到字符串”的方法。所以我最终不得不(很多很多次)做“好吧,既然图书馆作者先生没有这样做,现在我必须为他们制作静态数组......”。

我一直希望苹果能提供一种解决方法——它终于来了吗?或者这是只有调试器才能做到的一些技巧 - 仅仅运行时代码无法访问它?

4

2 回答 2

3

lldb 没有任何关于打印枚举名称的特殊功能。我认为您所看到的是枚举值记录在调试信息中(或不记录)的结果。例如,

enum myenums {a = 0, b, c};
int main ()
{
 enum myenums var = b;
 return (int) var;  // break here
}

% xcrun clang -g a.c
% xcrun lldb a.out
(lldb) br s -p break
Breakpoint 1: where = a.out`main + 18 at a.c:5, address = 0x0000000100000f92
(lldb) r
[...]
-> 5     return (int) var;  // break here
   6    }
(lldb) p var
(myenums) $0 = b
(lldb) p (myenums) 0
(myenums) $1 = a
(lldb) 

如果您查看此二进制文件 ( dwarfdump a.out.dSYM) 的调试信息,您会看到变量var的类型是myenums,并且调试信息包括这些枚举类型的值:

0x0000005a:     TAG_enumeration_type [5] *
                 AT_name( "myenums" )
                 AT_byte_size( 0x04 )
                 AT_decl_file( "/private/tmp/a.c" )
                 AT_decl_line( 1 )

0x00000062:         TAG_enumerator [6]  
                     AT_name( "a" )
                     AT_const_value( 0x0000000000000000 )

0x00000068:         TAG_enumerator [6]  
                     AT_name( "b" )
                     AT_const_value( 0x0000000000000001 )

0x0000006e:         TAG_enumerator [6]  
                     AT_name( "c" )
                     AT_const_value( 0x0000000000000002 )

如果我在我的示例文件中添加另一个未在任何地方使用的枚举,

enum myenums {a = 0, b, c};
enum otherenums {d = 0, e, f}; // unused in this CU
int main ()
{
 enum myenums var = b;
 return (int) var;  // break here
}

重新编译并通过 再次查看 DWARF dwarfdump,我找不到任何调试信息描述otherenums- 它未使用(在此编译单元中),因此被省略。

于 2013-08-07T04:53:04.343 回答
3

如前所述,除了在调试器中之外,编译后无法访问常量名称。我想, H2CO3 对TO_STR()宏的建议应该可以帮助您完成大多数用途。

我最近正在研究 libclang 并决定通过解决您对从枚举值转换为字符串的苦差事的抱怨来练习它“(您必须手动将其全部写出来/创建一个静态数组/将所有值放入字典中,然后把它们读回来……等等)”。

这是一个 Python 脚本,它将解析一个 Cocoa 文件,找到枚举(包括用NS_OPTIONSand定义的枚举NS_ENUM),并输出一个数组或一个函数(使用 a switch)来进行映射。array 选项利用称为“指定(数组)初始化程序”的 C 功能,数组成员可以显式与特定索引配对:

int arr[] = { [1] = 2, [3] = 8, [7] = 12 };

请注意,这不是一个稀疏数组——最后一个索引之前的任何未指定索引仍然被创建,并用 0 初始化。这意味着枚举旨在用作位掩码,其值为1 << 21 << 31 << 4等,将为相对较少的使用值创建相当大的数组。在这些情况下,函数可能是更好的选择。

Anonymous enums(我认为至少在 Foundation 中都是单个成员)NSString使用常量的名称直接转换为单个成员。我已经强调了负值常量的自动处理,例如NSOrderedAscending- 数组初始值设定项中的负索引不会编译,但函数/switch替代方法可以正常工作;对于这少数情况,您只需要手动选择即可。

该脚本已在 GitHub 上发布,这里是完整的。MIT 许可证,随心所欲。我很高兴听到任何修改。

#!/usr/bin/env python

"""
CocoaEnumToString

Parse a specified Cocoa header file and emit ObjC code to translate the values
of any enums found within into their names as NSStrings.
"""
# Copyright 2013 Joshua Caswell.
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import itertools
import argparse
import sys
import re
from os import path

from clang import cindex
from clang.cindex import CursorKind

def all_children(node):
    return itertools.chain(iter([node]), *map(all_children, 
                                              node.get_children()))

def all_constant_decls(enum):
    return iter(child for child in all_children(enum) if 
                    child.kind == CursorKind.ENUM_CONSTANT_DECL)

def indent_all_lines(s, indent):
    return '\n'.join(indent + line for line in s.split('\n'))

def format_anonymous(enum, title):
    const_str = 'NSString * const {} = @"{}";\n'
    constants = [const_str.format(title.replace('%e', constant.spelling),
                                  constant.spelling)
                            for constant in all_constant_decls(enum)]
    return "".join(constants)


def format_as_array(enum, title, indent):
    all_members = ['[{0}] = @"{0}"'.format(constant.spelling) 
                        for constant in all_constant_decls(enum)]
    all_members = ",\n".join(all_members)

    title = title.replace('%e', enum.spelling)
    array_str = "NSString * const {}[] = {{\n{}\n}};"
    return array_str.format(title, indent_all_lines(all_members, indent))

def format_as_func(enum, title, indent):
    case_str = 'case {0}:\n{1}return @"{0}";'
    all_cases = [case_str.format(constant.spelling, indent)
                        for constant in all_constant_decls(enum)]
    all_cases.append('default:\n{}@"";'.format(indent))
    all_cases = "\n".join(all_cases)

    switch = "switch( val ){{\n{}\n}}".format(indent_all_lines(all_cases, 
                                                               indent))
    title = title.replace('%e', enum.spelling)
    func_str = "NSString * {}({} val){{\n{}\n}}"
    return func_str.format(title, enum.spelling, 
                           indent_all_lines(switch, indent))


parser = argparse.ArgumentParser(description="Use libclang to find enums in "
                                  "the specified Objective-C file and emit a "
                                  "construct (array or function) that "
                                  "maps between the constant values and "
                                  "their names.")
# This argument must be added to the parser first for its default to override
# that of --arr and --fun
parser.add_argument("-c", "--construct", default="array",
                    help="Specify 'function' or any prefix ('f', 'fun', etc.) "
                         "to emit a function that uses a switch statement for "
                         "the mapping; specify 'array' or any prefix for "
                         "an array (this is the default). Whichever of -c, "
                         "--arr, or --fun occurs last in the argument list "
                         "will dictate the output.")
parser.add_argument("--arr", "--array", action="store_const", const="array",
                    dest="construct", help="Emit an array for the mapping.")
parser.add_argument("-e", "--enums", action="append",
                    help="Specify particular enums to capture; by default "
                    "all enums in the given file are used. This argument may "
                    "be present multiple times. Names which are not found in "
                    "the input file are ignored.")
parser.add_argument("--fun", "--func", "--function", action="store_const", 
                    const="function", dest="construct", 
                    help="Emit a function for the mapping.")
parser.add_argument("-i", "--indent", default="4s",
                    help="Number and type of character to use for indentation."
                    " Digits plus either 't' (for tabs) or 's' (for spaces), "
                    "e.g., '4s', which is the default.")
parser.add_argument("-n", "--name", default="StringFor%e",
                    help="Name for the construct; the prefix will "
# Escape percent sign because argparse is doing some formatting of its own.
                    "be added. Any appearances of '%%e' in this argument will "
                    "be replaced with each enum name. The default is "
                    "'StringFor%%e'.")
parser.add_argument("-o", "--output",
                    help="If this argument is present, output should go to a "
                    "file which will be created at the specified path. An "
                    "error will be raised if the file already exists.")
parser.add_argument("-p", "--prefix", default="",
                    help="Cocoa-style prefix to add to the name of emitted "
                    "construct, e.g. 'NS'")
parser.add_argument("file", help="Path to the file which should be parsed.")


arguments = parser.parse_args()

if "array".startswith(arguments.construct):
    format_enum = format_as_array 
elif "function".startswith(arguments.construct):
    format_enum = format_as_func
else:
   parser.error("Neither 'function' nor 'array' specified for construct.")

match = re.match(r"(\d*)([st])", arguments.indent)
if not match.group(2):
    parser.error("Neither tabs nor spaces specified for indentation.")
else:
    indent_char = '\t' if match.group(2) == 't' else ' '
    indent = indent_char * int(match.group(1) or 1)

if arguments.output:
    if path.exists(arguments.output):
        sys.stderr.write("Error: Requested output file exists: "
                         "{}\n".format(arguments.output))
        sys.exit(1)
    else:
        out_f = open(arguments.output, 'w')
else:
    out_f = sys.stdout

target_file_name = arguments.file


# Ignore the fact that system libclang probably doesn't match the version
# of the Python bindings.
cindex.Config.set_compatibility_check(False)
# Use what's likely to be a newer version than that found in /usr/lib
cindex.Config.set_library_file("/Applications/Xcode.app/Contents/Developer/"
                               "Toolchains/XcodeDefault.xctoolchain/usr/lib/"
                               "libclang.dylib")

# Preprocessor macros that resolve into enums; these are defined in
# NSObjCRuntime.h, but including that directly causes redefinition errors due
# to it being also imported.
ns_options_def = ("NS_OPTIONS(_type, _name)=enum _name : "
                 "_type _name; enum _name : _type")
ns_enum_def = ("NS_ENUM(_type, _name)=enum _name : _type _name; "
              "enum _name : _type")

tu = cindex.TranslationUnit.from_source(target_file_name, 
                                        args=["-ObjC", "-D", ns_enum_def,
                                              "-D", ns_options_def, "-D",
                                              "NS_ENUM_AVAILABLE="])


enums = [node for node in all_children(tu.cursor) if 
                node.kind == CursorKind.ENUM_DECL and
                node.location.file.name.find(target_file_name) != -1]
if arguments.enums:
    enums = filter(lambda enum: enum.spelling in arguments.enums, enums)

title = arguments.prefix + arguments.name

for enum in enums:
    if not enum.spelling:
        out_f.write(format_anonymous(enum, title))
    else:
        out_f.write(format_enum(enum, title, indent))
    out_f.write("\n\n")
于 2013-08-15T19:55:31.623 回答