背景

开发过Windows Mobile和Wince(Windows Embedded CE)的开发者,特别是Native C++开发者,或多或少都遇到过ANSI字符集和Unicode字符集的转换问题。本文试图把Windows Mobile和Wince(Windows Embedded CE)开发的字符集问题讲明白,其实这个题目有点ambitious和aggressive,就当成标题党吧。

简介

本文试图通过一篇文章讲清楚Windows Mobile和Wince(Windows Embedded CE) Native C++开发中字符集的转换问题。从字符集的概念入手,讲述Wince支持的所有字符串类型,以及各种类型的转换方法,最后给出使用建议。

什么是字符集

字符集(Character Set)是映射关系,其定义了字符和编码的关系。这里编码通常来说是1和0的bit。当前流行的计算机系统任何数据存储最终都表达为1和0。而这些1和0在不同字符集下映射成不同含义的字符。

计算机发明和使用初期,存储设备都十分的昂贵,科学家们想尽办法来节省成本,因此开始的是,最常见的字符集是单字节字符集(Signle-byte),所谓单字节字符集就是使用一个byte来代表一个字符,单字符集的典型是ASCII (American Standard Code for Information Interchange),你看这不是国籍标准,仅仅是美国标准,压根就没有考虑咱们感受,咱们从甲骨文开始发展汉字,美国人看到就A~Z几个字母。

这个ASCII 表,学过C语言的人都学过,以前考试也用到,需要背下来的。

ANSI(American National Standards Institute) 是在ASCII 7bit 编码标准 (ASA X3.4-1963)的基础上,加入了欧洲字母发展出来的一个标准。

但是单字节字符集有个最要命的缺点是一个byte只要8个bit,也就是最多表示256 (28)个可见和不可见的字符。 对于英语国家可能是够用的,但是对于说中文的国家,汉字是没办法通过256个字符表达的。因此慢慢出来国际标准的字符集Unicode。

Wince与Unicode

对于刚刚接触Windows Mobile和Wince(Windows Embedded CE) Native C++开发的人来说,会有这样的想法:Windows Mobile和Wince仅仅支持Unicode,不支持ANSI的。TinyXML是使用ANSI string的,但是Wince使用Unicode,那么TinyXML不能使用在Wince和Windows Mobile中。等等…… 其实这些想法有些错误,Wince是一个Unicode系统,没错,这表示Wince里面所有字符串处理代码都是基于Unicode编码,但是不表示Wince不支持ANSI。我们同样可以继续在Wince中使用ANSI,例如使用std::string,
char[]等。

但是为什么会有这些误区呢,先看一下下面的编译错误。

error C2664: 'wprintf' : cannot convert parameter 1 from 'const char [21]' to 'const
wchar_t *'

error C2664: 'DeleteFileW' : cannot convert parameter 1 from 'const char [21]' to 'LPCWSTR'

我敢保证刚刚接触Windows Mobile和Wince(Windows Embedded CE) Native C++开发的人10个有9个甚至10个都碰到过上述问题。在调用Win32 API的时候,使用MFC,WTL的接口的时候都会碰到这样的问题,因为我们习惯使用char*,std::string,但是恰恰Win32 API,MFC和WTL的函数入口中的字符串为Unicode,因此发生上述的编译错误。不知道为什么大家碰到这个错误后会形成一个错误的想法:Wince只是支持Unicode,不支持ANSI了。其实Wince还是支持ANSI的,我们定义单字符的char数组,甚至可以通过C
Runtime在Console中打印出ANSI string。

char ansiStr[] = "I am ANSI string";

printf(ansiStr);

Wince支持的字符串

既然Wince支持ANSI和Unicode,那什么时候用ANSI,什么时候用Unicode,下面我从在Wince开发中常常用到字符串讲起,然后讲述字符串的转换以及使用建议。

char*

char*和char[]没有本质的区别,都是指向内存的指针。ANSI版本的Win32的API中的字符串都是使用char*。 由于Win32的API是语言无关的,因此这些参数其实传递的是一段应该存放字符串的内存的指针。(很拗口,但是确实这样,呵呵)。在ANSI环境下使用纯粹C开发,程序是离不开char*的。

wchar_t *

