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 接口时需要将 Unicode 转换为 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 分配的字符串的宏和 Helper 函数。 这些宏超出了此次讨论的范围。有关这些宏的详细信息,请参阅 AFXPRIV.H。

OLE 转换宏

OLE 转换宏专用于处理需要 OLESTR 字符的函数。 如果你检查 OLE 标头,会看到许多对 LPCOLESTR 和 OLECHAR 的引用。 这些类型用于以一种非特定于平台的方法引用 OLE 接口中使用的字符类型。 OLECHAR 在 Win16 和 Macintosh 平台中映射到 char,在 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));

}

上述代码可能导致在堆栈上分配内存(以 MB 为单位),取决于字符串 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
}

虽然宏易于使用和插入到代码中,但从上面的警告可看出,你在使用宏时要特别谨慎。

另请参阅

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