iOS/C: 将"integer"转换为四个字符的字符串

17

与音频会话编程相关的许多常量实际上是四个字符的字符串(音频会话服务参考)。同样的情况也适用于像AudioSessionGetProperty这样的函数返回的OSStatus code

问题在于,当我尝试打印这些内容时,它们看起来像1919902568。我可以将其输入计算器并开启ASCII输出,它会告诉我"roch",但肯定有一种编程的方式来做到这一点。

在我的一个C函数中,我已经有了一些有限的成功,以下是相关代码块:

char str[20];
// see if it appears to be a four-character code
*(UInt32 *) (str + 1) = CFSwapInt32HostToBig(error);
if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
    str[0] = str[5] = '\'';
    str[6] = '\0';
} else {
    // no, format as integer
    sprintf(str, "%d", (int)error);
}

我想做的是将这个功能从其当前函数中抽象出来,以便在其他地方使用它。我试过了。
char * fourCharCode(UInt32 code) {
    // block
}
void someOtherFunction(UInt32 foo){
    printf("%s\n",fourCharCode(foo));
}

但是那给我“à*€/3íT:ê*€/+€/”,而不是“roch”。我的C语言能力不是很强,但我猜测上面的代码试图将内存地址解释为字符串。或者可能存在编码问题?有什么想法吗?

16个回答

20
你所讲的类型是FourCharCode,在CFBase.h中定义。它等同于OSType。在OSTypeNSString之间进行转换最简单的方法是使用NSFileTypeForHFSTypeCode()NSHFSTypeCodeFromFileType()。不幸的是,这些函数在iOS上不可用。
对于iOS和Cocoa-portable代码,我喜欢来自Joachim BengtssonFourCC2Str(),从他的NCCommon.h(加一些强制类型转换以便更轻松地使用):
#include <TargetConditionals.h>
#if TARGET_RT_BIG_ENDIAN
#   define FourCC2Str(fourcc) (const char[]){*((char*)&fourcc), *(((char*)&fourcc)+1), *(((char*)&fourcc)+2), *(((char*)&fourcc)+3),0}
#else
#   define FourCC2Str(fourcc) (const char[]){*(((char*)&fourcc)+3), *(((char*)&fourcc)+2), *(((char*)&fourcc)+1), *(((char*)&fourcc)+0),0}
#endif

