WinNing's profile无限&思想BlogListsGuestbook Tools Help

无限&思想

无限&思想
October 24

lockon操作键盘

飞行控制:
↑ :飞机拉起
↓ :飞机压低
← :向左翻滚
→ :向右翻滚
Z:左舵(空中),左转(地面)
X:右舵(空中),右转(地面)
A:自动驾驶
H:打开/取消高度固定
J:打开/取消速度固定
Shift+N:重置语音警报
K:解除仰角抑制(执行“眼镜蛇”机动)
U:在航母上进入起飞位置
Ctrl+. :向上配平
Ctrl+; :向下配平
Ctrl+, :向左配平
Ctrl+/ :向右配平
Ctrl+T:取消配平
Ctrl+Z:左舵配平
Ctrl+X:右舵配平

机械系统控制:
B:打开/关闭减速板
F:打开/关闭襟翼
G:打开/关闭起落架
E: 打开/关闭电子干扰
P:释放阻力伞
W:机轮刹车(保持键)
T:拉烟开关
R:受油管伸缩开关
L:仪表照明开关
Ctrl+L:机身信号灯开关
Alt+L:起落架灯--近光/远光/关闭
Ctrl+EEE:弹射跳伞
Ctrl+C:座舱盖开关
Ctrl+G:打开/关闭尾勾(仅限SU33)
Ctrl+P:机翼折叠开关(仅限SU33)
Ctrl+R:空中释放燃油/地面加油(保持键)
Alt+R:抛弃副油箱
Ctrl+H:改变HUD颜色

引擎控制:
Page up:发动引擎/快速增加推力
Page Down:关闭引擎/快速减小推力
With Alt:ALT组合单纯控制左引擎
With Shift:SHIFT组合单纯控制右引擎

导航模式:
~ :选择下一导航点或机场
1 :导航模式
Alt+C:重置飞行时钟

作战模式:
2 :超视距空战模式
3 :近距空战――垂直扫描模式
4 :近距空战――孔径模式
5 :近距空战――头盔模式
6 :径向瞄准模式
7 :空对地模式
8 :目视投弹模式
~ :目标循环切换
Tab:锁定所选目标或目标点(打开目标跟踪)
Ctrl+Tab:关闭目标跟踪

武器系统:
D:武器选择
C:机炮选择
Q:释放干扰弹(单发释放)
Shift+Q:释放干扰弹(连续释放)
Enter:发射导弹
Spacebar:发射机炮
Ctrl+W:成对抛弃武器(空中),重新装填武器(地面)
Ctrl+V:齐射模式开关
Shift+C:改变机炮射速(A10)
Shift+V:减小投弹间距(A10)
Ctrl+Space:改变投弹数量(A10)
Alt+Space:选择首选武器(A10)
Shift+Space:单个/成对投弹模式切换(A10)

雷达光电系统:
I:雷达开关
Alt+I:RWS/TWS(目标跟踪)雷达模式切换(仅限于F-15)
O:俄战斗机:打开/关闭光电扫描仪
     俄攻击机:打开/关闭激光测距仪
     美攻击机:打开/关闭CCRP投弹模式
Ctrl+I:所有扫描仪器归中
- :扩大MFD显示范围
+ :减小MFD显示范围
Ctrl++ :增大F15雷达天线方位角
Ctrl+- :减小F15雷达天线方位角

空对空模式:
Shift+; :向上移动雷达/光电扫描区
Shift+, :向左移动雷达/光电扫描区
Shift+. :向下移动雷达/光电扫描区
Shift+/ :向右移动雷达/光电扫描区

空对地模式:
Shift+; :雷达/电视制导探头上移
Shift+, :雷达/电视制导探头左移
Shift+. :雷达/电视制导探头下移
Shift+/ :雷达/电视制导探头右移

视角选择:
F1:座舱视角
F2:外部视角
F3:飞越视角
F4:后视视角
Ctrl+F4:追尾视角
F5:空战视角
F6:武器视角
F7:活动的地面物体视角
F8:目标视角
F9:舰船视角
F10:地图视角
F11:塔台及地表视角
F12:静态物体视角

座舱视角控制:
Key Pad 1:视角向左下移动
Key Pad 2:视角向下移动
Key Pad 3:视角向右下移动
Key Pad 4:视角向左移动
Key Pad 5:视角归中
Key Pad 6:视角向右移动
Key Pad 7:视角向左上移动
Key Pad 8:视角向上移动
Key Pad 9:视角向右上移动
Key Pad 0:仪表盘视角切换(保持键)
Key Pad 1+5:HUD视角
Key Pad * :向前移动视点
Key Pad / :向后移动视点
Key Pad Del:目视锁定
Key Pad NumLock:取消目视锁定
M:右后视镜视角
N:左后视镜视角

程序控制:
Esc:退出任务/录相
Ctrl+Q:在播放录相时控制飞机
Alt+J:取得/放弃所选飞机控制权
Ctrl+A:加快游戏速度(快镜头)
Alt+A:减慢游戏速度(慢镜头)
Shift+A:恢复正常游戏速度
S:任务开始/暂停/继续
‘ :单人任务时显示任务目标窗口/联网任务时显示成绩窗口
Ctrl+M:联网任务时打开对话窗口――向所有玩家发送信息
Alt+M:联网任务时打开对话窗口――向己方玩家发送信息

July 05

VC

VC字符串转换(附数据类型转换)

一、BSTR、LPSTR和LPWSTR
在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类 型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。

  那么什么是BSTR、LPSTR以及LPWSTR呢?

   BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的 API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种 场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。

   LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI 字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、 LPCTSTR等,它们的含义如图2所示。

  例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。

  一般地,还有下列类型定义:

