| | 网站首页 | 新闻 | SOPC | FPGA | DSP | ARM | 嵌入式操作系统 | 下载 | 所有产品 | 留言 | 论坛 | 购买指南 | 网络协议 | 驱动设计 | | |
![]() |
![]() |
| 您现在的位置: 21嵌入式控制研究室 >> 嵌入式操作系统 >> Windows CE >> 文章正文 |
|
|||||
| Microsoft Windows CE 编程的十点忠告 | |||||
| 作者:佚名 文章来源:21control 点击数: 更新时间:2005-12-26 | |||||
|
一般说来,这个计划不是太难。我们起步于Microsoft Win32代码,当然 Windows CE是 基于Win32应用程序接口(API)的。有利的是,我们的应用程序(即Raima 数据管理器 )有方便的使用接口,并包含一个大约由150个子函数组成的库,这些函数都是由C语言 写成,可以用来创建、管理和访问数据库。 按建立应用程序的方式来说,我们原以为将它移植到Windows CE中是一项相对简单 的C语言编程练习。然而,我们不久便遇到好些困难。从粗心大意的错误开始,比如在基 于Windows NT 的Windows CE仿真器上使用Microsoft Windows NT库,接着又违背Windo ws CE的编程戒律,如"千万不要给Unicode(国际标准组织10646标准)字符分配奇数内 存地址"。 大约有百分之九十的问题或多或少地与Unicode有关。尽管Unicode编程不难,但是 ,当给单字节字符编写代码时,很容易出错(我有过许多次错误)。 下面这些忠告是根据我们在Windows CE上编写Raima 数据管理器的经验总结出来的 ,但我相信,在做任何其它Windows CE程序之前,它们都值得借鉴。毕竟大多数Window s开发者,当他们创建第一个Windows CE应用程序时,真正运用的是已掌握的Win32知识 。 1. 不要在仿真器上使用Windows NT库 这里所讨论的第一个错误实在太愚蠢了,但我还是陷了进去,也许你也会。当用Mi crosoft VC++(5.0版)创建一个Windows CE程序时,你会发现,包含路径(include) 、 库路径(library)、及可执行程序路径被自动调整以匹配反应目标环境的选择。因 此,比如说为Windows CE模拟器建立应用程序时,你会发现,include路径没有指向Win 32的包含文件(在VC目录下),而是指向Windows CE包含文件(在WCE目录下)。千万别 去修改。 由于Windows CE在Windows NT下运行,所以仿真器上运行的程序能够调用任一Wind ows NT动态链接库(DLL)中的函数,即使这个DLL不是模拟器的成员也一样。显然,这不 是很好的事,因为相同的函数也许在手持PC(H/PC)或Windows CE设备上不可用,而你的 软件最终要能在这些设备上运行。 第一次将非Unicode应用程序装入Windows CE仿真器时,你会发现,许多正在使用的 函数它都不支持,例如美国国家标准协会(ANSI)定义的字符函数strcpy()。这也许引诱 你去链接Windows NT 运行时间库,以便能解决所有问题。 如果你是刚开始用Windows CE编程,可能你能用的包含文件和库文件是明显的。答 案就是,你不要采用那些在写普通Win32或非Windows CE程序时使用的包含文件和库文件 。 2. 不要混淆TCHARs和bytes 如果你正在Windows CE上写非Unicode应用程序,你或许要将所有的字符串从单个字 符(chars)转换为宽字符(widechars)(例如,C变量类型whcar_t)。几乎所有Windows CE支持的Win32和运行时间库函数都要求宽字符变量。Windows 95不支持Unicode,然而 ,为了使程序代码具有可移植性,你要尽可能采用tchar.h中定义的TCHAR类型,不要直 接使用wchar_t。 TCHAR是定义为wchar_t还是char,取决于预处理器的符号UNICODE是否定义。同样, 所有有关字符串处理函数的宏,如_tcsncpy宏,它是定义为Unicode函数wcsncpy还是定 义为ANSI函数strncpy,取决于UNICODE是否定义。 在现存的Windows应用程序中,有些代码也许暗示字符长为单字节。这在给字符串分 配内存时经常用到,例如: int myfunc(char *p) { char *pszFileName; pszFileName = malloc(MAXFILELEN); if(pszFileName) strncpy(pszFileName, p, MAXFILELEN); /*etc*/ 在这段代码中,分配的内存块应该写作(MAXFILELEN * sizeof(char)),但是大多数 程序员喜欢将它简化为MAXFILELEN,因为对于所有的平台来说sizeof(char)的值等于1。 然而,当你用TCHARS代替多个字符时,很容易忘记这种固有的概念,于是将代码编写成 下面的形式: int myfunc(TCHAR *p) { TCHAR *pszFileName; PszFileName = (TCHAR*)malloc(MAXFILELEN); If (pszFileName) tcsncpy(pszFileName, p, MAXFILELEN); /*etc*/ 这是不行的。它马上会导致出错。这里的错误在于malloc函数中指定变量大小为by tes,然而_tcsncpy函数中使用的第三个变量却指定为TCHARs而不是bytes。当UNICODE被 定义时,一个TCHAR等于两个字节数(bytes)。 上述代码段应该改写为: int myfunc(TCHAR *p) { TCHAR *pszFileName; PszFileName = (TCHAR*)malloc(MAXFILELEN * sizeof(TCHAR)); if(pszFileName) tcsncpy(pszFileName, p, MAXFILELEN); /*etc*/ 3. 不要将Unicode 字符串放入奇数内存地址 在Intel系列处理器上,你可以在一奇数内存地址储存任何变量或数组,不会导致任 何致命的错误影响。但在H/PC上,这一点不一定能行 ? 你必须对大于一个字节的数据类 型小心谨慎,包括定义为无符号短型(unsigned short) 的wchar_t。当你设法访问它 们的时候,将它们置于奇地址会导致溢出。 编辑器经常在这些问题上提醒你。你无法管理堆栈变量地址,并且编辑器会检查确 定这些地址与变量类型是否相匹配。同样,运行时间库必须保证从堆中分配的内存总是 满足一个word边界 ,所以你一般不必担心那两点。但是,如果应用程序含有用memcpy( )函数拷贝内存区域的代码,或者使用了某种类型的指针算术以确定内存地址,问题也许 就出现了。考虑下面的例子: int send_name (TCHAR * pszName) { char *p, *q; int nLen=(_tcslen(pszName) + 1) * sizeof(TCHAR); p=maloc(HEADER_SIZE + nLen); if(p) { q = p + HEADER_SIZE; _tcscpy((TCHAR*)q, pszName); } /* etc */ 这段代码是从堆中分配内存并复制一个字符串,在字符串的开头留一个HEADER_SIZ E的大小。假设UNICODE定义了,那么该字符串就是一个widechar字符串。如果HEADER_S IZE是一个偶数,这段代码就会正常工作,但如果HEADER_SIZE为奇数,这段代码就会出 错,因为q指向的地址也将为奇数。 注意,当你在Intel系列处理器中的Windows CE仿真器上测试这段代码时,这个问题 是不会发生的。 在这个例子中,只要确保HEADER_SIZE为偶数,你就可以避免问题的发生。然而,在 某些情况下你也许不能这么做。例如,如果程序是从一台式PC输入数据,你也许不得不 采用事先定义过的二进制格式,尽管它对H/PC不适合。在这种情况下,你必须采用函数 ,这些函数用字符指针控制字符串而不是TCHAR指针。如果你知道字符串的长度,就可以 用memcpy()复制字符串。因此,采用逐个字节分析Unicode字符串的函数也许足以确定字 符串在widechars中的长度。 4. 在ANSI和Unicode字符串之间进行翻译 如果你的Windows CE应用程序接口于台式PC,也许你必须操作PC机中的ANSI字符串 数据(例如,char字符串)。即使你在程序中只用到Unicode字符串,这都是事实。 你不能在Windows CE上处理一个ANSI字符串,因为没有操纵它们的库函数。最好的 解决办法是将ANSI字符串转换成Unicode字符串用到H/PC上,然后再将Unicode字符串转 换回ANSI字符串用到PC上。为了完成这些转换,可采用MultiByteToWideChar()和WideC harToMultiByte () Win32 API 函数。 5. 对于Windows CE 1.0的字符串转换,劈开(hack) 在Windows CE 1.0 版本中,这些Win32API函数还没有完成。所以如果你想既要支持 CE 1.0又能支持CE 2.0,就必须采用其它函数。将ANSI字符串转换成Unicode字符串可以 用wsprintf(),其中第一个参数采用一widechar字符串,并且认识"%S"(大写),意思是 一个字符串。由于没有wsscanf() 和 wsprintfA(),你必须想别的办法将Unicode字符串 转换回ANSI字符串。由于Windows CE 1.0不在国家语言支持(NLS)中,你也许得求助于h ack,如下所示: /* Definition / prototypes of conversion functions Multi-Byte (ANSI) to WideChar (Unicode) atow() converts from ANSI to widechar wtoa() converts from widechar to ANSI */ #if ( _WIN32_WCE >= 101) #define atow(strA, strW, lenW) \ MultiByteToWidechar (CP_ACP, 0, strA, -1, strW, lenW) #define wtoa(strW, strA, lenA) \ WideCharToMutiByte (CP_ACP, 0, strW, -1, strA, lenA, NULL, NULL) #else /* _WIN32_WCE >= 101)*/ /* MultiByteToWideChar () and WideCharToMultiByte() not supported on Windows CE 1.0 */ int atow(char *strA, wchar_t *strW, int lenW); int wtoa(wchar_t *strW, char *strA, int lenA); endif /* _WIN32_WCE >= 101*/ #if (_WIN32_WCE <101) int atow(char *strA, wchar_t *strW, int lenW) { int len; char *pA; wchar_t *pW; /* Start with len=1, not len=0, as string length returned must include null terminator, as in MultiByteToWideChar() */ for(pA=strA, pW=strW, len=1; lenW; pA++, pW++, lenW--, len++) { *pW = (lenW = =1) ? 0 : (wchar_t)( *pA); if( ! (*pW)) break; } return len; } int wtoa(wxhar_t *strW, char *strA, int lenA) { int len; char *pA; wchar_t *pW; /* Start with len=1,not len=0, as string length returned Must include null terminator, as in WideCharToMultiByte() */ for(pA=strA, pW=strW, len=1; lenA; pa++, pW++, lenA--, len++) { pA = (len==1)? 0 : (char)(pW); if(!(*pA)) break; } return len; } #endif /*_WIN32_WCE<101*/ 这种适合于Windows CE 1.0的实现办法比使用wsprintf()函数要容易,因为使用ws printf()函数更难以限制目标指针所指向的字符串的长度。 6. 选择正确的字符串比较函数 如果你要分类Unicode标准字符串,你会有以下几个函数可供选择: wcscmp(), wcsncmp(), wcsicmp(), 和wcsnicmp() wcscoll(), wcsncoll(), wcsicoll(),和wcsnicoll() CompareString() 第一类函数可用来对字符串进行比较,不参考当地(Locale)或外文字符。如果你 永远不想支持外文,或者你仅仅想测试一下两个字符串的内容是否相同,这类函数非常 好用。 第二类函数使用现有的当地设置(current locale settings)(系统设置,除非你在 字符串比较函数之前调用了wsetlocale()函数)来比较两个字符串。这些函数也能正确 分类外文字符。如果当地的字符"C"("C" locale)被选定,这些函数与第一类函数就具 有了相同的功能。 第三类函数是Win32函数CompareString()。这个函数类似于第二类函数,但是它允 许你指定当地设置(the locale)作为一个参数,而不是使用现有的当地设置(current locale settings)。CompareString()函数允许你选择性地指定两个字符串的长度。你 可以将第二个参数设置为NORM_IGNORECASE,从而使函数比较字符串时不比较大小写。 通常,即使不将第二个参数设置为NORM_IGNORECASE,CompareString()函数也不用 来区分大小写。我们经常用wcsncoll()函数来区分大小写,除非使用当地的字符"C"(" C" locale)。所以,在我们的代码中,不使用CompareString()函数来区分大小写,而 用wcsncoll()函数来区分大小写 7. 不要使用相对路径 与Windows NT不一样,Windows CE没有当前目录这个概念,因此,任何路径只是相 对于根目录而言的。如果你的软件给文件或目录使用相对路径,那么你很可能把它们移 到别的地方了。例如,路径".\abc"在Windows CE中被当作"\abc"看待。 8.移走了对calloc()和 time()函数的调用 C运行库中的calloc()函数不能使用,但是malloc()函数可以代替calloc()函数。并 且不要忘记,calloc()函数初始化时分配的内存为零,而malloc()函数不一样。同样, time()函数也不能使用,但你可以使用Win32函数GetSystemTime()函数代替time()函数 。 经过以上的警告后,你会高兴地学习最后令你惊讶的两点忠告。 9. 不需要改变Win32 输入/输出(I/O)文件的调用 Win32的输入输出函数,Windows CE也支持。允许你象访问Win32文件系统那样访问 对象。CreateFile()函数在Windows CE中不能辩认标志FILE_FLAG_RANDOM_ACCESS,但是 这个标志仅用作可选的磁盘访问,并且不影响函数调用的功能。 10. 不要担心字节的状态 当我们把应用程序写入Windows CE时,有了一个美好的发现,那就是Windows CE的 数字数据类型的字节状态与Intel结构的字节状态一样,在所有的处理器上,Windows C E均支持。 几乎象所有的数据库引擎一样,Raima数据库管理器在数据库文件中以二进制形式保 存数字数据。这就意味一个记录无论何时写入数据库或从数据库读出,均被当作一系列 的字节来处理,不管它域的内容。只要数据库文件不要传给别的任何系统,数字数据的 字节状态问题就解决了。如果数据库文件被一个来自原始系统且带有不同字节状态的处 理器访问,数字数据将被误解。 无论何时,当你在拥有不同处理器的机器上传输文件时,就会出现这个问题。在这 个问题上,值得高兴的是所有类型的处理器都使用相同的字节状态。 在使用Windows CE时,这10点忠告应该引起你足够的重视,避免学习时走弯路。 |
|||||
| 文章录入:fengfeiyi 责任编辑:fengfeiyi | |||||
| 【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 | |||||
| 最新热点 | 最新推荐 | 相关文章 | ||
网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!) |
| | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 管理登录 | | |
![]() |
站长:康草科技 |