TN059. Использование макросов преобразования MFC из MBCS в Юникод

Примечание.

Следующее техническое примечание не было обновлено, поскольку сначала оно было включено в электронную документацию. В результате некоторые процедуры и разделы могут быть устаревшими или неверными. Для получения последних сведений рекомендуется выполнить поиск интересующей темы в алфавитном указателе документации в Интернете.

В этом примечании описывается использование макросов для преобразования МБ CS/Юникода, определенных в AFXPRIV.H. Эти макросы наиболее полезны, если приложение работает непосредственно с OLE API или по какой-то причине, часто требуется преобразовать между Юникодом и МБ CS.

Обзор

В MFC 3.x используется специальная библиотека DLL (MFCANS32.DLL) для автоматического преобразования между Юникодом и МБ CS при вызове интерфейсов OLE. Эта библиотека DLL была почти прозрачным слоем, что позволило писать приложения OLE, как если бы API и интерфейсы OLE были МБ CS, даже если они всегда Юникод (за исключением Macintosh). Хотя этот уровень был удобным и позволил приложениям быстро переноситься из Win16 в Win32 (MFC, Microsoft Word, Microsoft Excel и VBA, это лишь некоторые из приложений Майкрософт, которые использовали эту технологию), это иногда существенное снижение производительности. По этой причине MFC 4.x не использует эту библиотеку DLL и вместо этого напрямую взаимодействует с интерфейсами OLE в Юникоде. Для этого MFC необходимо преобразовать в Юникод в МБ CS при вызове интерфейса OLE, и часто необходимо преобразовать в МБ CS из Юникода при реализации интерфейса OLE. Для эффективной и легкой обработки этих макросов было создано несколько макросов, чтобы упростить это преобразование.

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

// we want to convert an MBCS string in lpszA
int nLen = MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    NULL,
    NULL);

LPWSTR lpszW = new WCHAR[nLen];
MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    lpszW,
    nLen);

// use it to call OLE here
pI->SomeFunctionThatNeedsUnicode(lpszW);

// free the string
delete[] lpszW;

Такой подход является рядом проблем. Основная проблема заключается в том, что это много кода для записи, тестирования и отладки. То, что было простым вызовом функции, в настоящее время гораздо сложнее. Кроме того, в этом случае существует значительный объем затрат на среду выполнения. Память должна быть выделена в куче и освобождена при каждом завершении преобразования. Наконец, приведенный выше код должен был быть добавлен для #ifdefs сборок Юникода и Macintosh (которые не требуют этого преобразования).

Мы придумали решение , чтобы создать некоторые макросы, которые 1) маскируют разницу между различными платформами, и 2) использовать эффективную схему выделения памяти, и 3) легко вставить в существующий исходный код. Ниже приведен пример одного из определений:

#define A2W(lpa) (\
((LPCSTR)lpa == NULL) NULL : (\
    _convert = (strnlen(lpa)+1),\
    AfxA2WHelper((LPWSTR) alloca(_convert*2),
    lpa,
    _convert)\)\)

Использование этого макроса вместо приведенного выше кода и вещей гораздо проще:

// use it to call OLE here
USES_CONVERSION;
pI->SomeFunctionThatNeedsUnicode(T2OLE(lpszA));

Существуют дополнительные вызовы, в которых требуется преобразование, но использование макросов является простым и эффективным.

Реализация каждого макроса использует функцию _alloca() для выделения памяти из стека вместо кучи. Выделение памяти из стека гораздо быстрее, чем выделение памяти в куче, и память автоматически освобождается при выходе функции. Кроме того, макросы избегают вызова MultiByteToWideChar (или WideCharToMultiByte) нескольких раз. Это делается путем выделения немного больше памяти, чем необходимо. Мы знаем, что МБ C преобразуется в не более одного WCHAR и что для каждого WCHAR у нас будет не более двух МБ C байтов. При выделении немного больше, чем необходимо, но всегда достаточно для обработки преобразования второго вызова второй вызов функции преобразования не требуется. Вызов вспомогательной функции AfxA2Whelper уменьшает количество push-вызовов аргументов, которые необходимо выполнить для выполнения преобразования (это приводит к меньшему коду, чем при вызове MultiByteToWideChar напрямую).