#ifdef UNICODE
typedef LPWSTR LPTSTR;
typedef LPCWSTR LPCTSTR;
#else
typedef LPSTR LPTSTR;
typedef LPCSTR LPCTSTR;
#endif

二、CString、CStringA 和 CStringW

   Visual C++.NET中将CStringT作为ATL和MFC的共享的“一般”字符串类,它有CString、CStringA和CStringW三种形式,分 别操作不同字符类型的字符串。这些字符类型是TCHAR、char和wchar_t。TCHAR在Unicode平台中等同于WCHAR(16位 Unicode字符),在ANSI中等价于char。wchar_t通常定义为unsigned short。由于CString在MFC应用程序中经常用到,这里不再重复。

三、VARIANT、COleVariant 和_variant_t

  在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:

struct tagVARIANT {
VARTYPE vt;
union {
short iVal; // VT_I2.
long lVal; // VT_I4.
float fltVal; // VT_R4.
double dblVal; // VT_R8.
DATE date; // VT_DATE.
BSTR bstrVal; // VT_BSTR.

short * piVal; // VT_BYREF|VT_I2.
long * plVal; // VT_BYREF|VT_I4.
float * pfltVal; // VT_BYREF|VT_R4.
double * pdblVal; // VT_BYREF|VT_R8.
DATE * pdate; // VT_BYREF|VT_DATE.
BSTR * pbstrVal; // VT_BYREF|VT_BSTR.
};
};

  显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:

VARIANT va;
:: VariantInit(&va); // 初始化
int a = 2002;
va.vt = VT_I4; // 指明long数据类型
va.lVal = a; // 赋值

  为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:

  VariantInit —— 将变量初始化为VT_EMPTY;

  VariantClear —— 消除并初始化VARIANT;

  VariantChangeType —— 改变VARIANT的类型;

  VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。

   COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化, 然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被 自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与 VARIANT类型转换中为我们提供极大的方便。例如下面的代码:

COleVariant v1("This is a test"); // 直接构造
COleVariant v2 = "This is a test";
// 结果是VT_BSTR类型,值为"This is a test"
COleVariant v3((long)2002);
COleVariant v4 = (long)2002;
// 结果是VT_I4类型,值为2002

  _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:

  #include "comutil.h"

  #pragma comment( lib, "comsupp.lib" )

四、CComBSTR和_bstr_t

  CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:

CComBSTR bstr1;
bstr1 = "Bye"; // 直接赋值
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
CComBSTR bstr2(wcslen(str)); // 定义长度为5
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中
CComBSTR bstr3(5, OLESTR("Hello World"));
CComBSTR bstr4(5, "Hello World");
CComBSTR bstr5(OLESTR("Hey there"));
CComBSTR bstr6("Hey there");
CComBSTR bstr7(bstr6);
// 构造时复制,内容为"Hey there"

  _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。

五、BSTR、char*和CString转换

  (1) char*转换成CString

  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:

char chArray[] = "This is a test";
char * p = "This is a test";

  或

LPSTR p = "This is a test";

  或在已定义Unicode应的用程序中

TCHAR * p = _T("This is a test");

  或

LPTSTR p = _T("This is a test");
CString theString = chArray;
theString.Format(_T("%s"), chArray);
theString = p;

  (2) CString转换成char*

  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:

  方法一,使用强制转换。例如:

CString theString( "This is a test" );
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;

  方法二,使用strcpy。例如:

CString theString( "This is a test" );
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
_tcscpy(lpsz, theString);

  需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。

  方法三,使用CString::GetBuffer。例如:

CString s(_T("This is a test "));
LPTSTR p = s.GetBuffer();
// 在这里添加使用p的代码
if(p != NULL) *p = _T('\0');
s.ReleaseBuffer();
// 使用完后及时释放,以便能使用其它的CString成员函数

  (3) BSTR转换成char*

  方法一,使用ConvertBSTRToString。例如:

#include
#pragma comment(lib, "comsupp.lib")
int _tmain(int argc, _TCHAR* argv[]){
BSTR bstrText = ::SysAllocString(L"Test");
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
SysFreeString(bstrText); // 用完释放
delete[] lpszText2;
return 0;
}

  方法二,使用_bstr_t的赋值运算符重载。例如:

_bstr_t b = bstrText;
char* lpszText2 = b;

  (4) char*转换成BSTR

  方法一,使用SysAllocString等API函数。例如:

BSTR bstrText = ::SysAllocString(L"Test");
BSTR bstrText = ::SysAllocStringLen(L"Test",4);
BSTR bstrText = ::SysAllocStringByteLen("Test",4);

  方法二,使用COleVariant或_variant_t。例如:

//COleVariant strVar("This is a test");
_variant_t strVar("This is a test");
BSTR bstrText = strVar.bstrVal;

  方法三,使用_bstr_t,这是一种最简单的方法。例如:

BSTR bstrText = _bstr_t("This is a test");

  方法四,使用CComBSTR。例如:

BSTR bstrText = CComBSTR("This is a test");

  或

CComBSTR bstr("This is a test");
BSTR bstrText = bstr.m_str;

  方法五,使用ConvertStringToBSTR。例如:

char* lpszText = "Test";
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);

  (5) CString转换成BSTR

  通常是通过使用CStringT::AllocSysString来实现。例如:

CString str("This is a test");
BSTR bstrText = str.AllocSysString();

SysFreeString(bstrText); // 用完释放

  (6) BSTR转换成CString

  一般可按下列方法进行:

BSTR bstrText = ::SysAllocString(L"Test");
CStringA str;
str.Empty();
str = bstrText;

  或

CStringA str(bstrText);

  (7) ANSI、Unicode和宽字符之间的转换

  方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。

  方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:

