Windows 平台编程的字符串那些注意的东西
Unicode 与多字节Windows 支持 Unicode 后, 所有和字符串相关的 Windows API 都有了两个版本, 以 _A 结尾的和带 _W 结尾。 比如函数 MessageBox 就有 MessageBoxA 和 MessageBoxW 两个版本。 MessageBox 只是一个宏,在编译的时候根据项目的字符集设置, 用 MessageBoxA 或者 MessageBoxW 替换。 User32.dll 导出了 MessageBoxA 和 MessageBoxW,没有导出 MessageBox, 换而言之 MessageBox 函数并不存在。 图: Unicode 字符集设置 int WINAPI MessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType); int WINAPI MessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType); #ifdef UNICODE #define MessageBox MessageBoxW // 如果项目使用 Unicode,UNICODE 宏会被定义,MessageBox 被 MessageBoxW 替换 #else #define MessageBox MessageBoxA #endif // !UNICODE C 风格字符串Windows API 统一使用一系列数据类型, 比如 DWORD INT32 等等, 而字符串的表示使用 CHAR,LPSTR,LPCSTR
CHAR filename[MAX_PATH] = "C:WindowsSystem32user32.dll"; LPSTR filename2 = filename; // 正确 LPCSTR filename3 = "C:WindowsSystem32user32.dll"; // 正确 //LPSTR filename4 = "C:WindowsSystem32user32.dll"; // 编译错误 C2440, 无法从“const char [31]”转换为“LPSTR” LoadLibraryA(filename); Unicode 版本的 Windows API 使用 LPWSTR,LPCWSTR 来传递字符串参数
WCHAR filename[MAX_PATH] = L"C:WindowsSystem32user32.dll"; LPWSTR filename2 = filename; // 正确 LPCWSTR filename3 = L"C:WindowsSystem32user32.dll"; // 正确 //LPWSTR filename4 = L"C:WindowsSystem32user32.dll"; // 编译错误 C2440, 无法从“const wchar_t [31]”转换为“LPWSTR” LoadLibraryW(filename); 还有中性的 LPTSTR,LPCTSTR, 这里的中性的意思是, 项目字符集无论是 Unicode 还是 多字节字符集, 代码都是可以编译的 如果项目使用 Unicode 字符集
如果项目使用多字节字符集
LPTSTR 字符串允许修改 当使用中性的字符串字面量时, 要把字面量放在 _T() 宏中, _T() 宏的使用是根据字符集设置决定要不要在字面量前面加一个 L 比如 _T("user32.dll") 在 Unicode 字符集项目下展开为 L"user32.dll" 也可以使用TEXT() 宏, 用法功能都和 _T() 宏一样 TCHAR filename[MAX_PATH] = _T("C:WindowsSystem32user32.dll"); LPTSTR filename2 = filename; // 正确 LPCTSTR filename3 = _T("C:WindowsSystem32user32.dll"); // 正确 LoadLibrary(filename); // 中性版本的 LoadLibrary ATL/MFC 中的 CString 和 CStrBuf
CStringW 里有对 LPCWSTR 隐式类型转换操作重载,CStringW::operator LPCWSTR() const,CStringW 对象可以直接赋值给 LPCWSTR 指针(const wchar_t* 指针),可以在任何使用 LPCWSTR 指针的位置使用 CStringW 对象。 CStringW message = L"这是一条消息"; LPCTSTR message2 = message; // 隐式调用 message.operator LPCWSTR() const wchar_t* message3 = message; // 隐式调用 message.operator LPCWSTR() CStringW title = L"这是标题"; MessageBoxW(NULL,message2,title,MB_OK); // 隐式调用 title.operator LPCWSTR() 但是 CStringW 不能直接赋值给 LPCSTR 指针(const char* ),因为 CStringW 没有对 LPCSTR 指针(const char*指针) 的类型转换重载 CStringW message = L"这是一条消息"; LPCSTR message2 = message; // 编译错误 const char* message3 = message; // 编译错误 CStringW title = L"这是标题"; MessageBoxA(NULL,MB_OK); // 编译错误 当一个 CStringW 对象赋值给 LPCWSTR, LPCWSTR 指针指向的内存由 CStringW 对象来管理, 当 CStringW 对象被删除时, 这个指针会变成悬垂指针。所以在使用这个指针时, 必须保证 CStringW 对象是有效的 像这样的操作是错误的, GetSomePath() 返回的实际是上 悬垂指针, 像这种情况可以使用 CStringW 类型返回值 LPCWSTR GetSomePath() { CStringW path; path = L"C:WindowsSystem32user32.dll"; return path; // 错误 } 相对的 CStringA 也是同样的操作, 可以隐式转换为 LPCSTR,这里不重复示例 CString 是中性的, 所以在初始化字符串应该用中性的 _T()宏,使用 C 字符串指针用中性的 LPCTSTR, API 调用也应该使用中性的, 这样可以确保在 Unicode 字符集和 多字节字符集两种字符集设置的情况都能编译通过 CString msg = _T("Welcome !"); LPCTSTR msg2 = msg; MessageBox(NULL,msg,msg2,MB_OK); 像下面这样的操作是错误的, 因为 CString 可以是 CStringW , 也可以是 CStringA, 根据项目的字符集设置决定, 如果使用了非中性的 C 字符串指针, 或者调用非中性 API , 那肯定有一种字符集下是会编译报错的。 也许你并不要求在两种字符集都能编译, 比方说只要求在 Unicode 字符集编译通过就行了, 那是不是就可以这样呢? 如果是这样还不如直接使用 CStringW , 这样代码意思会更明确, 并且即使在 多字节下编译也没问题。 CString msg = _T("Welcome !"); LPCSTR msg2 = msg; // 错误 LPCWSTR msg3 = msg; // 错误 MessageBoxA(NULL,nullptr,MB_OK); // 错误 MessageBoxW(NULL,MB_OK); // 错误 与 LPSTR 、 LPWSTR 和 LPTSTR 的转换 有时候需要从 API 获取一个字符串, 这就要求 CStringW 能够返回一个允许修改字符串内容的 LPWSTR,而不是 LPCWSTR,这时候可以使用 GetBuffer()和ReleaseBuffer() 方法,GetBuffer 可以接收一个字符数长度的参数, 返回一个足够大小的缓冲区指针, CStringW exePath; LPWSTR buffer = exePath.GetBuffer(MAX_PATH); DWORD pathLen = ::GetModuleFileNameW(NULL,buffer,MAX_PATH); exePath.ReleaseBuffer(pathLen); buffer = nullptr; MessageBoxW(NULL,exePath,L"",MB_OK); 像下面这样的操作是错误,是非常危险,非常 SB..., CStringW exePath; LPWSTR buffer = (LPWSTR)(LPCWSTR)exePath; // 错误 ::GetModuleFileNameW(NULL,MAX_PATH); MessageBoxW(NULL,MB_OK); 如果觉得 GetBuffer() / ReleaseBuffer() 的方式太麻烦, 你可以使用 CStrBuf 当然 CStrBuf 也有三版本, 另外两个是 CStrBufW 和 CStrBufA , 如果是 CStringW , 那肯定要用 CStringW, CStringW exePath; CStrBufW buffer(exePath,MAX_PATH); ::GetModuleFileNameW(NULL,MB_OK); 更少的代码 CStringW exePath; ::GetModuleFileNameW(NULL,CStrBufW(exePath,MAX_PATH),MB_OK); Unicode 、 多字节 和 中性字符串之间的互转这个问题一直困扰着一大片新手,一定有人大喊用 MultiByteToWideChar/WideCharToMultiByte 啊。 LPCSTR msg = "比方说这是一行文本"; // ANSI 编码 MessageBoxW(NULL,CStringW(msg),MB_OK); // Unicode 编码 MessageBox(NULL,CString(msg),MB_OK); // 中性 当然为了避免额外的转换操作, 在使用字面量初始化 CString CStringA CStringW 的时候, 最好使用指定的编码不要搞混 CStringA msg1 = "比方说这是一行文本"; CStringW msg2 = L"比方说这是一行文本"; CString msg3 = _T("比方说这是一行文本"); 还可以使用这几个ATL类互转,具体意思如其名, 这几个还有带 _EX 结尾的,具体请参考 ATL 源码
使用示例 LPCSTR msg = "比方说这是一行文本"; // ANSI 编码 MessageBoxW(NULL,CA2W(msg),MB_OK); // Unicode 编码 MessageBox(NULL,CA2T(msg),MB_OK); // 中性
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |