2009-10-22 7 views
5

Ich stehe heute auf stoopid fest, da ich ein einfaches Stück ObjC-Code nicht in sein Cpp-Äquivalent konvertieren kann. Ich habe dies:Was ist der CFString Equiv von NSString UTF8String?

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

Und ich versuche es mit diesem zu ersetzen:

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

Das ist alles in einem engen Unit-Test, die ein Beispiel HTTP-Anforderung über eine Steckdose mit CFNetwork APIs schreibt. Ich arbeite ObjC-Code, den ich versuche, nach C++ zu portieren. Ich ersetze nach und nach NS-API-Anrufe mit ihren gebührenfreien überbrückten Äquivalenten. Bis zur letzten Zeile war alles eins zu eins. Dies ist wie das letzte Stück, das abgeschlossen werden muss.

Antwort

14

Dies ist eines dieser Dinge, in denen Cocoa all die schmutzigen Dinge hinter den Kulissen erledigt, und Sie wissen nie wirklich, wie kompliziert die Dinge sein können, bis Sie die Ärmel hochkrempeln und es selbst machen müssen.

Die einfache Antwort dafür, warum es nicht ‚einfach‘ ist, weil NSString (und CFString) befassen sich mit all den komplizierten Details mit mehrer Zeichensätzen, Unicode zu tun, etc, etc, während eine einfache, einheitliche API präsentieren Saiten zum Manipulieren . Es ist objektorientiert am besten - die Details von "wie" (NS|CF)String geht auf Strings, die unterschiedliche String-Kodierungen haben (UTF8, MacRoman, UTF16, ISO 2022 Japanisch, etc.) ist ein privates Implementierungsdetail. Alles "funktioniert".

Es hilft zu verstehen, wie [@"..." UTF8String] funktioniert. Dies ist ein Detail der privaten Implementierung, also ist dies kein Evangelium, sondern basiert auf beobachtetem Verhalten. Wenn Sie eine Zeichenfolge in der UTF8String Nachricht senden, werden er die Zeichenfolge etwas annähert (nicht wirklich getestet, so halten es für Pseudo-Code, und es gibt tatsächlich einfache Möglichkeiten, die genau die gleiche Sache zu tun, so ist dies allzu ausführlich):

- (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); 
} 

Sie müssen sich keine Gedanken über die Speicherverwaltungsprobleme mit dem Puffer machen, den -UTF8String zurückgibt, da die NSMutableData automatisch freigegeben ist.

Ein String-Objekt ist frei, um den Inhalt der Zeichenfolge in der gewünschten Form zu halten. Daher gibt es keine Garantie dafür, dass die interne Darstellung die für Sie am besten geeignete ist (in diesem Fall UTF8). Wenn Sie nur C verwenden, müssen Sie mit der Verwaltung von Speicher umgehen, um eventuell erforderliche Zeichenfolgenkonvertierungen zu speichern. Was einmal ein einfacher Methodenaufruf war, ist jetzt viel, viel komplizierter.

meisten NSString wird tatsächlich umgesetzt in/mit Corefoundation/CFString, so gibt es offensichtlich einen Weg von einem CFStringRef ->-UTF8String. Es ist einfach nicht so sauber und einfach wie NSString-UTF8String. Die größte Komplikation besteht in der Speicherverwaltung. Hier ist, wie ich es in der Vergangenheit in Angriff genommen habe:

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; } 
} 

HINWEIS: Ich habe diesen Code nicht getestet, aber es wird modifiziert, von Code zu arbeiten. Abgesehen von offensichtlichen Fehlern glaube ich, dass es funktionieren sollte.

Das obige versucht, den Zeiger auf den Puffer zu erhalten, der CFString verwendet, um den Inhalt der Zeichenfolge zu speichern. Wenn CFString zufällig den Inhalt der Zeichenfolge in UTF8 codiert (oder eine entsprechend kompatible Codierung wie ASCII), dann wird wahrscheinlich CFStringGetCStringPtr() nicht NULL zurückgeben. Dies ist offensichtlich der beste und schnellste Fall. Wenn es den Zeiger aus irgendeinem Grund nicht bekommen kann, sagen wir, wenn CFString seinen Inhalt in UTF16 kodiert hat, dann weist es einen Puffer mit malloc() zu, der groß genug ist, um den gesamten String zu enthalten, wenn er nach UTF8 transcodiert wird. Dann, am Ende der Funktion, überprüft es, ob Speicher zugewiesen wurde und free() ist es bei Bedarf.

Und jetzt für ein paar Tipps und Tricks ... CFString "tendenziell" (und dies ist ein privates Implementierungsdetail, so dass es zwischen Releases ändern kann und ändert) halten Sie "einfache" Strings als MacRoman codiert, die ein ist 8-Bit breite Codierung. MacRoman ist wie UTF8 eine Obermenge von ASCII, so dass alle Zeichen < 128 äquivalent zu ihren ASCII-Gegenstücken sind (oder, mit anderen Worten, jedes Zeichen < 128 ist ASCII). In MacRoman sind Zeichen> = 128 Sonderzeichen. Sie haben alle Unicode-Entsprechungen und neigen dazu, Dinge wie zusätzliche Währungssymbole und "erweiterte westliche" Zeichen zu sein. Weitere Informationen finden Sie unter Wikipedia - MacRoman. Aber nur weil ein CFString sagt, es ist MacRoman (CFString Kodierungswert von kCFStringEncodingMacRoman, NSString Kodierungswert von NSMacOSRomanStringEncoding) bedeutet nicht, dass es Zeichen> = 128 drin hat. Wenn eine kCFStringEncodingMacRoman-codierte Zeichenfolge, die von CFStringGetCStringPtr() zurückgegeben wird, vollständig aus Zeichen < 128 besteht, entspricht sie exakt der ASCII-codierten Darstellung (C), die ebenfalls exakt der Zeichenfolge UTF8 (kCFStringEncodingUTF8) entspricht.