TCHAR tstr[] = _T("this is a test");
wchar_t wszStr[] = L"This is a test";
String* str = S”This is a test”;

  方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:

   其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、 T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面 是一些示例代码:

LPTSTR tstr= CA2TEX<16>("this is a test");
LPCTSTR tcstr= CA2CT("this is a test");
wchar_t wszStr[] = L"This is a test";
char* chstr = CW2A(wszStr);

另附前文 数据类型转换以参考

我们先定义一些常见类型变量借以说明

int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串

短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);

二、从其它包含字符串的变量中获取指向该字符串的指针

CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);

三、字符串转换为其它数据类型
strcpy(temp,"123");

短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);

四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:

整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;

五、BSTR、_bstr_t与CComBSTR

CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);

六、VARIANT 、_variant_t 与 COleVariant

VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a; ///赋值

对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal;  VT_I4 
float fltVal;  VT_R4
double dblVal;  VT_R8 
VARIANT_BOOL boolVal;  VT_BOOL
SCODE scode;  VT_ERROR
CY cyVal;  VT_CY
DATE date;  VT_DATE
BSTR bstrVal;  VT_BSTR
IUnknown FAR* punkVal;  VT_UNKNOWN
IDispatch FAR* pdispVal;  VT_DISPATCH
SAFEARRAY FAR* parray;  VT_ARRAY|*
unsigned char FAR* pbVal;  VT_BYREF|VT_UI1
short FAR* piVal;  VT_BYREF|VT_I2
long FAR* plVal;  VT_BYREF|VT_I4
float FAR* pfltVal;  VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode;  VT_BYREF|VT_ERROR
CY FAR* pcyVal;  VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal;  VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal;  VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* F

 

 

 

VC字符串转换处理(ZZ)

1.头文件中要定义宏;

#define  UNICODE
#define  _UNICODE
////////////////////

char   buf[128]; 
memset(buf, 0, 128); 
strcpy(buf, "你好"); 
WCHAR  pCName[128]; 
memset(pCName, 0,1 28); 
MultiByteToWideChar(CP_THREAD_ACP, MB_USEGLYPHCHARS, buf, strlen(buf), pCName, 128); 至于WCHAR   转换到CHAR,则用 
WideCharToMultiByte

//////////////////////////

2.char转换成wchar

const  char  *pFilePathName  =  "c:\\aa.dll";
int  nLen  =  strlen(pFilePathName)  +  1;
int  nwLen  =  MultiByteToWideChar(CP_ACP,  0,  pFilePathName, nLen,  NULL,  0);

TCHAR  lpszFile[256];
MultiByteToWideChar(CP_ACP,  0,  pFilePathName,  nLen,  lpszFile, nwLen);
3.wchar转换成char

char  *pFilePathName;
TCHAR  lpszFile[256];
_tcscpy(lpszFile,  _T("c:\\aa.dll"));
int  nLen  =  wcslen(wstr)+1;
WideCharToMultiByte(CP_ACP,  0,  lpszFile,  nLen,  pFilePathName, 2*nLen,  NULL,  NULL); char*和CString转换
CString 是一种很特殊的 C++ 对象,它里面包含了三个值:一个指向某个数据缓冲区的指针、一个是该缓冲中有效的字符记数(它是不可存取的,是位于
CString 地址之下的一个隐藏区域)以及一个缓冲区长度。
有效字符数的大小可以是从0到该缓冲最大长度值减1之间的任何数(因为字符串结尾有一个NULL字符)。字符记数和缓冲区长度被巧妙隐藏。

