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 ser incorrectos. Para obtener información más reciente, se recomienda buscar el tema de interés en el índice de la documentación en línea.

En esta nota se describe cómo usar las macros para la conversión de MBCS/Unicode que se definen en AFXPRIV.H. Estas macros son más útiles si la aplicación se ocupa directamente de la API de OLE o si, por algún motivo, necesita convertir a menudo 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 las interfaces de OLE. Esta DLL era una capa casi transparente que permitía escribir aplicaciones OLE como si las API y las interfaces de OLE fueran MBCS, a pesar de que siempre son Unicode (excepto en Macintosh). Aunque esta capa era conveniente y permitía que las aplicaciones se migraran 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), a veces, afectaba significativamente al rendimiento. Por este motivo, MFC 4.x no usa este archivo DLL y, en su lugar, se comunica directamente con las interfaces OLE de Unicode. Para ello, MFC debe convertir de Unicode a MBCS al realizar una llamada a una interfaz de OLE y, a menudo, tiene que convertir a MBCS desde Unicode al implementar una interfaz de 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 las cadenas no se pueden convertir in situ, se debe asignar nueva memoria que contenga los resultados convertidos. Esto se podría hacer con un código parecido 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 presenta una serie de problemas. El problema principal es que hay mucho código que escribir, probar y depurar. Algo que era una llamada de función simple, ahora se ha vuelto mucho más complejo. Además, hay una sobrecarga significativa en el entorno de ejecución al hacerlo. La memoria debe asignarse en el montón y liberarse cada vez que se realiza una conversión. Por último, el código anterior debería tener el valor de #ifdefs correcto agregado a las compilaciones de Unicode y Macintosh (que no requieren que se produzca esta conversión).

La solución a la que hemos llegado consiste en crear algunas macros que 1) enmascaren la diferencia entre las distintas plataformas, 2) usen un esquema de asignación de memoria eficaz y 3) sean 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 facilitará mucho las cosas:

// 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 de desde el 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 suficiente para controlar 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 argumentos que se deben realizar para llevar a cabo la conversión (esto da como resultado un código más pequeño, que si se llama a MultiByteToWideChar directamente).

Para que las macros tengan espacio para almacenar una longitud temporal, es necesario declarar una variable local denominada _convert que lo haga en cada función que use las macros de conversión. Esto se hace invocando 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 e implementación que se muestran en la sección anterior, A2W, es una de estas macros "genéricas". No está relacionada 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 las cadenas asignadas de TEXTMETRIC, DEVMODE, BSTR y OLE. Estas macros están fuera del ámbito de este análisis: consulte AFXPRIV.H para más información sobre esas macros.

Macros de conversión de OLE

Las macros de conversión de OLE están diseñadas específicamente para controlar funciones que esperan caracteres OLESTR. Si examina los encabezados de OLE, verá muchas referencias a LPCOLESTR y OLECHAR. Estos tipos se usan para hacer referencia al tipo de caracteres que se usan en las 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 #ifdef en el código MFC al mínimo, tenemos una macro similar para cada conversión en la que intervengan las cadenas OLE. Las siguientes macros son las que se usan más habitualmente:

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

De nuevo, hay macros parecidas para las cadenas asignadas de TEXTMETRIC, DEVMODE, BSTR y OLE. Consulte AFXPRIV.H para 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 de 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 una constante, encapsula la llamada de método en una función. Esto permitirá liberar el búfer de conversión 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 permita copiar 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 de insertar en el código, pero como puede ver por las advertencias anteriores, debe tener cuidado al usarlas.

Consulte también

Notas técnicas por número
Notas técnicas por categoría