FourCharCode code = 'APPL';
NSLog(@"%s", FourCC2Str(code));
NSLog(@"%@", @(FourCC2Str(code));

当然,你可以将@()加入到宏中,以便更轻松地使用。


哦,那听起来真的很有用。不过它直接给我一个“预期表达式”的错误,所以我得回头再看看这个。 - Spencer Williams
已经进行了清理,使其更易于使用。它返回一个char[]而不是char*,这似乎现在让事情变得混乱了。 - Rob Napier
2
NSFileTypeForHFSTypeCode 对于 1634039412 的烦人返回值是一个包含单引号的 6 个字符长度的字符串 "\'aevt\'"。:/ - pkamb

7

在Swift中,您将使用此函数:

func str4 (n: Int) -> String
{
    var s: String = ""
    var i: Int = n

    for var j: Int = 0; j < 4; ++j
    {
        s = String(UnicodeScalar(i & 255)) + s
        i = i / 256
    }

    return (s)
}

这个函数将在三分之一的时间内完成与上述相同的操作:
func str4 (n: Int) -> String
{
    var s: String = String (UnicodeScalar((n >> 24) & 255))
    s.append(UnicodeScalar((n >> 16) & 255))
    s.append(UnicodeScalar((n >> 8) & 255))
    s.append(UnicodeScalar(n & 255))
    return (s)
}

反过来的方式是:
func val4 (s: String) -> Int
{
    var n: Int = 0
    var r: String = ""
    if (countElements(s) > 4)
    {
        r = s.substringToIndex(advance(s.startIndex, 4))
    }
    else
    {
        r = s + "    "
        r = r.substringToIndex(advance(r.startIndex, 4))
    }
    for UniCodeChar in r.unicodeScalars
    {
        n = (n << 8) + (Int(UniCodeChar.value) & 255)
    }

    return (n)
}

为了简化您的一个函数 [code]func stringValue(unicodeValue: Int) -> String { var stringValue = "" var value = unicodeValuefor _ in 0..<4 { stringValue = String(UnicodeScalar(value & 255)) + stringValue value = value / 256 } return stringValue}[\code] - TheCodingArt
另一种方法:扩展Int { var unicodeStringValue:String { get { var stringValue = "" var value = self for _ in 0..<4 { stringValue = String(UnicodeScalar(value & 255)) + stringValue value = value / 256 } return stringValue } }} - TheCodingArt

4
char str[5];
str[4] = '\0';
long *code = (long *)str;
*code = 1919902568;
printf("%s\n", str);

1
在找到一个reverseStr函数之后,这个代码可以工作。我真的很想得到一个更DRY的解决方案(比如将其封装在一个方法中),但现在它已经足够了。 - Spencer Williams
long *code = (long *)str; *code = 1919902568; 是一种严重的别名违规行为,并会引发未定义的行为。此外,仅仅创建指针 long *code = (long *)str; 如果不满足任何特定平台的对齐要求,也可能引发未定义的行为 - 需要注意的是,在这种情况下,未定义行为会立即在指针创建时触发,而不需要进行解引用操作来触发未定义行为。 - Andrew Henle

4

整型 = 4字节 = 4个字符。因此,要将整型转换为char *,您可以简单地编写:

char st[5] = {0};
st[0] = yourInt & 0xff;
st[1] = (yourInt >> 8) & 0xff;
st[2] = (yourInt >> 16) & 0xff;
st[3] = (yourInt >> 24) & 0xff;

将其转换回来:
yourInt = st[0] | (st[1] << 8) | (st[2] << 16) | (st[3] << 24);

如果有人想打印这个字符串,加上st[4] = '\0';不是很好吗? - Jan Rüegg
1
看起来很简单,但方法是错误的。第一个字符是最高字节。#define FourCC2Str(code) (char[5]){(code >> 24) & 0xFF, (code >> 16) & 0xFF, (code >> 8) & 0xFF, code & 0xFF, 0} - Paul B.
现在大多数CPU,包括英特尔的CPU,都是小端序的,所以这并没有错。 - Nickolay Olshevsky
不建议这样做,因为你不知道 st[4] 是否可以更改。它在存储 FourBytesCode 的内存之外。请记住,这些几乎总是简单的 Int32 变量,而不是“内存块”,你正在清零的字节可能很容易成为另一个变量的一部分或者立即导致程序崩溃。 - Motti Shneor
@JanRüegg st[4]已经由= {0}初始化成了\0。 @MottiShneor为什么一旦st[4]在堆栈上就不能是你的? - Nickolay Olshevsky

4

我为我的音频代码编写了这个C函数...它可能有点天真,但对我来说已经足够好了:

NSString* fourCharNSStringForFourCharCode(FourCharCode aCode){

char fourChar[5] = {(aCode >> 24) & 0xFF, (aCode >> 16) & 0xFF, (aCode >> 8) & 0xFF, aCode & 0xFF, 0};

NSString *fourCharString = [NSString stringWithCString:fourChar encoding:NSUTF8StringEncoding];

return fourCharString; }

3

如果你是在开发 macOS 而不是 iOS,那么在 Launch Services 中已经有一个内置的函数可以实现这个功能。要将 4cc 转换为 NSString,请使用以下代码:

(__bridge_transfer NSString *)UTCreateStringForOSType(fourccInt)

与Charlesism的现有答案相同。 - matt
没有任何人回答过这个名字,也没有提到UTCreateStringForOSType/()的答案。 - svth
1
他在10小时前删除了它。尽管如此,它适用于macOS,并且对我编写macOS应用程序非常有帮助。正如他的答案所说,然而,原始问题是关于iOS的,其中UTCreateStringForOSType - 因此您的答案也受到该问题的影响。 - matt
1
到目前为止,这是最好的答案。原因:它是使用意图创建和维护的,由 Apple 开发,因此可以保证未来更改的安全性。此外,我相信它应该很有效,并且它的名称很具描述性。一直在各地寻找这个。 - Motti Shneor
1
很遗憾,苹果在macOS 12中已经弃用了它。唉。 - dmaclach

3

我建议使用这样的一个函数:

static NSString * NSStringFromCode(UInt32 code)
{
    UInt8 chars[4];
    *(UInt32 *)chars = code;
    for(UInt32 i = 0; i < 4; ++i)
    {
        if(!isprint(chars[i]))
        {
            return [NSString stringWithFormat:@"%u", code];
        }
    }
    return [NSString stringWithFormat:@"%c%c%c%c", chars[3], chars[2], chars[1], chars[0]];
}

这将确保您不会得到一些随机的结果,对于一些实际是数字的FourCharCodes,比如kCMPixelFormat_32ARGB = 32

3

这是我在测试目标中使用的辅助函数:

Swift 5:

extension FourCharCode {
    private static let bytesSize = MemoryLayout<Self>.size
    var codeString: String {
        get {
            withUnsafePointer(to: bigEndian) { pointer in
                pointer.withMemoryRebound(to: UInt8.self, capacity: Self.bytesSize) { bytes in
                    String(bytes: UnsafeBufferPointer(start: bytes,
                                                      count: Self.bytesSize),
                           encoding: .macOSRoman)!
                }
            }
        }
    }
}

extension OSStatus {
    var codeString: String {
        FourCharCode(bitPattern: self).codeString
    }
}

private func fourChars(_ string: String) -> String? {
    string.count == MemoryLayout<FourCharCode>.size ? string : nil
}
private func fourBytes(_ string: String) -> Data? {
    fourChars(string)?.data(using: .macOSRoman, allowLossyConversion: false)
}
func stringCode(_ string: String) -> FourCharCode {
    fourBytes(string)?.withUnsafeBytes { $0.load(as: FourCharCode.self).byteSwapped } ?? 0
}

这个方法在我的macOS和iOS上都能工作,并且与内置的macOS NSFileTypeForHFSTypeCodeNSHFSTypeCodeFromFileType的行为非常相似。

需要注意以下几点:

  1. 使用bigEndianbyteSwapped
  2. 使用macOSRoman编码
  3. 处理长字符串时返回0,就像NSHFSTypeCodeFromFileType一样

这种实现与标准库方法的不同之处在于:

  1. NSFileTypeForHFSTypeCode在字符串周围添加额外的单引号:NSFileTypeForHFSTypeCode(OSType(kAudioCodecBadDataError))) == "'bada'"。而我选择只返回bada
  2. 它无法生成中间带有\0的字符串,例如NSFileTypeForHFSTypeCode(kAudioFormatMicrosoftGSM)应该是ms\01,但它返回了'ms

