Что такое эквивалент CFString UTF8String для NSString? - PullRequest
5 голосов
/ 22 октября 2009

Сегодня я застрял на Stoopid, так как не могу преобразовать простой кусок кода ObjC в его Cpp-эквивалент. У меня есть это:

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

И я пытаюсь заменить его следующим:

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

Это все в строгом модульном тесте, который пишет пример HTTP-запроса через сокет с API-интерфейсами CFNetwork. У меня есть рабочий код ObjC, который я пытаюсь перенести на C ++. Я постепенно заменяю вызовы NS API их бесплатными мостовыми эквивалентами. До сих пор все было один за одним до последней строки. Это как последний кусок, который нужно закончить.

Ответы [ 6 ]

14 голосов
/ 23 октября 2009

Это одна из тех вещей, когда Какао делает все грязное за кулисами, и вы никогда не поймете, насколько сложными могут быть вещи, пока вам не придется засучить рукава и сделать это самостоятельно.

Простой ответ, почему он не «прост», заключается в том, что NSStringCFString) имеют дело со всеми сложными деталями работы с несколькими наборами символов, Unicode и т. Д., И т. Д., В то же время представляя простой унифицированный API для манипулирования строками. Это объектно-ориентированный в лучшем виде - подробности того, «как» (NS|CF)String имеет дело со строками, которые имеют разные кодировки (UTF8, MacRoman, UTF16, ISO 2022 японский и т. Д.), Являются частной реализацией. Все это «просто работает».

Это помогает понять, как работает [@"..." 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);
}

Вам не нужно беспокоиться о проблемах управления памятью при работе с буфером, который -UTF8String возвращает, поскольку NSMutableData автоматически освобожден.

Строковый объект может хранить содержимое строки в любой форме, которую хочет, поэтому нет гарантии, что его внутреннее представление будет наиболее удобным для ваших нужд (в данном случае, 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. Затем в конце функции он проверяет, была ли выделена память, и free(), если необходимо.

А теперь несколько советов и хитростей ... CFString «стремится» (и это частная деталь реализации, поэтому он может меняться и меняется между выпусками), сохраняя «простые» строки в кодировке MacRoman, что 8-битная кодировка. MacRoman, как и UTF8, является надмножеством ASCII, так что все символы <128 эквивалентны их аналогам ASCII (или, другими словами, любой символ <128 является ASCII). В MacRoman символы> = 128 являются «специальными» символами. Все они имеют Unicode-эквиваленты и, как правило, представляют собой дополнительные символы валюты и расширенные западные символы. См. Википедия - MacRoman для получения дополнительной информации. Но то, что CFString говорит, что это MacRoman (CFString значение кодирования kCFStringEncodingMacRoman, NSString значение кодирования NSMacOSRomanStringEncoding) не означает, что в нем есть символы> = 128. Если строка в кодировке kCFStringEncodingMacRoman, возвращаемая CFStringGetCStringPtr(), полностью состоит из символов <128, то она в точности эквивалентна ее кодированному представлению ASCII (<code>kCFStringEncodingASCII), что также в точности эквивалентно строкам UTF8 (kCFStringEncodingUTF8) закодированное представление.

В зависимости от ваших требований, вы можете «обойтись», используя kCFStringEncodingMacRoman вместо kCFStringEncodingUTF8 при звонке CFStringGetCStringPtr(). Вещи «могут» (возможно) будут быстрее, если вам требуется строгая кодировка UTF8 для ваших строк, но используйте kCFStringEncodingMacRoman, затем убедитесь, что строка, возвращаемая CFStringGetCStringPtr(string, kCFStringEncodingMacRoman), содержит только символы <128. 128 в строке, затем идите по медленному маршруту, <code>malloc() используя буфер для хранения преобразованных результатов. Пример:

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

Как я уже сказал, вы на самом деле не цените, сколько работы Какао выполняет для вас автоматически, пока вам не придется делать все это самостоятельно. :)

5 голосов
/ 01 марта 2011

В приведенном выше примере кода отображается следующее:

CFIndex stringLength = CFStringGetLength(cfString)

stringLength затем используется для malloc () временного буфера с таким количеством байтов плюс 1.

Но заголовочный файл для CFStringGetLength() прямо говорит, что возвращает количество 16-битных символов Unicode, а не байтов. Поэтому, если некоторые из этих символов Unicode находятся за пределами диапазона ASCII, буфер malloc() будет недостаточно длинным, чтобы содержать преобразование строки в UTF-8.

Возможно, я что-то упустил, но, чтобы быть абсолютно безопасным, количество байтов, необходимое для хранения N произвольных символов Юникода, составляет не более 4 * n, когда все они преобразованы в UTF-8.

3 голосов
/ 23 октября 2009

Из документации :

Будет ли эта функция возвращать действительный указатель или значение NULL, зависит от многих факторов, все из которых зависят от того, как была создана строка и ее свойства. Кроме того, результат функции может меняться в разных выпусках и на разных платформах. Поэтому не рассчитывайте на получение ненулевого результата от этой функции ни при каких обстоятельствах.

Вы должны использовать CFStringGetCString, если CFStringGetCStringPtr возвращает NULL.

2 голосов
/ 28 марта 2014

Вот некоторый рабочий код. Я начал с ответа @ johne, заменил CFStringGetBytes на CFStringGetLength для простоты и сделал исправление, предложенное @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);
0 голосов
/ 27 мая 2010

Вот способ напечатать CFStringRef, который подразумевает, что мы получаем '\ 0' -концевую строку из 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;
}
0 голосов
/ 22 октября 2009

Если он предназначен для сокета, возможно, CFStringGetBytes() будет вашим лучшим выбором?

Также обратите внимание, что в документации для CFStringGetCStringPtr() написано:

Эта функция либо возвращает запрошенный указатель немедленно, без выделения памяти и без копирования, в постоянное время, либо возвращает NULL. Если последнее является результатом, вызовите альтернативную функцию, такую ​​как CFStringGetCString, для извлечения символов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...