LPWSTR, PWSTR等宏定义其实都是wchar_t*的定义, 最常见的LPCWSTR是const wchar_t*的宏定义。wchar_t表示16位Unicode字符。wchar_t*和wchar_t[]用来定义Unicode字符串。在Unicode版本下,所有Win32的API的字符串都由char*变成了wchar_t*了。

这个可以看一下头文件的预编译。例如以winbase.h的DeleteFile API为例。

WINBASEAPI

BOOL

WINAPI

DeleteFileA(

LPCSTR lpFileName

);

WINBASEAPI

BOOL

WINAPI

DeleteFileW(

LPCWSTR lpFileName

);

#ifdef UNICODE

#define DeleteFile DeleteFileW

#else

#define DeleteFile DeleteFileA

#endif // !UNICODE

在ANSI版本DeleteFile为DeleteFileA,参数的字符串定义为LPCSTR,也就是const char*,而Unicode版本的DeleteFile为DeleteFileW,参数字符串定义变成了LPCWSTR,也就是const wchar_t*。

决定DeleteFile到底是DeleteFileA或者DeleteFileW是由预编译宏 UNICODE 来决定的。

这个宏可以在项目属性里面配置,如下图:

当选择Use Unicode Character Set时候,预编译会增加宏UNICODE和_UNICODE。

但是需要注意的是,如果目标平台为Windows Mobile或者Wince,不管是否选择Use Unicode Character Set,UNICODE和_UNICODE的预编译都会加上,也就是说Wince下所有Win32 API都是Unicode版本的。

CString

CString最初在MFC里面封装,ATL和WTL分别封装了CString,这三个不同封装的CString具有语义兼容性,也就是他们提供的接口是相同的。使用CString的好处是可以同时兼容ANSI和Unicode,如下例子:

CString str = "Independent String";

m_wndPic.SetWindowText(str);

m_wndPic是一个CStatic控件,上面的代码不管在ANSI或者在Unicode都能使用,无需更改。

下面以ATL CString为例子讲述CString是如何同时支持支持ANSI或者Unicode的。

typedef CAtlString CString;typedef CStringT< TCHAR, StrTraitATL< TCHAR > > CAtlString;

CString存储字符串的类型由TCHAR来决定的,而TCHAR又是由UNICODE预编译来决定,见下面的宏定义。

#ifdef UNICODE // r_winnt

typedef WCHAR TCHAR, *PTCHAR;

#else /* UNICODE */ // r_winnt

typedef char TCHAR, *PTCHAR;

#endif /* UNICODE */

以此CString使用字符串类型根据预编译选项来自动决定。

std::string

STL里面的string,封装的是单字节字符,由于其跨平台的特性,我编写的代码中大量使用std::string,其实准确来说我大量使用STL。例如我一般使用std::string来操作TinyXML。抛开Wince平台不说,使用std::string基本上没有缺点,可以跨任何支持标准C++的平台。可是在Wince和Windows Mobile下做开发,情况有点不一样,因为std::string封装的是单字节字符,所以如果需要调用Win32的API,使用MFC,ATL和WTL的功能时都需要转型,这姑且算是一个缺点吧,但是熟悉了转换以后,使用std::string一点问题都没有。

std::wstring

STL里面的string的Unicode版本,和std::string一样,使用了unicode字符进行封装。其实std::wstring我用的不多,用std::string已经够了。

如何转换Wince支持的字符串

既然Windows Mobile和Wince(Windows Embedded CE)支持上述的字符串,那么我们开发的时候会碰到这些字符串直接相互转换的问题,下面通过例子演示如何转换。

转换过程我推荐使用ATL的宏,关于ATL的宏可以参考  ATL
and MFC String Conversion Macros

这些宏的命名规范为

CSourceType2[C]DestinationType[EX]

SourceType/DestinationType

Description

A

ANSI character string.

W

Unicode character string.

T

Generic character string (equivalent to W when _UNICODE is defined, equivalent to A otherwise).

OLE

OLE character string (equivalent to W).

A表示ANSI string,W表示Unicode string,T表示通用string,根据预编译来决定类型。OLE和W一样,我从来不用OLE。

例如CT2CA就是通用string转成ANSI string。

转换到char*

void ConvertToCharArray()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



CString cstr("ATL CString");



std::string stlStr("STL string");

std::wstring stlWStr(_T("STL wstring")); //use _T() convert const string to wchar_t string



