TN059: usando macros de conversão MBCS/Unicode MFC

Observação

A nota técnica a seguir não foi atualizada desde que foi incluída pela primeira vez na documentação online. Como resultado, alguns procedimentos e tópicos podem estar desatualizados ou incorretos. Para obter as informações mais recentes, é recomendável que você pesquise o tópico de interesse no índice de documentação online.

Esta observação descreve como usar as macros para conversão MBCS/Unicode, que são definidas no AFXPRIV.H. Essas macros são mais úteis se o aplicativo lida diretamente com a API OLE ou, por algum motivo, precisa ser frequentemente convertida entre Unicode e MBCS.

Visão geral

No MFC 3.x, uma DLL especial era usada (MFCANS32.DLL) para converter automaticamente entre Unicode e MBCS quando as interfaces OLE eram chamadas. Essa DLL era uma camada quase transparente que permitia que aplicativos OLE fossem escritos como se as APIs e interfaces OLE fossem MBCS, mesmo que sempre fossem Unicode (exceto no Macintosh). Embora essa camada fosse conveniente e permitisse que os aplicativos fossem portados rapidamente do Win16 para o Win32 (MFC, Microsoft Word, Microsoft Excel e VBA são apenas alguns dos aplicativos da Microsoft que usaram essa tecnologia), ela às vezes trazia um impacto significativo no desempenho. Por esse motivo, o MFC 4.x não usa essa DLL e, em vez disso, fala diretamente com as interfaces OLE Unicode. Para fazer isso, o MFC precisa converter em Unicode para MBCS ao fazer uma chamada para uma interface OLE e, muitas vezes, precisa converter do Unicode para MBCS ao implementar uma interface OLE. Para lidar com isso com eficiência e facilidade, várias macros foram criadas para facilitar essa conversão.

Um dos maiores obstáculos para criar esse conjunto de macros é a alocação de memória. Como as cadeias de caracteres não podem ser convertidas no local, uma nova memória para manter os resultados convertidos deve ser alocada. Isso poderia ter sido feito com código semelhante ao seguinte:

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

Essa abordagem tem uma série de problemas. O principal problema é que há muito código para escrever, testar e depurar. Algo que era uma chamada de função simples, agora é muito mais complexo. Além disso, há uma sobrecarga significativa de runtime ao fazer isso. A memória deve ser alocada no heap e liberada sempre que uma conversão é feita. Por fim, o código acima precisaria ter a adição apropriada de #ifdefs para builds Unicode e Macintosh (que não exigem que essa conversão ocorra).

A solução que criamos é criar algumas macros que 1) mascaram a diferença entre as várias plataformas, 2) usam um esquema de alocação de memória eficiente e 3) são fáceis de inserir no código-fonte existente. Aqui está um exemplo de uma das definições:

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

Use essa macro em vez do código acima e as coisas serão muito mais simples:

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

Há chamadas extras nas quais a conversão é necessária, mas usar as macros é simples e eficaz.

A implementação de cada macro usa a função _alloca() para alocar memória da pilha em vez do heap. Alocar memória da pilha é muito mais rápido do que alocar memória no heap e a memória é liberada automaticamente quando a função é encerrada. Além disso, as macros evitam chamar MultiByteToWideChar (ou WideCharToMultiByte) mais de uma vez. Isso é feito alocando um pouco mais de memória do que o necessário. Sabemos que um MBC será convertido em, no máximo, um WCHAR e que para cada WCHAR teremos um máximo de dois bytes MBC. Ao alocar um pouco mais do que o necessário, mas sempre o suficiente para lidar com a conversão, evita-se uma segunda chamada para a função de conversão. A chamada para a função auxiliar AfxA2Whelper reduz o número de pushes de argumento que devem ser feitos para executar a conversão (isso resulta em um código menor do que se ele chamasse MultiByteToWideChar diretamente).

Para que as macros tenham espaço para armazenar um comprimento temporário, é necessário declarar uma variável local chamada _convert que faz isso em cada função que usa as macros de conversão. Isso é feito invocando a macro USES_CONVERSION, conforme visto acima no exemplo.

Há macros de conversão genéricas e macros específicas do OLE. Esses dois conjuntos de macros diferentes são discutidos abaixo. Todas as macros residem em AFXPRIV.H.

Macros de Conversão Genérica

As macros de conversão genérica formam o mecanismo subjacente. O exemplo de macro e a implementação mostrados na seção anterior, A2W, é uma macro "genérica". Não está relacionado especificamente ao OLE. O conjunto de macros genéricas está listado abaixo:

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

Além de fazer conversões de texto, também há macros e funções auxiliares para converter TEXTMETRIC, DEVMODE, BSTR e cadeias de caracteres OLE alocadas. Essas macros estão além do escopo dessa discussão – consulte AFXPRIV.H para obter mais informações sobre essas macros.

Macros de conversão OLE

As macros de conversão OLE foram projetadas especificamente para lidar com funções que esperam caracteres OLESTR. Se você examinar os cabeçalhos OLE, verá muitas referências a LPCOLESTR e OLECHAR. Esses tipos são usados para se referir ao tipo de caracteres usados em interfaces OLE de uma forma que não seja específico da plataforma. O OLECHAR mapeia para char nas plataformas Win16 e Macintosh e WCHAR no Win32.

Para manter o número de diretivas #ifdef no código MFC no mínimo, temos uma macro semelhante para cada conversão em que as cadeias de caracteres OLE estejam envolvidas. As macros a seguir são as mais comumente usadas:

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

Novamente, há macros semelhantes para executar cadeias de caracteres alocadas TEXTMETRIC, DEVMODE, BSTR e OLE. Consulte AFXPRIV.H para obter mais informações.

Outras considerações

Não use as macros em um loop estreito. Por exemplo, você não deseja escrever o seguinte tipo de código:

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

}

O código acima pode resultar na alocação de megabytes de memória na pilha, dependendo do conteúdo da cadeia de caracteres lpsz! Também leva tempo para converter a cadeia de caracteres para cada iteração do loop. Em vez disso, mova essas conversões constantes para fora do loop:

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

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

}

Se a cadeia de caracteres não for constante, encapsule a chamada de método em uma função. Isso permitirá que o buffer de conversão seja sempre liberado. Por exemplo:

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 retorne o resultado de uma das macros, a menos que o valor retornado implique fazer uma cópia dos dados antes do retorno. Por exemplo, esse código é ruim:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

O código acima pode ser corrigido alterando o valor retornado para algo que copia o valor:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

As macros são fáceis de usar e fáceis de inserir em seu código, mas como você pode perceber pelas ressalvas acima, você precisa ter cuidado ao usá-las.

Confira também

Observações técnicas por número
Observações técnicas por categoria