2009-10-22 67 views
5

今天我被困在stoopid上,因爲我無法將簡單的一段ObjC代碼轉換爲它的Cpp等價物。我有這樣的:什麼是NSString的UTF8String的CFString Equiv?

const UInt8 *myBuffer = [(NSString*)aRequest UTF8String]; 

而且我想這個來替代它:

const UInt8 *myBuffer = (const UInt8 *)CFStringGetCStringPtr(aRequest, kCFStringEncodingUTF8); 

這一切在將覆蓋與CFNetwork的API的一個插座爲例HTTP請求緊張的單元測試。我有工作的ObjC代碼,我試圖移植到C++。我正在逐漸用免費橋接等價物取代NS API電話。到目前爲止,一切都是一對一的。這就像需要完成的最後一部分。

回答

14

這是Cocoa在幕後做所有雜亂的東西的其中一件事情,在你必須自己捲起袖子並自己動手才能做到這一點之前,你從不真正理解事情的複雜程度。

爲什麼它不是'簡單'的簡單答案是因爲NSString(和CFString)處理處理多個字符集,Unicode等等的所有複雜細節,同時呈現一個簡單的統一API來處理字符串。它最好以對象爲導向 - 'how'(NS|CF)String處理具有不同字符串編碼的字符串(UTF8,MacRoman,UTF16,ISO 2022 Japanese等)的細節是一個私有實現細節。這一切都「正常」。

它有助於瞭解[@"..." UTF8String]如何工作。這是一個私人的實現細節,所以這不是福音,而是基於觀察到的行爲。當您發送一個字符串UTF8String消息,該字符串做了近似(沒有實際測試過,所以認爲這是僞代碼,而且也確實簡單的方式做同樣的事情,所以這是過於冗長):

