2009-10-22 28 views
5

Tôi bị mắc kẹt trên stoopid ngày hôm nay vì tôi không thể chuyển đổi một mảnh đơn giản của mã ObjC để tương đương Cpp của nó. Tôi có điều này:Phương thức CFString của UTF8String của NSString là gì?

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

Và tôi đang cố gắng để thay thế nó với điều này:

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

Đây là tất cả trong một thử nghiệm đơn vị chặt chẽ mà viết một yêu cầu HTTP dụ trên một ổ cắm với CFNetwork API. Tôi đã làm việc mã ObjC mà tôi đang cố gắng chuyển sang C++. Tôi đang dần thay thế các cuộc gọi API của NS bằng số cầu nối tương đương miễn phí của họ. Tất cả mọi thứ đã được một cho đến nay cho đến dòng cuối cùng này. Điều này giống như mảnh cuối cùng cần hoàn thành.

Trả lời

14

Đây là một trong những điều mà Cocoa làm tất cả những thứ lộn xộn đằng sau hậu trường, và bạn không bao giờ thực sự đánh giá cao những thứ phức tạp như thế nào cho đến khi bạn phải cuộn tay áo và tự làm.

Câu trả lời đơn giản vì sao không đơn giản là vì NSString (và CFString) xử lý tất cả các chi tiết phức tạp về xử lý nhiều bộ ký tự, Unicode, v.v. . Đó là đối tượng được định hướng tốt nhất- chi tiết về 'cách' (NS|CF)String giải quyết các chuỗi có mã hóa chuỗi khác nhau (UTF8, MacRoman, UTF16, ISO 2022 Nhật Bản, v.v.) là chi tiết triển khai riêng tư. Tất cả 'chỉ hoạt động'.

Giúp hiểu cách hoạt động của [@"..." UTF8String]. Đây là một chi tiết thực hiện riêng tư, vì vậy đây không phải là phúc âm, nhưng dựa trên hành vi được quan sát. Khi bạn gửi một chuỗi một thông báo UTF8String, chuỗi sẽ làm điều gì đó gần đúng (không thực sự được kiểm tra, vì vậy hãy xem xét mã giả, và thực sự có những cách đơn giản hơn để làm điều tương tự, vì vậy điều này quá chi tiết):

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

Bạn không phải lo lắng về các vấn đề quản lý bộ nhớ khi xử lý bộ đệm mà -UTF8String trả về vì NSMutableData được tự động phát hành.

Đối tượng chuỗi là miễn phí để giữ nội dung của chuỗi theo bất kỳ hình thức nào, vì vậy không đảm bảo rằng biểu diễn bên trong của nó là thuận tiện nhất cho nhu cầu của bạn (trong trường hợp này là UTF8). Nếu bạn đang sử dụng đồng bằng C, bạn sẽ phải đối phó với việc quản lý một số bộ nhớ để giữ bất kỳ chuyển đổi chuỗi nào có thể được yêu cầu. Những gì đã từng là một cuộc gọi phương thức -UTF8String đơn giản bây giờ là nhiều, phức tạp hơn nhiều.

Hầu hết NSString thực sự được triển khai trong/với CoreFoundation/CFString, vì vậy rõ ràng là đường dẫn từ CFStringRef ->-UTF8String. Nó không đơn giản và đơn giản như NSString 's -UTF8String. Hầu hết các biến chứng là với quản lý bộ nhớ. Đây là cách tôi đã tức giải quyết nó trong quá khứ:

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

LƯU Ý: Tôi đã không kiểm tra mã này, nhưng nó được sửa đổi từ mã làm việc. Vì vậy, ngoài những lỗi rõ ràng, tôi tin rằng nó sẽ hoạt động.

Ở trên cố gắng đưa con trỏ đến bộ đệm CFString sử dụng để lưu trữ nội dung của chuỗi. Nếu CFString xảy ra có nội dung chuỗi được mã hóa bằng UTF8 (hoặc mã hóa tương thích thích hợp, chẳng hạn như ASCII), thì có khả năng CFStringGetCStringPtr() sẽ trả về không NULL. Đây rõ ràng là trường hợp tốt nhất và nhanh nhất. Nếu không thể nhận được con trỏ đó vì lý do nào đó, hãy nói rằng nếu CFString có nội dung được mã hóa bằng UTF16, thì nó sẽ phân bổ bộ đệm với malloc() đủ lớn để chứa toàn bộ chuỗi khi được chuyển mã sang UTF8. Sau đó, ở phần cuối của chức năng, nó sẽ kiểm tra xem bộ nhớ đã được cấp phát chưa và free() nếu cần.

Và bây giờ để có một số mẹo và thủ thuật ... CFString 'có xu hướng' (và đây là chi tiết triển khai cá nhân, vì vậy nó có thể và thay đổi giữa các bản phát hành) giữ các chuỗi 'đơn giản' được mã hóa như MacRoman. Mã hóa rộng 8 bit. MacRoman, như UTF8, là một siêu của ASCII, sao cho tất cả các ký tự < 128 tương đương với các đối tác ASCII của chúng (hoặc nói cách khác, bất kỳ ký tự nào < 128 là ASCII). Trong MacRoman, ký tự> = 128 là ký tự 'đặc biệt'. Tất cả chúng đều có tương đương Unicode, và có xu hướng là những thứ như ký hiệu tiền tệ bổ sung và các ký tự 'mở rộng về phía tây'. Xem Wikipedia - MacRoman để biết thêm thông tin. Nhưng chỉ vì một số CFString cho biết đó là MacRoman (CFString giá trị mã hóa của kCFStringEncodingMacRoman, NSString giá trị mã hóa của NSMacOSRomanStringEncoding) không có nghĩa là nó có ký tự> = 128 trong đó. Nếu một chuỗi được mã hóa kCFStringEncodingMacRoman được trả về bởi CFStringGetCStringPtr() được tạo thành hoàn toàn bằng các ký tự < 128, thì nó chính xác tương đương với biểu diễn được mã hóa ASCII (kCFStringEncodingASCII), cũng chính xác tương đương với chuỗi biểu diễn được mã hóa UTF8 (kCFStringEncodingUTF8).