Je nach Ihren Anforderungen können Sie unter Verwendung von kCFStringEncodingMacRoman anstelle von kCFStringEncodingUTF8 beim Aufruf CFStringGetCStringPtr() "durchkommen". Die Dinge können (wahrscheinlich) schneller sein, wenn Sie eine strikte UTF8-Codierung für Ihre Zeichenfolgen benötigen, aber kCFStringEncodingMacRoman verwenden und dann sicherstellen, dass die von CFStringGetCStringPtr(string, kCFStringEncodingMacRoman) zurückgegebene Zeichenfolge nur Zeichen enthält, die < 128 sind. Wenn Zeichen> = 128 in der Zeichenfolge enthalten sind , dann gehen Sie die langsame Route von malloc() in einem Puffer, um die konvertierten Ergebnisse zu halten. Beispiel:

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; 
} 

Wie ich schon sagte, Sie tun schätzen nicht wirklich, wie viel Arbeit Cocoa tut automatisch für Sie, bis Sie alles selbst zu tun haben.:)

+0

Das ist eine Erklärung! Danke Johnne! Ich habe deinen Code ausprobiert und jetzt habe ich ein anderes Problem. Da ich mit ObjC in einer ".m" Datei gestartet habe, konnte ich schnell ein Beispiel erstellen. Nun, da ich zu C++ bin Umwandlung unter Verwendung einer ".mm" Datei I Ausnahmen auf Build erhalten: undefinierte Symbole: "___gxx_personality_v0", verwiesen von: ___ gxx_personality_v0 $ non_lazy_ptr in libMyNetworking.a (MyLowLevelNetworking.o) ld: Symbol (s) nicht gefunden Ich fühle mich immer noch so ahnungslos zu Apple Tools ... – Cliff

0

Wenn es für eine Steckdose bestimmt ist, wäre vielleicht CFStringGetBytes() Ihre beste Wahl?

Beachten Sie auch, dass die Dokumentation für CFStringGetCStringPtr() sagt:

Diese Funktion gibt entweder den angeforderten Zeiger sofort, ohne Speicherzuordnungen und Vervielfältigung, in konstanter Zeit, oder kehrt NULL. Wenn letzteres das Ergebnis ist, rufen Sie eine alternative Funktion wie die CFStringGetCString-Funktion auf, um die Zeichen zu extrahieren.

+0

Das hat, wie eine Million Parameter. Ich denke, ich könnte sein 1080-Formular ausfüllen, das aussieht, als würde es mich in der gleichen Position lassen. Ich werde in einem Moment mit Ergebnissen zurückschreiben. – Cliff

3

Vom documentation:

Ob diese Funktion einen gültigen Zeiger zurückgibt oder NULL, hängt von vielen Faktoren ab, von denen alle davon abhängen, wie die Zeichenfolge erstellt wurde und dessen Eigenschaften. Außerdem kann sich das Funktionsergebnis zwischen verschiedenen Releases und auf verschiedenen Plattformen ändern. Zählen Sie daher unter keinen Umständen davon aus, ein Nicht-NULL-Ergebnis von dieser Funktion zu erhalten.

Sie sollten CFStringGetCString wenn CFStringGetCStringPtr kehrt NULL verwenden.

+0

Schließen, aber keine Zigarre. Ich verwende jetzt: CFStringGetCString (aRequest, myBuffer, [(NSString *) aRequest Länge], kCFStringEncodingUTF8); und es funktioniert fast, aber das erste Zeichen ist abgeschnitten. Wie bekomme ich einen String-Zeiger von einem CFStringRef? Warum ist das so schwer? – Cliff

+2

Sie können [aRequest length] +1 verwenden, um den Null-Terminator zu berücksichtigen. – ianh

+0

Der Grund, soweit ich das beurteilen kann, ist, dass die interne Repräsentation des CFString nicht UTF8 ist, also kann es keinen rohen Zeiger geben. – ianh

0

Hier ist ein Weg, um eine CFStringRef printf, die wir von einem CFStringRef bekommen ‚\ 0'-terminierten String impliziert: Code

// 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

Im Beispiel oben, wird die folgende:

CFIndex stringLength = CFStringGetLength(cfString) 

StringLength wird dann malloc verwendet wird() einen temporären Puffer von so vielen Bytes, plus 1.

Aber die Header-Datei für CFStringGetLength() sagt ausdrücklich die nu zurück mber von 16-Bit-Unicode-Zeichen, nicht Bytes. Wenn also einige dieser Unicode-Zeichen außerhalb des ASCII-Bereichs liegen, reicht der Puffer malloc() nicht aus, um die UTF-8-Umwandlung der Zeichenfolge zu speichern.

Vielleicht fehlt mir etwas, aber um absolut sicher zu sein, ist die Anzahl der Bytes, die benötigt werden, um N beliebige Unicode-Zeichen zu halten, höchstens 4 * n, wenn sie alle in UTF-8 konvertiert werden.

2

Hier ist ein Arbeitscode. Ich begann mit @ johnes Antwort, ersetzte einfach CFStringGetBytes durch CFStringGetLength und machte die von @Doug vorgeschlagene Korrektur.

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); 
Verwandte Themen