printf("All string convert to char*\n");

strcpy(ansiStr, CT2CA(unicodeStr));

printf("Convert from wchar_t*, %s\n", ansiStr);



strcpy(ansiStr, CT2CA(cstr));

printf("Convert from CString, %s\n", ansiStr);



strcpy(ansiStr, stlStr.c_str());

printf("Convert from std::string, %s\n", ansiStr);



strcpy(ansiStr, CT2CA(stlWStr.c_str()));

printf("Convert from std::wstring, %s\n", ansiStr);

}

例子中用到了ATL CString,如果新建的是Win32项目需要加入ATL支持,方法可以参考: 在Windows
Mobile和Wince(Windows Embedded CE)下Win32项目加入ATL支持

上面讲过ATL CString和WTL以及MFC CString语义相同,因此本文所有CString的代码在MFC下同样有效。

转换到wchar_t*

void ConvertToWCharArray()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



CString cstr("ATL CString");



std::string stlStr("STL string");

std::wstring stlWStr(_T("STL wstring")); //use _T() convert const string to wchar_t string



printf("All string convert to wchar_t*\n");

wcscpy(unicodeStr, CComBSTR(ansiStr));

wprintf(_T("Convert from char*, %s\n"), unicodeStr);



wcscpy(unicodeStr, cstr);

wprintf(_T("Convert from CString, %s\n"), unicodeStr);



wcscpy(unicodeStr, CComBSTR(stlStr.c_str()));

wprintf(_T("Convert from std::string, %s\n"), unicodeStr);



wcscpy(unicodeStr, stlWStr.c_str());

wprintf(_T("Convert from std::wstring, %s\n"), unicodeStr);

}

这里使用了微软推荐的CComBSTR(),而不是CA2W()。

转换到CString

void ConvertToCString()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



CString cstr("ATL CString");



std::string stlStr("STL string");

std::wstring stlWStr(_T("STL wstring")); //use _T() convert const string to wchar_t string



printf("All string convert to CString\n");

cstr = ansiStr;

wprintf(_T("Convert from char*, %s\n"), cstr);



cstr = unicodeStr;

wprintf(_T("Convert from wchar_t*, %s\n"), cstr);



cstr = stlStr.c_str();

wprintf(_T("Convert from std::string, %s\n"), cstr);



cstr = stlWStr.c_str();

wprintf(_T("Convert from std::wstring, %s\n"), cstr);

}

转换到std::string

void ConvertToStlString()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



CString cstr("ATL CString");



std::string stlStr("STL string");

std::wstring stlWStr(_T("STL wstring")); //use _T() convert const string to wchar_t string



printf("All string convert to STL string\n");

stlStr = ansiStr;

printf("Convert from char*, %s\n", stlStr.c_str());



stlStr = CT2CA(unicodeStr);

printf("Convert from wchar_t*, %s\n", stlStr.c_str());



stlStr = CT2CA(cstr);

printf("Convert from CString, %s\n", stlStr.c_str());



stlStr = CT2CA(stlWStr.c_str());

printf("Convert from std::wstring, %s\n", stlStr.c_str());

}

转换到std::wstring

void ConvertToStlWstring()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



CString cstr("ATL CString");



std::string stlStr("STL string");

std::wstring stlWStr(_T("STL wstring")); //use _T() convert const string to wchar_t string



printf("All string convert to STL wstring\n");

stlWStr = CComBSTR(ansiStr);

wprintf(_T("Convert from char*, %s\n"), stlWStr.c_str());



stlWStr = unicodeStr;

wprintf(_T("Convert from wchar_t*, %s\n"), stlWStr.c_str());



stlWStr = cstr;

wprintf(_T("Convert from CString, %s\n"), stlWStr.c_str());



stlWStr = CComBSTR(stlStr.c_str());

wprintf(_T("Convert from std::string, %s\n"), stlWStr.c_str());

}

纯C Runtime库转换

有时候使用Win32进行纯C的开发,例如进行今日插件的开发,不使用ATL,WTL,MFC以及STL的情况下,也会有转换char*和wchar_t*的需求,但是不能使用ATL的宏,下面演示如何使用C Runtime库来转换。

void ConvertToWCharArrayUsingCRuntime()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



printf("Convert to char* from wchar_t* using C Runtime library.\n");

