TN059: utilizzo di macro di conversione MFC MBCS/Unicode
Nota
La seguente nota tecnica non è stata aggiornata da quando è stata inclusa per la prima volta nella documentazione online. Di conseguenza, alcune procedure e argomenti potrebbero essere non aggiornati o errati. Per le informazioni più recenti, è consigliabile cercare l'argomento di interesse nell'indice della documentazione online.
Questa nota descrive come usare le macro per la conversione MBCS/Unicode, definite in AFXPRIV.H. Queste macro sono più utili se l'applicazione gestisce direttamente l'API OLE o per qualche motivo, spesso deve eseguire la conversione tra Unicode e MBCS.
Panoramica
In MFC 3.x è stata usata una DLL speciale (MFCANS32.DLL) per eseguire automaticamente la conversione tra Unicode e MBCS quando sono state chiamate interfacce OLE. Questa DLL era un livello quasi trasparente che consentiva la scrittura delle applicazioni OLE come se le API OLE e le interfacce fossero MBCS, anche se sono sempre Unicode (tranne che in Macintosh). Anche se questo livello era pratico e consentiva la conversione rapida delle applicazioni da Win16 a Win32 (MFC, Microsoft Word, Microsoft Excel e VBA, sono solo alcune delle applicazioni Microsoft che usavano questa tecnologia), ha avuto un impatto a volte significativo sulle prestazioni. Per questo motivo, MFC 4.x non usa questa DLL e comunica direttamente con le interfacce OLE Unicode. A tale scopo, MFC deve eseguire la conversione in Unicode in MBCS quando si effettua una chiamata a un'interfaccia OLE e spesso deve eseguire la conversione in MBCS da Unicode quando si implementa un'interfaccia OLE. Per gestire questa operazione in modo efficiente e semplice, sono state create numerose macro per semplificare questa conversione.
Uno degli ostacoli principali per la creazione di un set di macro di questo tipo è l'allocazione di memoria. Poiché le stringhe non possono essere convertite sul posto, è necessario allocare una nuova memoria per contenere i risultati convertiti. Questa operazione potrebbe essere stata eseguita con codice simile al seguente:
// 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;
Questo approccio è un certo numero di problemi. Il problema principale è che si tratta di un sacco di codice per scrivere, testare ed eseguire il debug. Qualcosa che era una semplice chiamata di funzione, è ora molto più complesso. Inoltre, si verifica un sovraccarico significativo in fase di esecuzione. La memoria deve essere allocata nell'heap e liberata ogni volta che viene eseguita una conversione. Infine, il codice precedente avrebbe bisogno di avere aggiunto il codice appropriato #ifdefs
per le compilazioni Unicode e Macintosh (che non richiedono che questa conversione venga eseguita).
La soluzione con cui è stato creato è creare alcune macro che 1) mascherare la differenza tra le varie piattaforme e 2) usare uno schema di allocazione di memoria efficiente e 3) è facile da inserire nel codice sorgente esistente. Di seguito è riportato un esempio di una delle definizioni:
#define A2W(lpa) (\
((LPCSTR)lpa == NULL) NULL : (\
_convert = (strnlen(lpa)+1),\
AfxA2WHelper((LPWSTR) alloca(_convert*2),
lpa,
_convert)\)\)
L'uso di questa macro invece del codice precedente e le cose sono molto più semplici:
// use it to call OLE here
USES_CONVERSION;
pI->SomeFunctionThatNeedsUnicode(T2OLE(lpszA));
Esistono chiamate aggiuntive in cui è necessaria la conversione, ma l'uso delle macro è semplice ed efficace.
L'implementazione di ogni macro usa la funzione _alloca() per allocare memoria dallo stack anziché dall'heap. L'allocazione della memoria dallo stack è molto più veloce rispetto all'allocazione della memoria nell'heap e la memoria viene liberata automaticamente quando la funzione viene chiusa. Inoltre, le macro evitano di chiamare MultiByteToWideChar
(o WideCharToMultiByte
) più volte. Questa operazione viene eseguita allocando un po'più di memoria di quanto sia necessario. Sappiamo che un MBC verrà convertito al massimo in un WCHAR e che per ogni WCHAR avremo un massimo di due byte MBC. Allocando un po ' di più del necessario, ma sempre abbastanza per gestire la conversione la seconda chiamata alla seconda chiamata alla funzione di conversione viene evitato. La chiamata alla funzione AfxA2Whelper
helper riduce il numero di push di argomenti che devono essere eseguiti per eseguire la conversione ( questo comporta codice più piccolo, rispetto a quello chiamato MultiByteToWideChar
direttamente).
Affinché le macro dispongano di spazio per archiviare la lunghezza temporanea, è necessario dichiarare una variabile locale denominata _convert che esegue questa operazione in ogni funzione che utilizza le macro di conversione. Questa operazione viene eseguita richiamando la macro U edizione StandardS_CONVERSION come illustrato in precedenza nell'esempio.
Esistono sia macro di conversione generiche che macro specifiche OLE. Questi due set di macro diversi sono illustrati di seguito. Tutte le macro risiedono in AFXPRIV.H.
Macro di conversione generiche
Le macro di conversione generiche costituiscono il meccanismo sottostante. L'esempio di macro e l'implementazione illustrati nella sezione precedente, A2W, è una macro "generica". Non è correlato specificamente a OLE. Il set di macro generiche è elencato di seguito:
A2CW (LPCSTR) -> (LPCWSTR)
A2W (LPCSTR) -> (LPWSTR)
W2CA (LPCWSTR) -> (LPCSTR)
W2A (LPCWSTR) -> (LPSTR)
Oltre a eseguire conversioni di testo, sono disponibili anche macro e funzioni helper per la conversione TEXTMETRIC
di stringhe allocate , DEVMODE
BSTR
, e OLE. Queste macro non rientrano nell'ambito di questa discussione: fare riferimento ad AFXPRIV. H per altre informazioni su tali macro.
Macro di conversione OLE
Le macro di conversione OLE sono progettate specificamente per la gestione delle funzioni che prevedono caratteri OLESTR . Se si esaminano le intestazioni OLE, verranno visualizzati molti riferimenti a LPCOLESTR e OLECHAR. Questi tipi vengono usati per fare riferimento al tipo di caratteri utilizzati nelle interfacce OLE in modo non specifico per la piattaforma. OLECHAR esegue il mapping a char
nelle piattaforme Win16 e Macintosh e WCHAR in Win32.
Per mantenere minimo il numero di direttive #ifdef nel codice MFC, è disponibile una macro simile per ogni conversione in cui sono coinvolte le stringhe OLE. Le macro seguenti sono le più comunemente usate:
T2COLE (LPCTSTR) -> (LPCOLESTR)
T2OLE (LPCTSTR) -> (LPOLESTR)
OLE2CT (LPCOLESTR) -> (LPCTSTR)
OLE2T (LPCOLESTR) -> (LPCSTR)
Anche in questo caso, esistono macro simili per l'esecuzione di stringhe allocate TEXTMETRIC, DEVMODE, BSTR e OLE. Fare riferimento ad AFXPRIV. H per altre informazioni.
Altre considerazioni
Non utilizzare le macro in un ciclo stretto. Ad esempio, non si vuole scrivere il tipo di codice seguente:
void BadIterateCode(LPCTSTR lpsz)
{
USES_CONVERSION;
for (int ii = 0; ii <10000; ii++)
pI->SomeMethod(ii, T2COLE(lpsz));
}
Il codice precedente potrebbe comportare l'allocazione di megabyte di memoria nello stack a seconda del contenuto della stringa lpsz
. La conversione della stringa per ogni iterazione del ciclo richiede tempo. Spostare invece tali conversioni costanti all'esterno del ciclo:
void MuchBetterIterateCode(LPCTSTR lpsz)
{
USES_CONVERSION;
LPCOLESTR lpszT = T2COLE(lpsz);
for (int ii = 0; ii <10000; ii++)
pI->SomeMethod(ii, lpszT);
}
Se la stringa non è costante, incapsulare la chiamata al metodo in una funzione. In questo modo il buffer di conversione verrà liberato ogni volta. Ad esempio:
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]);
}
Non restituire mai il risultato di una delle macro, a meno che il valore restituito non implica la creazione di una copia dei dati prima della restituzione. Ad esempio, questo codice non è valido:
LPTSTR BadConvert(ISomeInterface* pI)
{
USES_CONVERSION;
LPOLESTR lpsz = NULL;
pI->GetFileName(&lpsz);
LPTSTR lpszT = OLE2T(lpsz);
CoMemFree(lpsz);
return lpszT; // bad! returning alloca memory
}
Il codice precedente può essere corretto modificando il valore restituito in un elemento che copia il valore:
CString BetterConvert(ISomeInterface* pI)
{
USES_CONVERSION;
LPOLESTR lpsz = NULL;
pI->GetFileName(&lpsz);
LPTSTR lpszT = OLE2T(lpsz);
CoMemFree(lpsz);
return lpszT; // CString makes copy
}
Le macro sono facili da usare e facili da inserire nel codice, ma come si può dire dalle avvertenze precedenti, è necessario prestare attenzione quando vengono usate.