2

一个更Swifty的实现,适用于Swift 4:

extension String {
    init(fourCharCode: FourCharCode) {
        let n = Int(fourCharCode)
        var s: String = ""

        let unicodes = [UnicodeScalar((n >> 24) & 255), UnicodeScalar((n >> 16) & 255), UnicodeScalar((n >> 8) & 255), UnicodeScalar(n & 255)]
        unicodes.flatMap { (unicode) -> String? in
            guard let unicode = unicode else { return nil }
            return String(unicode)
        }.forEach { (unicode) in
            s.append(unicode)
        }

        self = s.trimmingCharacters(in: CharacterSet.whitespaces)
    }
}

以下是使用方法:

String(fourCharCode: CMFormatDescriptionGetMediaSubType(charCode))

2
最初的回答:
此代码可处理不同字节序架构,适用于iOS和MacOS等操作系统。易于理解且生成的字符串(应该)与OSType相同,即使有8位字符也是如此。
#import <Cocoa/Cocoa.h>
#import <CoreServices/CoreServices.h> // for EndianU32_NtoB
@implementation NSString (FourCC)

+ (NSString *) stringFromFourCC: (OSType) cccc
{
    NSString * result = nil;
    cccc = EndianU32_NtoB(cccc); // convert to network byte order, if needed
    NSData * data = [NSData dataWithBytes: &cccc length: sizeof(OSType)];
    result = [[NSString alloc] initWithData: data encoding: NSWindowsCP1252StringEncoding]; // lossless 8-bit encoding, could also use NSMacOSRomanStringEncoding
    return result;
}

请查看前面的文本框和完整的注释。如果不够清楚,请提出具体问题。 - pawneebill
我认为你可以避免创建NSData包装对象,直接使用以下方法从“bytes”构建NSString:- (nullable instancetype)initWithBytes:(const void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding; - Motti Shneor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接