- (const char *)UTF8String 
{ 
    NSUInteger utf8Length = [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 
    NSMutableData *utf8Data = [NSMutableData dataWithLength:utf8Length + 1UL]; 
    char *utf8Bytes = [utf8Data mutableBytes]; 
    [self  getBytes:utf8Bytes 
      maxLength:utf8Length 
      usedLength:NULL 
      encoding:NSUTF8StringEncoding 
      options:0UL 
       range:NSMakeRange(0UL, [self length]) 
     remainingRange:NULL]; 
    return(utf8Bytes); 
} 

因爲NSMutableData是自動發佈的,所以您不必擔心處理緩衝區的內存管理問題,因爲該緩衝區會返回-UTF8String

一個字符串對象可以自由地以任何形式保留字符串內容,所以不能保證它的內部表示是最適合您需要的內容表示(在這種情況下是UTF8)。如果您只使用普通的C語言,那麼您將不得不處理管理某些內存以保存可能需要的任何字符串轉換。曾經是簡單的-UTF8String方法調用現在非常複雜得多。

大多數NSString中/實際上是實現與的CoreFoundation/CFString,所以很明顯有從CFStringRef的路徑 - >-UTF8String。它只是不如NSString-UTF8String簡潔而簡單。大部分的複雜情況都與內存管理有關。下面是我如何在過去解決它:

void someFunction(void) { 
    CFStringRef cfString; // Assumes 'cfString' points to a (NS|CF)String. 

    const char *useUTF8StringPtr = NULL; 
    UInt8 *freeUTF8StringPtr = NULL; 

    CFIndex stringLength = CFStringGetLength(cfString), usedBytes = 0L; 

    if((useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8)) == NULL) { 
    if((freeUTF8StringPtr = malloc(stringLength + 1L)) != NULL) { 
     CFStringGetBytes(cfString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', false, freeUTF8StringPtr, stringLength, &usedBytes); 
     freeUTF8StringPtr[usedBytes] = 0; 
     useUTF8StringPtr = (const char *)freeUTF8StringPtr; 
    } 
    } 

    long utf8Length = (long)((freeUTF8StringPtr != NULL) ? usedBytes : stringLength); 

    if(useUTF8StringPtr != NULL) { 
    // useUTF8StringPtr points to a NULL terminated UTF8 encoded string. 
    // utf8Length contains the length of the UTF8 string. 

    // ... do something with useUTF8StringPtr ... 
    } 

    if(freeUTF8StringPtr != NULL) { free(freeUTF8StringPtr); freeUTF8StringPtr = NULL; } 
} 

注意:我還沒有測試此代碼,但它是從工作的代碼修改。所以,除了明顯的錯誤,我相信它應該起作用。

以上嘗試獲取指向CFString用於存儲字符串內容的緩衝區的指針。如果CFString恰好具有以UTF8編碼的字符串內容(或適當兼容的編碼,如ASCII),那麼很可能CFStringGetCStringPtr()將返回非NULL。這顯然是最好的,最快的案例。如果出於某種原因無法獲得該指針,請說明如果CFString的內容以UTF16編碼,則它將分配一個包含malloc()的緩衝區,該緩衝區的大小足以在將其轉碼爲UTF8時包含整個字符串。然後,在函數結束時,它會檢查內存是否被分配,如果有必要的話。

現在有一些提示和技巧... CFString'傾向於'(這是一個私人實現細節,所以它可以並且確實在版本之間變化)保留'簡單'字符串編碼爲MacRoman,這是一個8位寬編碼。像UTF8一樣,MacRoman是ASCII的超集,因此所有的字符< 128等同於它們的ASCII對應字符(或換句話說,任何字符< 128是ASCII)。在MacRoman中,字符> = 128是'特殊'字符。它們都具有Unicode等價物,並且傾向於是額外的貨幣符號和「擴展的西方」字符。有關更多信息,請參閱Wikipedia - MacRoman。但僅僅因爲CFString表示它是MacRoman(CFString編碼值kCFStringEncodingMacRoman,NSString編碼值NSMacOSRomanStringEncoding)並不意味着它的字符> = 128。如果由CFStringGetCStringPtr()返回的kCFStringEncodingMacRoman編碼的字符串完全由字符< 128組成,則它完全等同於其ASCII(kCFStringEncodingASCII)編碼表示,其也完全等同於字符串UTF8(kCFStringEncodingUTF8)編碼表示。

根據您的要求,當致電CFStringGetCStringPtr()時,您可能可以使用kCFStringEncodingMacRoman而不是kCFStringEncodingUTF8。如果您需要對字符串進行嚴格的UTF8編碼,但使用kCFStringEncodingMacRoman,然後檢查以確保由CFStringGetCStringPtr(string, kCFStringEncodingMacRoman)返回的字符串僅包含< 128的字符,則'可能'(可能)會更快。如果字符串中包含字符> = 128 ,然後通過一個緩衝區來緩存路由,以保存轉換後的結果。例如:

CFIndex stringLength = CFStringGetLength(cfString), usedBytes = 0L; 

useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); 

for(CFIndex idx = 0L; (useUTF8String != NULL) && (useUTF8String[idx] != 0); idx++) { 
    if(useUTF8String[idx] >= 128) { useUTF8String = NULL; } 
} 

if((useUTF8String == NULL) && ((freeUTF8StringPtr = malloc(stringLength + 1L)) != NULL)) { 
    CFStringGetBytes(cfString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', false, freeUTF8StringPtr, stringLength, &usedBytes); 
    freeUTF8StringPtr[usedBytes] = 0; 
    useUTF8StringPtr = (const char *)freeUTF8StringPtr; 
} 

就像我說的,你真的不明白可可到底有多少工作呢自動爲你,直到你必須自己做這一切。:)

+0

現在,這是一個解釋! Thanx Johne!我試過你的代碼,現在我有另一個問題。因爲我以一個「.m」文件開始使用ObjC,所以我能夠快速地模擬一個例子。如今,我將轉換爲C++使用 「.mm」 文件我得到的構建例外: 未定義的符號: 「___gxx_personality_v0」,從引用: ___在libMyNetworking.a(MyLowLevelNetworking.o)gxx_personality_v0 $ non_lazy_ptr LD:符號(s)not found 我仍然覺得蘋果工具有時毫無頭緒...... – Cliff 2009-10-23 13:18:28

0

如果它的目的地是一個插座,或許CFStringGetBytes()將是您的最佳選擇?

另請注意,CFStringGetCStringPtr()該文件說:

此功能可以立即返回所請求的指針,沒有內存分配,沒有複製,在固定時間,或返回NULL。如果後者是結果,則調用替代函數,如CFStringGetCString函數來提取字符。

+0