sprintf(ansiStr, "%S", unicodeStr);

printf("Convert from wchar_t*, %s\n", ansiStr);

}



void ConvertToCharArrayUsingCRuntime()

{

char ansiStr[255] = "ANSI string";

wchar_t unicodeStr[255] = _T("Unicode string"); //use _T() convert const string to
wchar_t string



printf("Convert to wchar_t* from char* using C Runtime library.\n");

swprintf(unicodeStr, _T("%S"), ansiStr);

wprintf(_T("Convert from char*, %s\n"), unicodeStr);

}

使用建议

上面讲述了Windows Mobile和Wince(Windows Embedded CE)支持那么多字符串,那么我们到底如何选择使用的字符串呢?其实这个没有准则,我下面谈一下我的经验。这不是准则,所以只做参考之用。

一.尽量避免使用char*和wchar_t*

除了以下情况,不得不使用char*和wchar_t*时,大部分时候尽量避免使用char*和wchar_t*。

情况1

做今日组件开发,只是使用Win32,如果不依赖于ATL,WTL,MFC和STL,那么没得选择只能使用char*和wchar_t*。

关于今日组件的开发,可以参考:

关于在Windows
Mobile下今日插件使用WTL的问题

情况2

封装DLL或者通用静态库提供给第三方使用,例如TinyXML, CppUnitLite这样的类库,他们内部都在char*基础上实现字符串处理类,这样库就不依赖于ATL,WTL,MFC和STL了。

关于TinyXML, CppUnitLite可以参考:

Windows
Mobile和Wince下使用TinyXML进行Native C++的开发

Wince和Windows
Mobile下native C++的单元测试

Windows
Mobile下使用CppUnitLite输出测试结果

情况3

封装DLL给.NET Compact Framework使用,接口函数只能使用char*和wchar_t*,不能使用CString和std::string。

关于DLL的封装,可以参考:

Windows
Mobile和Wince(Windows Embedded CE)下如何封装Native DLL提供给.NET Compact Framework进行调用

Windows
Mobile和Wince(Windows Embedded CE)下封装Native DLL进一步探讨

情况4

可以使用char*和wchar_t*包括一些字符串常量,用于替换宏定义。

除了上述情况以外,应当尽量避免使用char*和wchar_t*,而是用CString,std::string等封装好的字符串类。

二.程序需要同时支持PC和Window Mobile版本时使用CString

如果使用C++加上ATL,WTL或者MFC开发,程序需要同时支持Windows桌面版和Windows Mobile以及Wince,可以考虑使用CString。CString可以很好的兼容ANSI和Unicode版本。

例如我封装的一个SQL Server Compact的数据库访问类,使用到CString,这个类可以支持PC和Windows Mobile。可以参考:

Windows
Mobile下Native C++访问SqlCe的封装

三.程序需要跨平台时使用std::string

程序不仅仅用于windows平台,而且用于Linux,Unix,BSD等平台,可以考虑使用std::string,我一般不使用std::wstring,觉得没有这个必要,使用std::string在需要的时候转换就可以了。但是如果追求更高的跨平台性,那只能使用char*和wchar_t*了,连STL都不依赖。

我个人喜欢使用std::string,因为我大量使用STL。在设计的时候把界面和处理逻辑分开,处理逻辑内部统一使用std::string以及STL的容器。需要界面交互出来,或者调用Win32的时候进行字符串的转换。

可以进一步参考的文章

http://www.tenouk.com/ModuleG.html

http://www.codeproject.com/KB/string/cppstringguide1.aspx