Чтобы макросы имели место для хранения временной длины, необходимо объявить локальную переменную с именем _convert, которая делает это в каждой функции, использующую макросы преобразования. Это делается путем вызова макроса USES_CONVERSION, как показано выше в примере.

Существуют как универсальные макросы преобразования, так и определенные макросы OLE. Эти два разных набора макросов рассматриваются ниже. Все макросы находятся в AFXPRIV.H.

Универсальные макросы преобразования

Универсальные макросы преобразования образуют базовый механизм. Пример макроса и реализация, показанные в предыдущем разделе A2W, является одним из таких "универсальных" макросов. Это не связано с OLE конкретно. Ниже приведен набор универсальных макросов:

A2CW      (LPCSTR) -> (LPCWSTR)
A2W      (LPCSTR) -> (LPWSTR)
W2CA      (LPCWSTR) -> (LPCSTR)
W2A      (LPCWSTR) -> (LPSTR)

Помимо преобразования текста, существуют также макросы и вспомогательные функции для преобразования TEXTMETRIC, DEVMODEBSTRи выделенных строк OLE. Эти макросы выходят за рамки область этого обсуждения — ссылаются на AFXPRIV. H для получения дополнительных сведений об этих макросах.

Макросы преобразования OLE

Макросы преобразования OLE предназначены специально для обработки функций, ожидающих символов OLESTR . При проверке заголовков OLE вы увидите множество ссылок на LPCOLESTR и OLECHAR. Эти типы используются для ссылки на тип символов, используемых в интерфейсах OLE таким образом, что не относится к платформе. OLECHAR сопоставляется с char платформами Win16 и Macintosh и WCHAR в Win32.

Чтобы количество директив #ifdef в коде MFC было минимальным, у нас есть аналогичный макрос для каждого преобразования, в котором участвуют строки OLE. Наиболее часто используются следующие макросы:

T2COLE   (LPCTSTR) -> (LPCOLESTR)
T2OLE   (LPCTSTR) -> (LPOLESTR)
OLE2CT   (LPCOLESTR) -> (LPCTSTR)
OLE2T   (LPCOLESTR) -> (LPCSTR)

Опять же, существуют аналогичные макросы для выполнения выделенных строк TEXTMETRIC, DEVMODE, BSTR и OLE. Обратитесь к AFXPRIV. Дополнительные сведения см. в

Другие вопросы

Не используйте макросы в жестком цикле. Например, вы не хотите писать следующий код:

void BadIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, T2COLE(lpsz));

}

Приведенный выше код может привести к выделению мегабайт памяти в стеке в зависимости от содержимого строки lpsz ! Для преобразования строки для каждой итерации цикла также требуется время. Вместо этого переместите такие константы из цикла:

void MuchBetterIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    LPCOLESTR lpszT = T2COLE(lpsz);

    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, lpszT);

}

Если строка не является константой, инкапсулирует вызов метода в функцию. Это позволит буферу преобразования освободиться каждый раз. Например:

void CallSomeMethod(int ii, LPCTSTR lpsz)
{
    USES_CONVERSION;
    pI->SomeMethod(ii, T2COLE(lpsz));

}

void MuchBetterIterateCode2(LPCTSTR* lpszArray)
{
    for (int ii = 0; ii <10000; ii++)
    CallSomeMethod(ii, lpszArray[ii]);

}

Никогда не возвращает результат одного из макросов, если возвращаемое значение не подразумевает создание копии данных перед возвратом. Например, этот код недопустим:

LPTSTR BadConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

Приведенный выше код можно исправить, изменив возвращаемое значение на то, что копирует значение:

CString BetterConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

Макросы легко использовать и легко вставить в код, но, как можно сказать из приведенных выше предостережений, необходимо быть осторожным при их использовании.

См. также

Технические примечания по номеру
Технические примечания по категории