這就像一百萬個參數一樣。我想我可以填寫它的1080表格,看起來會讓我處於相同的位置。我會立即回覆結果。 – Cliff 2009-10-22 19:59:34

3

documentation

不論這個函數返回一個有效的指針或NULL取決於許多因素,所有這些都依賴於字符串是如何創建和它的屬性。另外,功能結果可能會在不同版本和不同平臺之間發生變化。因此,在任何情況下都不要指望從此函數接收到非NULL結果。

如果CFStringGetCStringPtr返回NULL,則應使用CFStringGetCString

+0

關閉但沒有雪茄。我正在使用: CFStringGetCString(aRequest,myBuffer,[(NSString *)aRequest length],kCFStringEncodingUTF8); 它幾乎可以工作,但第一個字符被截斷。基本上我如何從CFStringRef獲取一個String指針?爲什麼這麼難? – Cliff 2009-10-23 01:45:27

+2

您可能想要使用[aRequest length] +1來說明空終止符。 – ianh 2009-10-23 08:58:04

+0

據我所知,所有麻煩的原因是CFString的內部表示可能不是UTF8,所以可能不會有*原始指針來獲取。 – ianh 2009-10-23 08:59:06

0

下面是對printf CFStringRef的方式,這意味着我們得到從CFStringRef一個「\ 0'結尾的字符串:

// from: http://lists.apple.com/archives/carbon-development/2001/Aug/msg01367.html 
// by Ali Ozer 
// gcc -Wall -O3 -x objective-c -fobjc-exceptions -framework Foundation test.c 

#import <stdio.h> 
#import <Foundation/Foundation.h> 

/* 
This function will print the provided arguments (printf style varargs) out to the console. 
Note that the CFString formatting function accepts "%@" as a way to display CF types. 
For types other than CFString and CFNumber, the result of %@ is mostly for debugging 
and can differ between releases and different platforms. Cocoa apps (or any app which 
links with the Foundation framework) can use NSLog() to get this functionality. 
*/ 

void show(CFStringRef formatString, ...) { 
    CFStringRef resultString; 
    CFDataRef data; 
    va_list argList; 
    va_start(argList, formatString); 
    resultString = CFStringCreateWithFormatAndArguments(NULL, NULL, formatString, argList); 
    va_end(argList); 
    data = CFStringCreateExternalRepresentation(NULL, resultString, 
    CFStringGetSystemEncoding(), '?'); 
    if (data != NULL) { 
     printf ("%.*s\n", (int)CFDataGetLength(data), CFDataGetBytePtr(data)); 
     CFRelease(data); 
    } 
    CFRelease(resultString); 
} 

int main(void) 
{ 

    // To use: 
    int age = 25; 
    CFStringRef name = CFSTR("myname"); 

    show(CFSTR("Name is %@, age is %d"), name, age); 

    return 0; 
} 
4

在上面的示例代碼中,出現以下內容:

CFIndex stringLength = CFStringGetLength(cfString) 

stringLength然後被用於對malloc(),許多字節的暫存緩衝器,加1

但對於CFStringGetLength()頭文件明確地說,它返回NU大量的16位Unicode字符,而不是字節。因此,如果某些Unicode字符超出了ASCII範圍,那麼malloc()緩衝區將不足以保存字符串的UTF-8轉換。

也許我錯過了一些東西,但爲了絕對安全,當它們全部轉換爲UTF-8時,容納N個任意Unicode字符所需的字節數最多爲4 * n。

2

這是一些工作代碼。我從@ johne的回答開始,爲簡單起見,用CFStringGetLength代替CFStringGetBytes,並作出@Doug建議的更正。

const char *useUTF8StringPtr = NULL; 
char *freeUTF8StringPtr = NULL; 

if ((useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8)) == NULL) 
{ 
    CFIndex stringLength = CFStringGetLength(cfString); 
    CFIndex maxBytes = 4 * stringLength + 1; 
    freeUTF8StringPtr = malloc(maxBytes); 
    CFStringGetCString(cfString, freeUTF8StringPtr, maxBytes, kCFStringEncodingUTF8); 
    useUTF8StringPtr = freeUTF8StringPtr; 
} 

// ... do something with useUTF8StringPtr... 

if (freeUTF8StringPtr != NULL) 
    free(freeUTF8StringPtr); 
相關問題