Сравнение строк Unicode в C возвращает значения, отличные от C # - PullRequest
3 голосов
/ 07 сентября 2011

Итак, я пытаюсь написать функцию сравнения на C, которая может принять кодировку UTF-8 в кодировке Unicode и использовать функцию Windows CompareStringEx () , и я ожидаю, что она будет работать так же, как .NET CultureInfo.CompareInfo.Compare () .

Теперь функция, которую я написал на C, иногда работает, но не во всех случаях, и я пытаюсь выяснить, почему. Вот случай, который терпит неудачу (проходит в C #, а не в C):

CultureInfo cultureInfo = new CultureInfo("en-US");
CompareOptions compareOptions = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth;

string stringA = "คนอ้วน ๆ";
string stringB = "はじめまして";
//Result is -1 which is expected
int result = cultureInfo.CompareInfo.Compare(stringA, stringB);

А вот то, что я написал на C. Имейте в виду, что это должно принимать строку в кодировке UTF-8 и использовать функцию Windows CompareStringEx (), поэтому необходимо преобразование.

// Compare flags for the string comparison
#define COMPARE_STRING_FLAGS (NORM_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH)

int CompareStrings(int lenA, const void *strA, int lenB, const void *strB) 
{
    LCID ENGLISH_LCID = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
    int compareString = -1;

    // Get the size of the strings as UTF-18 encoded Unicode strings. 
    // Note: Passing 0 as the last parameter forces the MultiByteToWideChar function
    // to give us the required buffer size to convert the given string to utf-16s
    int strAWStrBufferSize = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)strA, lenA, NULL, 0);
    int strBWStrBufferSize = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)strB, lenB, NULL, 0);

    // Malloc the strings to store the converted UTF-16 values
    LPWSTR utf16StrA = (LPWSTR) GlobalAlloc(GMEM_FIXED, strAWStrBufferSize * sizeof(WCHAR));
    LPWSTR utf16StrB = (LPWSTR) GlobalAlloc(GMEM_FIXED, strBWStrBufferSize * sizeof(WCHAR));

    // Convert the UTF-8 strings (SQLite will pass them as UTF-8 to us) to standard  
    // windows WCHAR (UTF-16\UCS-2) encoding for Unicode so they can be used in the 
    // Windows CompareStringEx() function.
    if(strAWStrBufferSize != 0)
    {
        MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)strA, lenA, utf16StrA, strAWStrBufferSize);
    }
    if(strBWStrBufferSize != 0)
    {
        MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)strB, lenB, utf16StrB, strBWStrBufferSize);
    }

    // Compare the strings using the windows compare function.
    // Note: We subtract 1 from the size since we don't want to include the null termination character
    if(NULL != utf16StrA && NULL != utf16StrB)
    {
        compareValue = CompareStringEx(L"en-US", COMPARE_STRING_FLAGS, utf16StrA, strAWStrBufferSize - 1, utf16StrB, strBWStrBufferSize - 1, NULL, NULL, 0);
    }

    // In the Windows CompareStringEx() function, 0 indicates an error, 1 indicates less than, 
    // 2 indicates equal to, 3 indicates greater than so subtract 2 to maintain C convention
    if(compareValue > 0)
    {
        compareValue -= 2;
    }

    return compareValue;
}

Теперь, если я выполню следующий код, я ожидаю, что результат будет -1 на основе реализации .NET (см. Выше), но я получу 1, указывающий, что строки больше чем:

char strA[50] = "คนอ้วน ๆ";
char strB[50] = "はじめまして";

// Will be 1 when we expect it to be -1
int result = CompareStrings(strlen(strA), strA, strlen(strB), strB);

Есть идеи, почему полученные результаты отличаются? Насколько я могу судить, я использую один и тот же LCID / cultureInfo, и сравниваю варианты в обеих реализациях, и преобразования успешны.

К вашему сведению: эта функция будет использоваться в качестве пользовательского сопоставления в SQLite. Не имеет отношения к вопросу, но если кому-то интересно, почему функция подписи такая, какая она есть.

ОБНОВЛЕНИЕ: Я также определил, что при запуске того же кода в .NET 4 я вижу поведение, которое я видел в собственном коде. В результате возникло несоответствие между версиями .NET. См. Мой ответ ниже по причинам этого.

Ответы [ 3 ]

3 голосов
/ 07 сентября 2011

Ну, ваш код выполняет несколько шагов здесь - не ясно, проваливается ли шаг сравнения или нет.

В качестве первого шага я бы записал - и в коде .NET, и в коде C - точные единицы кода UTF-16, которые есть в utf16StrA, utf16StrB, stringA и stringB. Я не удивлюсь, если обнаружу, что во входных данных, которые вы используете в коде C, есть проблема.

2 голосов
/ 07 сентября 2011

Что вы надеетесь на то, что ваш текстовый редактор сохранит файл исходного кода в формате utf-8. И что компилятор тогда как-то не интерпретирует исходный код как utf-8. Это слишком много надежды, по крайней мере, на мой компилятор:

warning C4566: character represented by universal-character-name '\u0E04' cannot be represented in the current code page (1252)

Fix:

const wchar_t* strA = L"คนอ้วน ๆ";
const wchar_t* strB = L"はじめまして";

И удалить код преобразования.

0 голосов
/ 18 октября 2011

Итак, я выяснил проблему после обращения в службу поддержки Microsoft.Вот что они должны были сказать о проблеме:

Причина проблемы, с которой вы столкнулись, а именно запуск CompareInfo.Compare для одной и той же строки с теми же параметрами сравнения, но при получении разных возвращаемых значений, когдазапуск в разных версиях .NET Framework заключается в том, что правила сортировки привязаны к спецификации Unicode, которая со временем эволюционирует.Исторически .NET фиксировал данные для параллельных выпусков, чтобы соответствовать новейшей версии Windows и соответствующей версии Unicode, реализованной в то время, поэтому версии 2.0, 3.0 и 3.5 соответствуют версии для Windows XP или Server 2003, тогда как v4.0соответствует правилам сортировки Vista.В результате правила сортировки для различных версий .NET Framework со временем менялись.

Это также означает, что когда я запускал собственный код, я вызывал методы сортировки, которые придерживались правил сортировки Vista, и когда я работал в .NET 3.5, я запускал методы сортировки, которые использовали Windows XPправила сортировки.Мне кажется странным, что спецификация Unicode изменилась бы таким образом, что это привело бы к таким существенным различиям, но, очевидно, это именно тот случай.Мне кажется, что изменение спецификации Unicode таким драматическим способом - это фантастический способ сломать обратную совместимость.

...