Tùy thuộc vào yêu cầu của bạn, bạn có thể 'nhận được' bằng cách sử dụng kCFStringEncodingMacRoman thay vì kCFStringEncodingUTF8 khi gọi CFStringGetCStringPtr(). Mọi thứ 'có thể' (có thể) sẽ nhanh hơn nếu bạn yêu cầu mã hóa UTF8 nghiêm ngặt cho chuỗi của bạn nhưng sử dụng kCFStringEncodingMacRoman, sau đó kiểm tra để đảm bảo chuỗi trả về bởi CFStringGetCStringPtr(string, kCFStringEncodingMacRoman) chỉ chứa các ký tự là < 128. Nếu có ký tự> = 128 trong chuỗi , sau đó đi tuyến đường chậm bằng cách malloc() nhập bộ đệm để giữ kết quả được chuyển đổi. Ví dụ:

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

Như tôi đã nói, bạn không thực sự đánh giá cao công việc Cocoa tự động cho đến khi bạn phải tự làm tất cả.:)

+0

Bây giờ đó là một lời giải thích! Thanx Johne! Tôi đã thử mã của bạn và bây giờ tôi có một vấn đề khác. Bởi vì tôi bắt đầu với ObjC trong một tập tin ".m" tôi đã có thể nhanh chóng giả lập một ví dụ. Bây giờ tôi đang chuyển sang C++ sử dụng một ".mm" tập tin tôi nhận được ngoại lệ về xây dựng: ký Undefined: "___gxx_personality_v0", tham chiếu từ: ___ gxx_personality_v0 $ non_lazy_ptr trong libMyNetworking.a (MyLowLevelNetworking.o) ld: biểu tượng (s) không tìm thấy Tôi vẫn cảm thấy thất vọng với các công cụ của Apple vào những thời điểm ... – Cliff

0

Nếu nó được đặt cho một ổ cắm, có lẽ CFStringGetBytes() sẽ là lựa chọn tốt nhất của bạn?

Cũng lưu ý rằng các tài liệu cho CFStringGetCStringPtr() nói:

Chức năng này hoặc trả về con trỏ yêu cầu ngay lập tức, không phân bổ bộ nhớ và không sao chép, trong thời gian liên tục, hoặc trả về NULL. Nếu sau này là kết quả, hãy gọi một hàm thay thế như hàm CFStringGetCString để trích xuất các ký tự.

+0

Điều đó có, giống như một triệu thông số. Tôi đoán tôi có thể điền vào mẫu 1080 của nó có vẻ như nó sẽ để lại cho tôi ở vị trí tương tự. Tôi sẽ đăng lại trong giây lát với kết quả. – Cliff

3

Từ documentation:

hay không hàm này trả về một con trỏ hợp lệ hoặc NULL phụ thuộc vào nhiều yếu tố, tất cả đều phụ thuộc vào cách chuỗi được tạo ra và thuộc tính của nó. Ngoài ra, kết quả chức năng có thể thay đổi giữa các bản phát hành khác nhau và trên các nền tảng khác nhau. Vì vậy, không được tính khi nhận được kết quả không NULL từ chức năng này trong bất kỳ trường hợp nào.

Bạn nên sử dụng CFStringGetCString nếu CFStringGetCStringPtr lợi nhuận NULL.

+0

Đóng nhưng không có xì gà. Tôi hiện đang sử dụng: CFStringGetCString (aRequest, myBuffer, [(NSString *) aRequest length], kCFStringEncodingUTF8); và nó gần như hoạt động nhưng ký tự đầu tiên bị cắt ngắn. Về cơ bản làm thế nào để có được một con trỏ String từ một CFStringRef? Sao lại khó như vậy? – Cliff

+2

Bạn có thể muốn sử dụng [aRequest length] +1 để tính toán null-terminator. – ianh

+0

Lý do, theo như tôi có thể nói, cho tất cả những rắc rối là việc đại diện nội bộ của CFString có thể không phải là UTF8, vì vậy có thể không * là * một con trỏ thô để có được. – ianh

0

Dưới đây là một cách để printf một CFStringRef trong đó hàm ý chúng ta có được một '\ chuỗi 0'-chấm dứt từ một CFStringRef:

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

Trong đoạn mã ví dụ trên, có vẻ như sau:

CFIndex stringLength = CFStringGetLength(cfString) 

StringLength sau đó sẽ được sử dụng để malloc() một bộ đệm tạm thời mà nhiều byte, cộng với 1.

Nhưng những tập tin tiêu đề cho rõ ràng CFStringGetLength() nói nó trả về nu mber của ký tự Unicode 16 bit, không phải byte. Vì vậy, nếu một số ký tự Unicode nằm ngoài phạm vi ASCII, bộ đệm malloc() sẽ không đủ dài để giữ chuyển đổi UTF-8 của chuỗi.

Có lẽ tôi đang thiếu điều gì đó, nhưng để an toàn tuyệt đối, số byte cần để giữ N ký tự Unicode tùy ý tối đa là 4 * n khi tất cả chúng được chuyển thành UTF-8.

2

Dưới đây là một số mã hoạt động. Tôi bắt đầu với câu trả lời của @ johne, thay thế CFStringGetBytes bằng CFStringGetLength để đơn giản và thực hiện chỉnh sửa do @Doug đề xuất.

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); 
Các vấn đề liên quan