Windows Mobile和Wince(Windows Embedded CE)的字符集问题的更多相关文章

  1. (转) 关于Windows CE和Windows Mobile

    转发自http://www.cnblogs.com/chump/articles/1281955.aspx 一.Windows CE Windows CE是微软的嵌入式操作系统主要的一种,面世于199 ...

  2. Windows Mobile 6开发环境搭建

    Windows Mobile 6开发环境搭建 本文主要介绍在已有的Visual Studio 2005和Microsoft SQL Server2008环境基础上,如何搭建Windows Mobile ...

  3. 使用Visual Studio 2008创建你的第一个Windows Mobile程序介绍

    使用Visual Studio 2008创建你的第一个Windows Mobile程序介绍 Windows MobileMobileWindowsMicrosoftWinForm 介绍 Microso ...

  4. Windows Embedded CE 6.0开发环境的搭建

    最近开始在学习嵌入式,在这里首先得安装Windows Embedded CE 6.0,其中遇到了很多问题,电脑的系统以及相关配置都会在安装过程中受到影响,因此笔者就安装中的问题以及环境搭建来介绍一下. ...

  5. (转) Windows Mobile和Windows CE的区别

    转发自 http://blog.sina.com.cn/s/blog_6250bbe60100tsf3.html WinCE Windows CE 是一个可定制的操作系统: Windows Mobil ...

  6. Windows Embedded CE 6.0开发环境的搭建(2)

    最近开始在学习嵌入式,在这里首先得安装Windows Embedded CE 6.0,其中遇到了很多问题,电脑的系统以及相关配置都会在安装过程中受到影响,因此笔者就安装中的问题以及环境搭建来介绍一下. ...

  7. Windows Embedded CE 6.0 下载地址和序列号

    Windows Embedded CE 6.0 下载地址和序列号 安装起来特麻烦 程序下载地址 http://download.microsoft.com/download/a/0/9/a09e587 ...

  8. Win10系统windows mobile设备中心无法连接WinCE采集器

    1.开始-->运行,输入services.msc回车 2.在打开的服务界面中,找到“基于Windows Mobile 2003的连接设备” 3.右击属性,修改成自动 4.点击登陆选项卡,选择本地 ...

  9. [WinCE] [Win10] Win10 Creator 升级后 Windows Mobile Device Center 不能打开

    运行 services.msc 找到 Windows Mobile 2003-based device connectivity服务,右键属性,Log On选项卡选择 Local System acc ...

随机推荐

  1. C实战:强大的程序调试工具GDB

    C实战:强大的程序调试工具GDB 1.基本调试 这里只列举最最常用的GDB命令. 1.1 启动GDB gdb program:准备调试程序.也可以直接进入gdb,再通过file命令加载. 1.2 添加 ...

  2. 【SSH系列】Hibernate映射 -- 一对多关联映射

        映射原理       一对多关联映射和多对一关联映射的映射原理是一样一样的,所以说嘛,知识都是相通的,一通百通,为什么说一对多关联映射和多对一关联映射是一样的呢?因为她们都是在多的一端加入一个 ...

  3. Unable to ignore resources

    摘要:分享牛,分享牛系列, Unable to ignore resources Attempted to beginRule: 异常信息处理. 出现Unable to ignore resource ...

  4. springMVC源码分析--AbstractUrlHandlerMapping(三)

    上一篇博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMapping了,接下来我们介绍其子类AbstractUrlHand ...

  5. ROS(indigo) 安装和使用更新版本的Gazebo----3,4,5,6,7 附:中国机器人大赛中型组仿真比赛说明

    ROS(indigo) 安装和使用更新版本的Gazebo,本文以7为例. Gazebo7支持更多新的功能,如果使用下面命令安装ROS(indigo): ~$ sudo apt-get install ...

  6. JVM概述

    JVM是什么 JVM全称是Java Virtual Machine(java虚拟机).它之所以被称之为是"虚拟"的,就是因为它仅仅是由一个规范来定义的抽象计算机.我们平时经常使用的 ...

  7. Spark技术内幕:Sort Based Shuffle实现解析

    在Spark 1.2.0中,Spark Core的一个重要的升级就是将默认的Hash Based Shuffle换成了Sort Based Shuffle,即spark.shuffle.manager ...

  8. Dynamics CRM2013 从外部系统取到CRM系统的用户头像

    CRM从2013开始引入了entityimage的概念,具体这个字段怎么设置的,图像是怎么上传的这里就不谈了.说实在的这玩意在项目中没啥用,所以也没去关注,直到最近遇到了个难题,要在外部系统去获取这个 ...

  9. 06 获取Activity的栈管理器

    代码 <span style="font-size:18px;">package com.fmy.day8_29task.util; import java.util. ...

  10. antlr v4 使用指南连载1——简介

    antlr v4简介        antlr是一个强大语言解析工具,可以用于处理结构化文本.二进制文件.说白了,其实可以这么认为,antlr是一个更强大的正则表达式工具.它可以完成更多正则表达式无法 ...