Compartir a través de


TN059: Usar macros de conversión MBCS/Unicode de MFC

Nota:

La nota técnica siguiente no se ha actualizado desde que se incluyó por primera vez en la documentación en línea. Como resultado, algunos procedimientos y temas podrían estar obsoletos o incorrectos. Para obtener la información más reciente, se recomienda buscar el tema de interés en el índice de documentación en línea.

En esta nota se describe cómo usar las macros para la conversión MBCS/Unicode, que se definen en AFXPRIV.H. Estas macros son más útiles si la aplicación se ocupa directamente de la API OLE o por algún motivo, a menudo necesita convertir entre Unicode y MBCS.

Información general

En MFC 3.x, se usó un archivo DLL especial (MFCANS32.DLL) para convertir automáticamente entre Unicode y MBCS cuando se llamó a interfaces OLE. Este archivo DLL era una capa casi transparente que permitía escribir aplicaciones OLE como si las API OLE y las interfaces fueran MBCS, aunque siempre son Unicode (excepto en Macintosh). Aunque esta capa era conveniente y permitía que las aplicaciones se migrara rápidamente de Win16 a Win32 (MFC, Microsoft Word, Microsoft Excel y VBA, son solo algunas de las aplicaciones de Microsoft que usaban esta tecnología), tuvo un impacto de rendimiento a veces significativo. Por este motivo, MFC 4.x no usa este archivo DLL y, en su lugar, se comunica directamente con las interfaces OLE Unicode. Para ello, MFC debe convertir a Unicode a MBCS al realizar una llamada a una interfaz OLE y, a menudo, debe convertir a MBCS desde Unicode al implementar una interfaz OLE. Para controlar esto de forma eficaz y sencilla, se crearon varias macros para facilitar esta conversión.

Uno de los mayores obstáculos para crear este conjunto de macros es la asignación de memoria. Dado que no se pueden convertir las cadenas en su lugar, se debe asignar nueva memoria para contener los resultados convertidos. Esto podría haberse hecho con código similar al siguiente:

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

Este enfoque tiene una serie de problemas. El principal problema es que es una gran cantidad de código para escribir, probar y depurar. Algo que era una llamada de función simple, ahora es mucho más complejo. Además, hay una sobrecarga significativa en tiempo de ejecución para hacerlo. La memoria debe asignarse en el montón y liberarse cada vez que se realiza una conversión. Por último, al código anterior se le debería añadir el #ifdefs apropiado para las compilaciones de Unicode y Macintosh (que no requieren que se realice esta conversión).

La solución con la que hemos llegado es crear algunas macros que 1) enmascaran la diferencia entre las distintas plataformas y 2) usar un esquema de asignación de memoria eficaz y 3) son fáciles de insertar en el código fuente existente. Este es un ejemplo de una de las definiciones:

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

El uso de esta macro en lugar del código anterior y las cosas son mucho más sencillas:

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

Hay llamadas adicionales en las que es necesaria la conversión, pero el uso de las macros es sencillo y eficaz.

La implementación de cada macro usa la función _alloca() para asignar memoria desde la pila en lugar del montón. La asignación de memoria desde la pila es mucho más rápida que asignar memoria en el montón y esta se libera automáticamente cuando se sale de la función. Además, las macros evitan llamar a MultiByteToWideChar (o WideCharToMultiByte) más de una vez. Esto se hace asignando un poco más de memoria de lo necesario. Sabemos que un MBC se convertirá como máximo en un WCHAR y que para cada WCHAR tendremos un máximo de dos bytes de MBC. Al asignar un poco más de lo necesario, pero siempre lo suficiente para manejar la conversión, se evita la segunda llamada a la función de conversión. La llamada a la función AfxA2Whelper auxiliar reduce el número de inserciones de argumento que se deben realizar para realizar la conversión (esto da como resultado código más pequeño, que si se llama MultiByteToWideChar directamente).

Para que las macros tengan espacio para almacenar la longitud temporal, es necesario declarar una variable local denominada _convert que lo haga en cada función que use las macros de conversión. Para ello, se invoca la macro USES_CONVERSION como se ha visto anteriormente en el ejemplo.

Hay macros de conversión genéricas y macros específicas de OLE. Estos dos conjuntos de macros diferentes se describen a continuación. Todas las macros residen en AFXPRIV.H.

Macros de conversión genéricas

Las macros de conversión genéricas forman el mecanismo subyacente. El ejemplo de macro y la implementación que se muestran en la sección anterior, A2W, es una de estas macros "genéricas". No está relacionado específicamente con OLE. A continuación se muestra el conjunto de macros genéricas:

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

Además de realizar conversiones de texto, también hay macros y funciones auxiliares para convertir TEXTMETRIC, DEVMODE, BSTR y cadenas asignadas de OLE. Estas macros están fuera del ámbito de esta discusión: consulte AFXPRIV. H para obtener más información sobre esas macros.

Macros de conversión de OLE

Las macros de conversión OLE están diseñadas específicamente para controlar funciones que esperan caracteres OLESTR . Si examina los encabezados OLE, verá muchas referencias a LPCOLESTR y OLECHAR. Estos tipos se usan para hacer referencia al tipo de caracteres que se usan en interfaces OLE de una manera que no es específica de la plataforma. OLECHAR se asigna a char en plataformas Win16 y Macintosh y WCHAR en Win32.

Para mantener el número de directivas de #ifdef en el código MFC como mínimo, tenemos una macro similar para cada conversión en la que intervienen las cadenas OLE. Las macros siguientes son las más usadas:

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

De nuevo, existen macros similares para gestionar cadenas asignadas a TEXTMETRIC, DEVMODE, BSTR y OLE. Consulte AFXPRIV. H para obtener más información.

Otras consideraciones

No use las macros en un bucle ajustado. Por ejemplo, no desea escribir el siguiente tipo de código:

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

}

El código anterior podría dar lugar a la asignación de megabytes de memoria en la pila en función del contenido de la cadena lpsz . También se tarda tiempo en convertir la cadena para cada iteración del bucle. En su lugar, mueva estas conversiones constantes fuera del bucle:

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

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

}

Si la cadena no es constante, encapsula la llamada al método en una función. Esto permitirá que el búfer de conversión se libere cada vez. Por ejemplo:

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]);

}

Nunca devuelva el resultado de una de las macros, a menos que el valor devuelto implique realizar una copia de los datos antes de la devolución. Por ejemplo, este código es incorrecto:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

El código anterior podría corregirse cambiando el valor devuelto a algo que copia el valor:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

Las macros son fáciles de usar y fáciles de insertar en el código, pero como se puede decir de las advertencias anteriores, debe tener cuidado al usarlos.

Consulte también

Notas técnicas por número
Notas Técnicas por Categoría