Partager via


TN059 : Utilisation des macros de conversion MFC MBCS/Unicode

Remarque

La note technique suivante n’a pas été mise à jour depuis sa première inclusion dans la documentation en ligne. Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrectes. Pour obtenir les informations les plus récentes, il est recommandé de rechercher la rubrique intéressante dans l’index de documentation en ligne.

Cette remarque explique comment utiliser les macros pour la conversion MBCS/Unicode, qui sont définies dans AFXPRIV.H. Ces macros sont les plus utiles si votre application traite directement avec l’API OLE ou pour une raison quelconque, doit souvent effectuer une conversion entre Unicode et MBCS.

Aperçu

Dans MFC 3.x, une DLL spéciale a été utilisée (MFCANS32.DLL) pour convertir automatiquement entre Unicode et MBCS lorsque des interfaces OLE ont été appelées. Cette DLL était une couche presque transparente qui permettait aux applications OLE d’être écrites comme si les API ET interfaces OLE étaient MBCS, même s’ils sont toujours Unicode (sauf sur Macintosh). Bien que cette couche ait été pratique et a permis aux applications d’être rapidement transférées de Win16 vers Win32 (MFC, Microsoft Word, Microsoft Excel et VBA, ne sont que certaines des applications Microsoft qui utilisaient cette technologie), elles ont eu un impact parfois significatif sur les performances. Pour cette raison, MFC 4.x n’utilise pas cette DLL et parle directement aux interfaces OLE Unicode. Pour ce faire, MFC doit effectuer une conversion en Unicode en MBCS lors de l’appel à une interface OLE et doit souvent effectuer une conversion en MBCS à partir d’Unicode lors de l’implémentation d’une interface OLE. Pour gérer cette opération efficacement et facilement, un certain nombre de macros ont été créées pour faciliter cette conversion.

L’un des principaux obstacles à la création d’un tel ensemble de macros est l’allocation de mémoire. Étant donné que les chaînes ne peuvent pas être converties directement, une nouvelle mémoire pour contenir les résultats convertis doit être allouée. Cela peut avoir été effectué avec du code similaire à ce qui suit :

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

Cette approche présente un certain nombre de problèmes. Le principal problème est qu’il s’agit d’un grand nombre de code à écrire, tester et déboguer. Quelque chose qui était un appel de fonction simple, est maintenant beaucoup plus complexe. En outre, il y a une surcharge importante du runtime dans ce cas. La mémoire doit être allouée sur le tas et libérée chaque fois qu’une conversion est effectuée. Enfin, le code ci-dessus devra se voir ajouter le #ifdefs approprié pour les builds Unicode et Macintosh (qui ne nécessitent pas que cette conversion ait lieu).

La solution que nous avons créée consiste à créer des macros qui masquent la différence entre les différentes plateformes et 2) utiliser un schéma d’allocation de mémoire efficace, et 3) sont faciles à insérer dans le code source existant. Voici un exemple de l’une des définitions :

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

L’utilisation de cette macro au lieu du code ci-dessus et les choses sont beaucoup plus simples :

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

Il existe des appels supplémentaires où la conversion est nécessaire, mais l’utilisation des macros est simple et efficace.

L’implémentation de chaque macro utilise la fonction _alloca() pour allouer de la mémoire provenant de la pile au lieu du tas. Il est beaucoup plus rapide d’allouer de la mémoire à partir de la pile plutôt que du tas, car elle est automatiquement libérée lorsque la fonction est désactivée. En outre, les macros évitent d’appeler MultiByteToWideChar (ou WideCharToMultiByte) plusieurs fois. Pour ce faire, allouez un peu plus de mémoire que nécessaire. Nous savons qu’un MBC sera converti au maximum en un WCHAR et que pour chaque WCHAR, nous aurons un maximum de deux octets MBC. En allouant un peu plus que nécessaire, mais toujours suffisamment pour gérer la conversion, le deuxième appel à la fonction de conversion est évité. L’appel à la fonction AfxA2Whelper d’assistance réduit le nombre d’arguments push qui doivent être effectués pour effectuer la conversion (cela entraîne un code plus petit que s’il est appelé MultiByteToWideChar directement).

Pour que les macros aient de l’espace pour stocker la longueur temporaire, il est nécessaire de déclarer une variable locale appelée _convert qui effectue cette opération dans chaque fonction qui utilise les macros de conversion. Pour ce faire, appelez la macro USES_CONVERSION comme indiqué ci-dessus dans l’exemple.

Il existe à la fois des macros de conversion génériques et des macros spécifiques OLE. Ces deux ensembles de macros différents sont décrits ci-dessous. Toutes les macros résident dans AFXPRIV.H.

Macros de conversion génériques

Les macros de conversion génériques forment le mécanisme sous-jacent. L’exemple de macro et l’implémentation présentés dans la section précédente, A2W, est une macro « générique » de ce type. Elle n’est pas liée spécifiquement à OLE. L’ensemble de macros génériques est répertorié ci-dessous :

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

Outre les conversions de texte, il existe également des macros et des fonctions d’assistance pour la conversion de TEXTMETRIC, DEVMODE, BSTR, et des chaînes allouées par OLE. Ces macros dépassent le cadre de cette discussion : reportez-vous à AFXPRIV. H pour plus d’informations sur ces macros.

Macros de conversion OLE

Les macros de conversion OLE sont conçues spécifiquement pour la gestion des fonctions qui attendent des caractères OLESTR . Si vous examinez les en-têtes OLE, vous verrez de nombreuses références à LPCOLESTR et OLECHAR. Ces types sont utilisés pour faire référence au type de caractères utilisés dans les interfaces OLE d’une manière qui n’est pas spécifique à la plateforme. OLECHAR est mappé à char des plateformes Win16 et Macintosh et WCHAR dans Win32.

Pour conserver au minimum le nombre de directives #ifdef dans le code MFC, nous disposons d’une macro similaire pour chaque conversion qui implique les chaînes OLE. Les macros suivantes sont les plus couramment utilisées :

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

Là encore, il existe des macros similaires pour gérer TEXTMETRIC, DEVMODE, BSTR, et les chaînes allouées par OLE. Reportez-vous à AFXPRIV. H pour plus d’informations.

Autres considérations

N’utilisez pas les macros dans une boucle serrée. Par exemple, vous ne souhaitez pas écrire le type de code suivant :

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

}

Le code ci-dessus peut entraîner l’allocation de mégaoctets de mémoire sur la pile en fonction du contenu de la chaîne lpsz ! Il faut également du temps pour convertir la chaîne pour chaque itération de la boucle. Au lieu de cela, déplacez ces conversions constantes hors de la boucle :

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

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

}

Si la chaîne n’est pas constante, encapsulez l’appel de méthode dans une fonction. Cela permet de libérer la mémoire tampon de conversion à chaque fois. Par exemple:

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

}

Ne retournez jamais le résultat de l’une des macros, sauf si la valeur de retour implique d’effectuer une copie des données avant le retour. Par exemple, ce code est incorrect :

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

Le code ci-dessus peut être résolu en modifiant la valeur de retour en quelque chose qui copie la valeur :

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

Les macros sont faciles à utiliser et faciles à insérer dans votre code, mais comme vous pouvez le dire à partir des mises en garde ci-dessus, vous devez être prudent lors de leur utilisation.

Voir aussi

Notes techniques par numéro
Notes techniques par catégorie