TN059:使用 MFC MBCS/Unicode 转换宏

注释

自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。

此说明介绍如何对 AFXPRIV.H 中定义的 MBCS/Unicode 转换使用宏。 如果应用程序直接处理 OLE API 或出于某种原因,这些宏最有用,通常需要在 Unicode 和 MBCS 之间转换。

概述

在 MFC 3.x 中,在调用 OLE 接口时,使用特殊 DLL(MFCANS32.DLL)在 Unicode 和 MBCS 之间自动转换。 此 DLL 是一个几乎透明的层,允许编写 OLE 应用程序,就像 OLE API 和接口是 MBCS 一样,即使它们始终是 Unicode(在 Macintosh 上除外)。 虽然此层很方便,并允许应用程序快速从 Win16 移植到 Win32(MFC、Microsoft Word、Microsoft Excel 和 VBA),但这只是使用这项技术的一些Microsoft应用程序),但有时性能显著。 因此,MFC 4.x 不使用此 DLL,而是直接与 Unicode OLE 接口对话。 为此,MFC 需要在调用 OLE 接口时转换为 MBCS,在实现 OLE 接口时通常需要从 Unicode 转换为 MBCS。 为了高效轻松地处理此情况,创建了许多宏,以便更轻松地进行此转换。

创建此类宏集的最大障碍之一是内存分配。 由于无法就地转换字符串,因此必须分配用于保存转换结果的新内存。 这可以通过类似于下面的代码来完成:

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

此方法是一些问题。 主要问题是编写、测试和调试代码很多。 这是一个简单的函数调用,现在要复杂得多。 此外,这样做会产生巨大的运行时开销。 每次完成转换时,都必须在堆上分配内存并释放内存。 最后,上述代码需要为 Unicode 和 Macintosh 版本添加适当的 #ifdefs 代码(不需要进行此转换)。

我们想出的解决方案是创建一些宏,其中 1)屏蔽各种平台之间的差异,2) 使用高效的内存分配方案,3) 很容易插入到现有源代码中。 下面是其中一个定义的示例:

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

使用此宏而不是上述代码,作要简单得多:

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

在需要转换的情况下,有额外的调用,但使用宏非常简单且有效。

每个宏的实现使用 _alloca() 函数从堆栈而不是堆分配内存。 从堆栈分配内存比在堆上分配内存要快得多,退出函数时会自动释放内存。 此外,宏可避免多次调用 MultiByteToWideChar (或 WideCharToMultiByte)。 这是通过分配比必要的内存多一点来完成的。 我们知道,MBC 最多会转换为一个 WCHAR ,对于每个 WCHAR ,最多将有两个 MBC 字节。 通过分配比必要多一点,但始终足以处理转换,可以避免第二次调用转换函数。 对帮助程序函数 AfxA2Whelper 的调用可减少必须执行的自变量推送数,以便执行转换(这会导致代码更小,而不是直接调用 MultiByteToWideChar )。

为了使宏有空间来存储临时长度,必须声明一个名为_convert的局部变量,以便在使用转换宏的每个函数中执行此作。 这是通过调用示例中所示的USES_CONVERSION宏来完成的。

有泛型转换宏和特定于 OLE 的宏。 下面讨论了这两个不同的宏集。 所有宏都驻留在 AFXPRIV.H 中。

泛型转换宏

泛型转换宏构成基础机制。 上一部分所示的宏示例和实现 A2W 是这样的“泛型”宏。 它与 OLE 无关。 下面列出了一组泛型宏:

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

除了执行文本转换,还有用于转换 TEXTMETRICDEVMODEBSTR和 OLE 分配字符串的宏和帮助程序函数。 这些宏超出了此讨论的范围 - 请参阅 AFXPRIV。有关这些宏的详细信息,请参阅 H。

OLE 转换宏

OLE 转换宏专为处理需要 OLESTR 字符的函数而设计。 如果检查 OLE 标头,则会看到对 LPCOLESTROLECHAR 的许多引用。 这些类型用于引用 OLE 接口中使用的字符类型,其方式不特定于平台。 OLECHAR 映射到 char Win16 和 Macintosh 平台和 Win32 中的 WCHAR

为了将 MFC 代码中的 #ifdef 指令数保持在最低水平,对于涉及 OLE 字符串的每个转换,我们都有类似的宏。 以下宏是最常用的宏:

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

同样,也有类似的宏用于执行 TEXTMETRIC、DEVMODE、BSTR 和 OLE 分配的字符串。 请参阅 AFXPRIV。有关详细信息,请参阅 H。

其他注意事项

不要在紧密循环中使用宏。 例如,你不想编写以下类型的代码:

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

}

上面的代码可能会导致在堆栈上分配兆字节内存,具体取决于字符串 lpsz 的内容! 还需要一段时间才能转换循环每次迭代的字符串。 相反,将此类常量转换移出循环:

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

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

}

如果字符串不是常量,则将方法调用封装到函数中。 这将允许每次释放转换缓冲区。 例如:

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

}

从不返回其中一个宏的结果,除非返回值意味着在返回之前复制数据。 例如,此代码不正确:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

可以通过将返回值更改为复制值的内容来修复上述代码:

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

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

宏易于使用且易于插入到代码中,但正如从上述注意事项中所示,在使用宏时需要小心。

另请参阅

按编号列出的技术说明
按类别列出的技术说明