Partilhar via


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

Observação

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

Esta nota descreve como usar as macros para conversão MBCS/Unicode, que são definidas em AFXPRIV.H. Essas macros são mais úteis se seu aplicativo lida diretamente com a API OLE ou, por algum motivo, muitas vezes precisa converter entre Unicode e MBCS.

Visão geral

No MFC 3.x, uma DLL especial foi usada (MFCANS32.DLL) para converter automaticamente entre Unicode e MBCS quando interfaces OLE foram 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 sejam sempre Unicode (exceto no Macintosh). Embora essa camada fosse conveniente e permitisse que os aplicativos fossem rapidamente portados do Win16 para o Win32 (MFC, Microsoft Word, Microsoft Excel e VBA, são apenas alguns dos aplicativos da Microsoft que usavam essa tecnologia), ela teve um impacto de desempenho às vezes significativo. Por esse motivo, MFC 4.x não usa essa DLL e, em vez disso, fala diretamente com as interfaces OLE Unicode. Para fazer isso, MFC precisa converter para Unicode para MBCS ao fazer uma chamada para uma interface OLE e, muitas vezes, precisa converter para MBCS de Unicode ao implementar uma interface OLE. A fim de lidar com isso de forma eficiente e fácil, uma série de macros foram criadas para tornar essa conversão mais fácil.

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 armazenar os resultados convertidos deve ser alocada. Isso poderia ter sido feito com um 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;

Esta abordagem constitui uma série de problemas. O principal problema é que é muito código para escrever, testar e depurar. Algo que era uma simples chamada de função, agora é muito mais complexo. Além disso, há uma sobrecarga de tempo de execução significativa ao fazê-lo. A memória tem que ser alocada no heap e liberada cada vez que uma conversão é feita. Finalmente, o código acima precisaria que #ifdefs fosse adicionado adequadamente para compilações Unicode e Macintosh (que não exigem que essa conversão aconteça).

A solução que encontramos é criar algumas macros que 1) mascaram a diferença entre as várias plataformas, e 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)\)\)

Usando esta macro em vez do código acima e as coisas são muito mais simples:

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

Existem chamadas extras onde 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 é libertada automaticamente quando a função é terminada. 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 de MBC. Ao alocar um pouco mais do que o necessário, mas sempre o suficiente para lidar com a conversão, a segunda chamada para a função de conversão é evitada. A chamada para a função AfxA2Whelper auxiliar reduz o número de pushes de argumento que devem ser feitos para executar a conversão (isso resulta em código menor, do que se fosse chamado MultiByteToWideChar diretamente).

Para que as macros tenham espaço para armazenar o 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, como visto acima no exemplo.

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

Macros de conversão genéricas

As macros de conversão genéricas formam o mecanismo subjacente. O exemplo de macro e implementação mostrada na seção anterior, A2W, é uma dessas macros "genéricas". Não está relacionado especificamente com o 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, há também macros e funções auxiliares para converter TEXTMETRIC, DEVMODE, BSTR e cadeias de caracteres alocadas pelo OLE. Essas macros estão além do escopo desta discussão - consulte AFXPRIV. H para obter mais informações sobre essas macros.

Macros de conversão OLE

As macros de conversão OLE são 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 é específica para a plataforma. OLECHAR mapeia para char em plataformas Win16 e Macintosh e WCHAR em Win32.

Para manter o número de diretivas #ifdef no código MFC a um mínimo, temos uma macro semelhante para cada conversão que onde as cadeias de caracteres OLE estão 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 fazer TEXTMETRIC, DEVMODE, BSTR e cadeias de caracteres alocadas OLE. Consulte AFXPRIV. H para mais informações.

Outras considerações

Não use as macros em um loop apertado. 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 de qual é o 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 circuito:

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, encapsular a chamada de método em uma função. Isso permitirá que o buffer de conversão seja liberado a cada vez. 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 de retorno implique fazer uma cópia dos dados antes do retorno. Por exemplo, este 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 de retorno para algo que copie 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 de inserir no seu código, mas como você pode ver pelas advertências acima, você precisa ter cuidado ao usá-las.

Ver também

Notas técnicas por número
Notas técnicas por categoria