(1) char*转换成CString
  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:

      char chArray[] = "Char  test";
      TCHAR * p = _T("Char  test");( 或?LPTSTR p = _T("Char  test");)
      CString theString = chArray;
      theString.Format(_T("%s"), chArray);
      theString = p;(2) CString转换成char*

  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:

  方法一,使用强制转换。例如:

       CString theString( (_T("Char test "));
       LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;  方法二,使用strcpy。例如:

       CString theString( (_T("Char test "));
       LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
       _tcscpy(lpsz, theString);       需要说明的是,strcpy(或可移值的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char*(ANSI),系统编译器将会自动对其进行转换。

  方法三,使用CString::GetBuffer。
           如果你需要修改 CString 中的内容,它有一个特殊的方法可以使用,那就是 GetBuffer,它的作用是返回一个可写的缓冲指针。
           如果你只是打算修改字符或者截短字符串,例如:

  CString s(_T("Char test "));
  LPTSTR p = s.GetBuffer();
  LPTSTR dot = strchr(p, ''.'');
           // 在这里添加使用p的代码

  if(p != NULL)
  *p = _T('\0');
  s.ReleaseBuffer(); // 使用完后及时释放,以便能使用其它的CString成员函数  在 GetBuffer 和 ReleaseBuffer 之间这个范围,一定不能使用你要操作的这个缓冲的 CString 对象的任何方法。因为ReleaseBuffer 被调用之前,该 CString 对象的完整性得不到保障。 CString ,BSTR ,LPCTSTR之间关系和区别

CString是一个动态TCHAR数组,BSTR是一种专有格式的字符串(需要用系统提供的函数来操纵,LPCTSTR只是一个常量的TCHAR指针。

CString 是一个完全独立的类,动态的TCHAR数组,封装了 + 等操作符和字符串操作方法。

typedef OLECHAR FAR* BSTR;
typedef const char * LPCTSTR;vc++中各种字符串的表示法
首先char*是指向ANSI字符数组的指针,其中每个字符占据8位(有效数据是除掉最高位的其他7位),这里保持了与传统的C,C++的兼容。
LP的含义是长指针(long pointer)。LPSTR是一个指向以‘\0’结尾的ANSI字符数组的指针,与char*可以互换使用,在win32中较多地使用LPSTR。
而LPCSTR中增加的‘C’的含义是“CONSTANT”(常量),表明这种数据类型的实例不能被使用它的API函数改变,除此之外,它与LPSTR是等同的。
1.LP表示长指针,在win16下有长指针(LP)和短指针(P)的区别,而在win32下是没有区别的,都是32位.所以这里的LP和P是等价的.
2.C表示const
3.T是什么东西呢,我们知道TCHAR在采用Unicode方式编译时是wchar_t,在普通时编译成char.

为了满足程序代码国际化的需要,业界推出了Unicode标准,它提供了一种简单和一致的表达字符串的方法,所有字符中的字节都是16位的值,其数量也可以满足差不多世界上所有书面语言字符的编码需求,开发程序时使用Unicode(类型为wchar_t)是一种被鼓励的做法。
LPWSTR与LPCWSTR由此产生,它们的含义类似于LPSTR与LPCSTR,只是字符数据是16位的wchar_t而不是char。
然后为了实现两种编码的通用,提出了TCHAR的定义:
如果定义_UNICODE,声明如下:
typedef wchar_t TCHAR;
如果没有定义_UNICODE,则声明如下:
typedef char TCHAR;
LPTSTR和LPCTSTR中的含义就是每个字符是这样的TCHAR。
CString类中的字符就是被声明为TCHAR类型的,它提供了一个封装好的类供用户方便地使用。

LPCTSTR:
#ifdef _UNICODE
typedef const wchar_t * LPCTSTR;
#else
typedef const char * LPCTSTR;
#endif
VC常用数据类型使用转换详解
先定义一些常见类型变量借以说明

int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="木瓜";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;
一、其它数据类型转换为字符串
短整型(int)
itoa(i,temp,10);
//将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2);  //按二进制方式转换
长整型(long)
ltoa(l,temp,10);

二、从其它包含字符串的变量中获取指向该字符串的指针
CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);
三、字符串转换为其它数据类型
strcpy(temp,"123");
短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);
四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:
整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;
五、BSTR、_bstr_t与CComBSTR
CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR
b=_com_util::ConvertStringToBSTR("数据");
//使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);

六、VARIANT 、_variant_t 与 COleVariant
VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:

VARIANT va;
int a=2001;
va.vt=VT_I4;  //指?明?整?型?数?据?
va.lVal=a;  //赋?值?
对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR*  pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:
unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal;  VT_I4
float fltVal;  VT_R4
double dblVal;  VT_R8
VARIANT_BOOL boolVal;  VT_BOOL
SCODE scode;  VT_ERROR
CY cyVal;  VT_CY
DATE date;  VT_DATE
BSTR bstrVal;  VT_BSTR
IUnknown FAR* punkVal;  VT_UNKNOWN
IDispatch FAR* pdispVal;  VT_DISPATCH
SAFEARRAY FAR* parray;  VT_ARRAY|*
unsigned char FAR* pbVal;  VT_BYREF|VT_UI1
short FAR* piVal;  VT_BYREF|VT_I2
long FAR* plVal;  VT_BYREF|VT_I4
float FAR* pfltVal;  VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode;  VT_BYREF|VT_ERROR
CY FAR* pcyVal;  VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal;  VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal;  VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal;
VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray;  VT_ARRAY|*
VARIANT FAR* pvarVal;  VT_BYREF|VT_VARIANT
void FAR* byref;  VT_BYREF

_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
int i=100;
_variant_t lVal(l);
lVal = (long)i;
COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;

七、其它
对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);  //取低8位
BYTE hiValue = HIBYTE(wValue);  //取高8位

如何将CString类型的变量赋给char*类型的变量
1、GetBuffer函数:
使用CString::GetBuffer函数。

char *p;
CString str="hello";
p=str.GetBuffer(str.GetLength());
str.ReleaseBuffer();将CString转换成char * 时

CString str("aaaaaaa");
strcpy(str.GetBuffer(10),"aa");
str.ReleaseBuffer();当我们需要字符数组时调用GetBuffer(int  n),其中n为我们需要的字符数组的长度.使用完成后一定要马上调用ReleaseBuffer();还有很重要的一点就是,在能使用const char *的地方,就不要使用char *2、memcpy:CString mCS=_T("cxl");
char mch[20];
memcpy(mch,mCS,20);3、用LPCTSTR强制转换: 尽量不使用char *ch;
CString str;
ch=(LPSTR)(LPCTSTR)str;
CString str = "good";
char *tmp;
sprintf(tmp,"%s",(LPTSTR)(LPCTSTR)str);
4、
CString Msg;
Msg=Msg+"abc";
LPTSTR lpsz;
lpsz = new TCHAR[Msg.GetLength()+1];
_tcscpy(lpsz, Msg);
char * psz;
strcpy(psz,lpsz);CString类向const char *转换
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
或者如下:
strncpy(a,str,sizeof(a));
以上两种用法都是正确地. 因为strncpy的第二个参数类型为const char *.所以编译器会自动将CString类转换成const char *.
CString转LPCTSTR (const char *)
CString cStr;
const char *lpctStr=(LPCTSTR)cStr;
LPCTSTR转CString
LPCTSTR lpctStr;
CString cStr=lpctStr;
将char*类型的变量赋给CString型的变量
可以直接赋值,如:
CString myString = "This is a test";
也可以利用构造函数,如:
CString s1("Tom");
将CString类型的变量赋给char []类型(字符串)的变量
1、sprintf()函数
CString str = "good";
char tmp[200] ;
sprintf(tmp, "%s",(LPCSTR)str);
(LPCSTR)str这种强制转换相当于(LPTSTR)(LPCTSTR)str
CString类的变量需要转换为(char*)的时,使用(LPTSTR)(LPCTSTR)str
然而,LPCTSTR是const char *,也就是说,得到的字符串是不可写的!将其强制转换成LPTSTR去掉const,是极为危险的!
一不留神就会完蛋!要得到char *,应该用GetBuffer()或GetBufferSetLength(),用完后再调用ReleaseBuffer()。
2、strcpy()函数
CString str;
char c[256];
strcpy(c, str);
char mychar[1024];
CString source="Hello";
strcpy((char*)&mychar,(LPCTSTR)source);

关于CString的使用
1、指定 CString 形参
对于大多数需要字符串参数的函数,最好将函数原型中的形参指定为一个指向字符 (LPCTSTR)
而非 CString 的 const 指针。
当将形参指定为指向字符的 const 指针时,可将指针传递到 TCHAR 数组(如字符串 ["hi
there"])或传递到 CString 对象。
CString 对象将自动转换成 LPCTSTR。任何能够使用 LPCTSTR 的地方也能够使用
CString 对象。
2、如果某个形参将不会被修改,则也将该参数指定为常数字符串引用(即 const CString&)。

如果函数要修改该字符串,则删除 const 修饰符。如果需要默认为空值,则将其初始化为空字符串 [""],如下所示:
void AddCustomer( const CString& name, const
CString& address, const CString& comment = "" );
3、对于大多数函数结果,按值返回 CString 对象即可。

串的基本运算
对于串的基本运算,很多高级语言均提供了相应的运算符或标准的库函数来实现。
为叙述方便,先定义几个相关的变量:
char s1[20]="dir/bin/appl", s2[20]="file.asm",s3[30],*p;
int result;

下面以C语言中串运算介绍串的基本运算
1、求串长
int strlen(char *s);  //求串s的长度
【例】printf("%d",strlen(s1));  //输出s1的串长12
2、串复制
char *strcpy(char
*to,*from);//将from串复制到to串中,并返回to开始处指针
【例】strcpy(s3,s1);  //s3="dir/bin/appl",s1串不变

3、联接
char *strcat(char *to,char
*from);//将from串复制到to串的末尾,
//并返回to串开始处的指针
【例】strcat(s3,"/");  //s3="dir/bin/appl/"
strcat(s3,s2);
//s3="dir/bin/appl/file.asm"
4、串比较
int strcmp(char *s1,char *s2);//比较s1和s2的大小,
//当s1<s2、s1>s2和s1=s2时,分别返回小于0、大于0和等于0的值
【例】result=strcmp("baker","Baker");
//result>0
result=strcmp("12","12");
//result=0
result=strcmp("Joe","joseph")
//result<0
5、字符定位
char *strchr(char *s,char c);//找c在字符串s中第一次出现的位置,
//若找到,则返回该位置,否则返回NULL
【例】p=strchr(s2,'.');  //p指向"file"之后的位置
   if(p) strcpy(p,".cpp");  //s2="file.cpp"
注意:
 ①上述操作是最基本的,其中后4个操作还有变种形式:strncpy,strncath和strnchr。
 ②其它的串操作见C的<string.h>。在不同的高级语言中,对串运算的种类及符号都不尽相同
 ③其余的串操作一般可由这些基本操作组合而成
【例】求子串的操作可如下实现:
void substr(char *sub,char *s,int pos,int
len){
//s和sub是字符数组,用sub返回串s的第pos个字符起长度为len的子串
//其中0<=pos<=strlen(s)-1,且数组sub至少可容纳len+1个字符。
if (pos<0||pos>strlen(s)-1||len<0)
     Error("parameter error!");
strncpy(sub,&s[pos],len);
//从s[pos]起复制至多len个字符到sub

CString 型转化成 int 型

  把 CString 类型的数据转化成整数类型最简单的方法就是使用标准的字符串到整数转换例程。
  虽然通常你怀疑使用 _atoi()函数是一个好的选择,它也很少会是一个正确的选择。如果你准备使用 Unicode  字符,你应该用_ttoi(),它在 ANSI 编码系统中被编译成_atoi(),而在 Unicode编码系统中编译成_wtoi()。你也可以考虑使用_tcstoul()或者_tcstol(),它们都能把字符串转化成任意进制的长整数(如二进制、八进制、十进制或十六进制),不同点在于前者转化后的数据是无符号的(unsigned),而后者相反。看下面的例子
CString hex = _T("FAB");

CString decimal = _T("4011");

ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));

CString格式化字符串

与其用 sprintf() 函数或 wsprintf() 函数来格式化一个字符串,还不如用 CString对象的Format()方法:
CString s;s.Format(_T("The total is %d"), total);  

用这种方法的好处是你不用担心用来存放格式化后数据的缓冲区是否足够大,这些工作由CString类替你完成。格式化是一种把其它不是字符串类型的数据转化为CString类型的最常用技巧,比如,把一个整数转化成CString类型,可用如下方法:
CString s;s.Format(_T("%d"), total); 

在EVC下,我将CString转为Char  *的时候可以这样写

#include  <atlconv.h>
... USES_CONVERSION;
CString  str="Cstring";
char  *p;
p=(char*)W2A((LPCTSTR)str); 

或者这样写

CString strDemo;

char AnsiString[MAX_PATH] = { 0 };
wcstombs(AnsiString, (LPTSTR)(LPCTSTR)strDemo, strDemo.GetLength());

或者这样写

char* = (LPSTR)(LPCTSTR)CString;
const char* = (LPCSTR)(LPCTSTR)CString;

或者这样写

CString strFilePath;
char FilePath[256];
memset( FilePath, 0, 256 );
strFilePath = "hello world";
WideCharToMultiByte( CP_ACP, 0, strFilePath, -1, FilePath, 256,
NULL, NULL);
...
或者这样写

首先获得char缓冲区大小,然后再通过WideCharToMultiByte转换
CString strConvert;
char pchBuffer;
int iSize;
iSize = WideCharToMultiByte(CP_ACP,0, strFilePath.GetBuffer(0), -1,
NULL, 0, NULL, NULL);
pchBuffer = new char[iSize*2 + 1];
if(pchBuffer != NULL)
{
      memset(pchBuffer , 0 , (iSize*2 + 1)*sizeof(char));
}
WideCharToMultiByte(CP_ACP,0, strFilePath.GetBuffer(0), -1,
pchBuffer, (iSize*2 + 1), NULL, NULL);

浅谈EVC中文字符串操作
EVC在某种意义上说,相当于VC的一个子集。因为大多EVC有的功能,VC也具备,而VC有的功能,EVC则不一定拥有。在VC中,操作字符串很方便,因为WINDOWS 的字处理能力实在是很强大,它支持多种字符集。我们随便使用一个CString str=“你好”,就要以输入我们想要的中文字符串。在EVC中这种情况有所改变,因为WINCE的字处理能力不够强大,它在处理汉字里统一将它示为 UNICODE编码,所以我们在EVC中片理中文字符串时需要用到UNICODE编码。下面结合WINDOWS 下VC字符串的处理,对比一下EVC中文字符串的片理方法。

一、中文字符串定义
1、在VC中我们如果定义一个中文字符串,可以使用CString str=“你好”或LPCTSTR  str=“你好”。
2、在EVC中我们如果想定义一个中文字符串,可以使用如下方法:CString str=_T(“你好”)或者LPCTSTR  str=“你好”,这里LPCTSTR在EVC里就是表示UNICODE字符串。值得注意的是_T()宏中,括号中只能填写常量,不能填定变量。
二、字符串操作
1、在VC中我们想拷贝字符串,可以作如下操作:
char s[20];
CString str=“你好”;
strcpy(s,str);
在EVC中则不能这样做,首先定义中文数组应该用双字节指针wchar_t,而拷贝函数也不能用strcpy,而应该用:wchar_t * wcscpy(wchar_t * wDest,wchar_t wSource);函数,操作如下:
wchar_t s[20];
CString str=“你好”;
wcscpy(s,(LPCTSTR)str); //前面没有转成UNICODE编码,所以这里需要强制转换
2、在VC中我们想在一个字符串中查找某个子串,只需要作下面的操作:
CString str=“你是一个好学生”;
str.Find(“学生”);
在EVC中不能这样做,因为中文字符串为UNICODE编码,我们必需在查找函数的参数里作如下修改:
str.Find(_T(“学生”));

以上是我在用EVC写应用程序时操作中文字符串的一些积累,以文记之,以备不时之需。

单宽字节互换的程序,估计以后还用得着。

void MyWideCharToMultiByte(WCHAR* wchars,CHAR* schars,int scharsLen)
{
    memset(schars,0,scharsLen);
    CString m_snd = wchars;
    int len = m_snd.GetLength();
    CString tmpstr(m_snd); //复制要发送的字符串
    int multibytelen=WideCharToMultiByte( //计算从Unicode转换到Ansi后需要的字节数
        CP_ACP, //根据ANSI code page转换
        WC_COMPOSITECHECK | WC_DEFAULTCHAR, //转换出错用缺省字符代替
        (LPCWSTR)tmpstr.GetBuffer(len), //要转换的字符串地址
        len, //要转换的个数
        0, //转换后字符串放置的地址
        0, //最多转换字符的个数,为0表示返回转换Unicode后需要多少个字节
        0, //缺省的字符:"\0"
        0 //缺省的设置
        );
    WideCharToMultiByte( //转换Unicode到Ansi
        CP_ACP,
        WC_COMPOSITECHECK | WC_DEFAULTCHAR,
        (LPCWSTR)tmpstr.GetBuffer(len),
        len,
        (char *)schars, //转换到缓冲区中
        scharsLen, //最多个字节
        0,
        0
        );
}
//程序接收到的字符串最后保存到CString tmpstr中.
//接收函数片断
void MyMultiByteToWideChar(char* schars,CString &wstr)
{
    // TODO: Add your specialized code here and/or call the base class
    //  char * p = "abcdefg我是谁hijk";
    int widecharlen=MultiByteToWideChar( //计算从Ansi转换到Unicode后需要的字节数
        CP_ACP,
        MB_COMPOSITE,
        (char*)schars, //要转换的Ansi字符串
        -1, //自动计算长度
        0,
        0
        );
    CString tmpstr;
    tmpstr.GetBuffer(widecharlen); //为转换后保存Unicode字符串分配内存
    MultiByteToWideChar( //从Ansi转换到Unicode字符
        CP_ACP,
        MB_COMPOSITE,
        (char*)schars,
        -1,
        (LPWSTR)tmpstr.GetBuffer(widecharlen), //转换到tmpstr
        widecharlen //最多转换widecharlen个Unicode字符
        );
    wstr = tmpstr;
}

void TestFunction()
{
    TCHAR abc[]=_T("ab我们的家ab");
    char b[15];
    MyWideCharToMultiByte(abc,b,sizeof(b));
    //  char c[]="ab如果cd就好了!abcdefg";
    CString str;
    MyMultiByteToWideChar(b,str);

    MyWideCharToMultiByte((LPWSTR)str.GetBuffer(0),b,sizeof(b));
}

 

 

 

 

 

C++字符串完全指引之一 —— Win32 字符编码

原著:Michael Dunn
翻译:Chengjie Sun

原文出处:CodeProject:The Complete Guide to C++ Strings, Part I
引言
  毫无疑问,我们都看到过像 TCHAR, std::string, BSTR 等各种各样的字符串类型,还有那些以 _tcs 开头的奇怪的宏。你也许正在盯着显示器发愁。本指引将总结引进各种字符类型的目的,展示一些简单的用法,并告诉您在必要时,如何实现各种字符串类型之间的转换。
  在第一部分,我们将介绍3种字符编码类型。了解各种编码模式的工作方式是很重要的事情。即使你已经知道一个字符串是一个字符数组,你也应该阅读本部分。一旦你了解了这些,你将对各种字符串类型之间的关系有一个清楚地了解。
  在第二部分,我们将单独讲述string类,怎样使用它及实现他们相互之间的转换。
字符基础 -- ASCII, DBCS, Unicode
  所有的 string 类都是以C-style字符串为基础的。C-style 字符串是字符数组。所以我们先介绍字符类型。这里有3种编码模式对应3种字符类型。第一种编码类型是单子节字符集(single-byte character set or SBCS)。在这种编码模式下,所有的字符都只用一个字节表示。ASCII是SBCS。一个字节表示的0用来标志SBCS字符串的结束。
  第二种编码模式是多字节字符集(multi-byte character set or MBCS)。一个MBCS编码包含一些一个字节长的字符,而另一些字符大于一个字节的长度。用在Windows里的MBCS包含两种字符类型,单字节字符(single-byte characters)和双字节字符(double-byte characters)。由于Windows里使用的多字节字符绝大部分是两个字节长,所以MBCS常被用DBCS代替。
  在DBCS编码模式中,一些特定的值被保留用来表明他们是双字节字符的一部分。例如,在Shift-JIS编码中(一个常用的日文编码模式),0x81-0x9f之间和 0xe0-oxfc之间的值表示"这是一个双字节字符,下一个子节是这个字符的一部分。"这样的值被称作"leading bytes",他们都大于0x7f。跟随在一个leading byte子节后面的字节被称作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一样,DBCS字符串的结束标志也是一个单字节表示的0。
  第三种编码模式是Unicode。Unicode是一种所有的字符都使用两个字节编码的编码模式。Unicode字符有时也被称作宽字符,因为它比单子节字符宽(使用了更多的存储空间)。注意,Unicode不能被看作MBCS。MBCS的独特之处在于它的字符使用不同长度的字节编码。Unicode字符串使用两个字节表示的0作为它的结束标志。
  单字节字符包含拉丁文字母表,accented characters及ASCII标准和DOS操作系统定义的图形字符。双字节字符被用来表示东亚及中东的语言。Unicode被用在COM及Windows NT操作系统内部。
  你一定已经很熟悉单字节字符。当你使用char时,你处理的是单字节字符。双字节字符也用char类型来进行操作(这是我们将会看到的关于双子节字符的很多奇怪的地方之一)。Unicode字符用wchar_t来表示。Unicode字符和字符串常量用前缀L来表示。例如:

wchar_t wch = L''1''; // 2 bytes, 0x0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters

字符在内存中是怎样存储的
  单字节字符串:每个字符占一个字节按顺序依次存储,最后以单字节表示的0结束。例如。"Bob"的存贮形式如下:

42
6F
62
00

B
o
b
BOS

Unicode的存储形式,L"Bob"

42 00
6F 00
62 00
00 00

B
o
b
BOS

使用两个字节表示的0来做结束标志。
  一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我们一会儿将看到 DBCS 字符串的微妙之处,它使得使用字符串操作函数和永字符指针遍历一个字符串时会产生预料之外的结果。字符串" " ("nihongo")在内存中的存储形式如下(LB和TB分别用来表示 leading byte 和 trail byte)

93 FA
96 7B
8C EA
00

LB TB
LB TB
LB TB
EOS




EOS

值得注意的是,"ni"的值不能被解释成WORD型值0xfa93,而应该看作两个值93和fa以这种顺序被作为"ni"的编码。
使用字符串处理函数
  我们都已经见过C语言中的字符串函数,strcpy(), sprintf(), atoll()等。这些字符串只应该用来处理单字节字符字符串。标准库也提供了仅适用于Unicode类型字符串的函数,比如wcscpy(), swprintf(), wtol()等。
  微软还在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函数都有对应名字的DBCS版本_mbs***()。如果你料到可能会遇到DBCS字符串(如果你的软件会被安装在使用DBCS编码的国家,如中国,日本等,你就可能会),你应该使用_mbs***()函数,因为他们也可以处理SBCS字符串。(一个DBCS字符串也可能含有单字节字符,这就是为什么_mbs***()函数也能处理SBCS字符串的原因)
  让我们来看一个典型的字符串来阐明为什么需要不同版本的字符串处理函数。我们还是使用前面的Unicode字符串 L"Bob":

42 00
6F 00
62 00
00 00

B
o
b
BOS

  因为x86CPU是little-endian,值0x0042在内存中的存储形式是42 00。你能看出如果这个字符串被传给strlen()函数会出现什么问题吗?它将先看到第一个字节42,然后是00,而00是字符串结束的标志,于是strlen()将会返回1。如果把"Bob"传给wcslen(),将会得出更坏的结果。wcslen()将会先看到0x6f42,然后是0x0062,然后一直读到你的缓冲区的末尾,直到发现00 00结束标志或者引起了GPF。
  到目前为止,我们已经讨论了str***()和wcs***()的用法及它们之间的区别。Str***()和_mbs**()之间的有区别区别呢?明白他们之间的区别,对于采用正确的方法来遍历DBCS字符串是很重要的。下面,我们将先介绍字符串的遍历,然后回到str***()与_mbs***()之间的区别这个问题上来。
正确的遍历和索引字符串
  因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。
  然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。

  • 1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte;
  • 2.永远不要使用-操作进行后向遍历。
  •   我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:\Program Files\MyCoolApp,那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时,你发现程序运行正常。
      现在,想象你合成文件名的代码可能是这样的:

    bool GetConfigFileName ( char* pszName, size_t nBuffSize )
    {
        char szConfigFilename[MAX_PATH];
     
        // Read install dir from registry... we''ll assume it succeeds.
     
        // Add on a backslash if it wasn''t present in the registry value.
        // First, get a pointer to the terminating zero.
        char* pLastChar = strchr ( szConfigFilename, ''\0'' );
     
        // Now move it back one character.
        pLastChar--;  
     
        if ( *pLastChar != ''\\'' )
            strcat ( szConfigFilename, "\\" );
     
        // Add on the name of the config file.
        strcat ( szConfigFilename, "config.bin" );
     
        // If the caller''s buffer is big enough, return the filename.
        if ( strlen ( szConfigFilename ) >= nBuffSize )
            return false;
        else
            {
            strcpy ( pszName, szConfigFilename );
            return true;
            }
    }      
      这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\。下面是这个名字在内存中的存储形式:

    43
    3A
    5C
    83 88
    83 45
    83 52
    83 5C
    00

    LB TB
    LB TB
    LB TB
    LB TB

    C
    :
    \




    EOS

      当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。
      哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。
      正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明)

    bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
    {
        char szConfigFilename[MAX_PATH];
     
        // Read install dir from registry... we''ll assume it succeeds.
     
        // Add on a backslash if it wasn''t present in the registry value.
        // First, get a pointer to the terminating zero.
        char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );
     
        // Now move it back one double-byte character.
        pLastChar = CharPrev ( szConfigFilename, pLastChar );
     
        if ( *pLastChar != ''\\'' )
            _mbscat ( szConfigFilename, "\\" );
     
        // Add on the name of the config file.
        _mbscat ( szConfigFilename, "config.bin" );
    
         // If the caller''s buffer is big enough, return the filename.
        if ( _mbslen ( szInstallDir ) >= nBuffSize )
            return false;
        else
            {
            _mbscpy ( pszName, szConfigFilename );
            return true;
            }
    }
    
      上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。
      让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。
    与规则2相关的关于字符串索引的规则:
    2a. 永远不要使用减法去得到一个字符串的索引。

    违背这条规则的代码和违背规则2的代码很相似。例如,

    char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

    这和向后移动一个指针是同样的效果。
    回到关于str***()和_mbs***()的区别
      现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。
      关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。
    Win32 API中的MBCS和Unicode
    两组 APIs:
      尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。
      当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如:

    BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
    BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
     
    #ifdef UNICODE
    #define SetWindowText  SetWindowTextW
    #else
    #define SetWindowText  SetWindowTextA
    #endif      
    当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:
    #define SetWindowText SetWindowTextA

      这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。)
      所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:

    HWND hwnd = GetSomeWindowHandle();
    char szNewText[] = "we love Bob!";
    SetWindowText ( hwnd, szNewText );

    在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:

    HWND hwnd = GetSomeWindowHandle();
    char szNewText[] = "we love Bob!";
    SetWindowTextW ( hwnd, szNewText );

      看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:

    HWND hwnd = GetSomeWindowHandle();
    #ifdef UNICODE
    wchar_t szNewText[] = L"we love Bob!";
    #else
    char szNewText[] = "we love Bob!";
    #endif
    SetWindowText ( hwnd, szNewText );

    你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR.
    使用TCHAR
      TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:

    #ifdef UNICODE
    typedef wchar_t TCHAR;
    #else
    typedef char TCHAR;
    #endif

    所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。

    #ifdef UNICODE
    #define _T(x) L##x
    #else
    #define _T(x) x
    #endif

      ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。

    TCHAR szNewText[] = _T("we love Bob!");

      像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。
      不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。
    字符串和TCHAR typedefs
      由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。

    type
    Meaning in MBCS builds
    Meaning in Unicode builds

    WCHAR
    wchar_t
    wchar_t

    LPSTR
    zero-terminated string of char (char*)
    zero-terminated string of char (char*)

    LPCSTR
    constant zero-terminated string of char (const char*)
    constant zero-terminated string of char (const char*)

    LPWSTR
    zero-terminated Unicode string (wchar_t*)
    zero-terminated Unicode string (wchar_t*)

    LPCWSTR
    constant zero-terminated Unicode string (const wchar_t*)
    constant zero-terminated Unicode string (const wchar_t*)

    TCHAR
    char
    wchar_t

    LPTSTR
    zero-terminated string of TCHAR (TCHAR*)
    zero-terminated string of TCHAR (TCHAR*)

    LPCTSTR
    constant zero-terminated string of TCHAR (const TCHAR*)
    constant zero-terminated string of TCHAR (const TCHAR*)

    何时使用 TCHAR 和 Unicode
      到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:

  • 1.你的程序只运行在Windows NT系统中。
  • 2. 你的程序需要处理超过MAX_PATH个字符长的文件名。
  • 3. 你的程序需要使用XP中引入的只有Unicode版本的API.
  •   Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。
      只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。
      最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。
      即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。

    作者简介
      Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 
      Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。
      他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。

     

    WinNing Zero

    Occupation
    感谢访问!
    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.
    WinNing Zerowrote:
    欢迎发表评论,给我留言! 
    Aug. 1