南山狒狒

我梦江南好

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  9 随笔 :: 0 文章 :: 0 评论 :: 0 Trackbacks

2008年5月19日 #

近日在开发过程中遇到WinCE应用程序开机自动运行的问题,在网上找了找,发现大概有以下三种方法:
1、将应用程序和应用程序快捷方式添加到映像里,再将快捷方式添加到StartUp目录下,这样当系统运行后应用程序就能自动运行;

2、直接替换Wince的SHELL,即修改注册表:
[HKEY_LOCAL_MACHINE\init]
"Launch50"="explorer.exe"
"Depend50"=hex:14,00, 1e,00
把这个explorer.exe改成你的应用程序(比如:MyApp.exe);

3、把应用程序加入到映像,修改注册表:
[HKEY_LOCAL_MACHINE\init]
"Launch80"="MyApp.exe"
"Depend80"=hex:1E,00
可以设置启动顺序和依赖程序;
以上方法都可行,但是都存在一个问题,就是应用程序是集成到NK里面的,也就是说每次升级应用程序都要重新编译下载内核,很麻烦,尤其在程序调试阶段,大家都希望把应用程序放在SD卡上,这样更新起来比较容易;据说通过第三种方法可以实现,即修改"Launch80"="MyApp.exe"为"Launch80"="\STDCard\MyApp.exe"( STDCard为SD卡目录),但是我试了一下没有成功,因为Launch80运行时SD卡的文件驱动还没有加载,找不到MyApp.exe文件。同样,采用快捷方式加载SD卡里的应用程序也不可行。
所以我采用了另一种方法,自己编了一个小程序,比如叫ShellExe.exe,将此程序加入到映像里,通过StartUp快捷方式调用ShellExe,ShellExe再去调用SD卡里的应用程序,具体实现步骤如下:
1、 在eVC下编译如下代码:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{    
 WIN32_FIND_DATA fd;
 HANDLE hd=INVALID_HANDLE_VALUE;  

 int iCount = 20;
    while(iCount--)      
    {
  hd=::FindFirstFile(lpCmdLine,&fd);  
  Sleep(500);  
  
  if(INVALID_HANDLE_VALUE!=hd)
   break;      
 }   
 
 if(0==iCount)
  return 0;  

 FindClose(hd);  

 SHELLEXECUTEINFO ShExeInfo={0};      
 ShExeInfo.cbSize=sizeof(SHELLEXECUTEINFO);      
 ShExeInfo.fMask=SEE_MASK_NOCLOSEPROCESS;      
 ShExeInfo.hwnd=NULL;      
 ShExeInfo.lpVerb=NULL;     
 ShExeInfo.lpFile=lpCmdLine;      
 ShExeInfo.lpParameters=L"";    
 ShExeInfo.lpDirectory=NULL;     
 ShExeInfo.nShow=SW_SHOW;      
 ShExeInfo.hInstApp=NULL;   

 ShellExecuteEx(&ShExeInfo);
 return 0;
}
  生成ShellExe.exe的可执行文件,此段代码主要功能是查找指定的应用程序,然后执行;下面这段代码可以保证在SD卡文件系统正确加载后才去执行应用程序; 

    while(iCount--)      
    {
  hd=::FindFirstFile(lpCmdLine,&fd);  
  Sleep(500);  
  
  if(INVALID_HANDLE_VALUE!=hd)
   break;      
 }  

文件的名称和路径由命令行参数指定:ShExeInfo.lpFile=lpCmdLine;

2、 新建一个快捷方式,如Autorun.lnk,按如下方式编辑其内容:
21#\windows\shellexe.exe \stdcard\MyApp.exe其中\stdcard\MyApp.exe应用程序的绝对路径;

3、 将MyApp.exe和Autorun.lnk添加到NK里,方法是在project.bib文件内加入如下内容:
ShellExe.exe f:\WINCE420\PBWORKSPACES\LioetEnTer\RelDir\ShellExe.exe NK SAutorun.lnk f:\WINCE420\PBWORKSPACES\LioetEnTer\RelDir\Autorun.lnk NK S注意:ShellExe.exe的属性不能带H(隐藏).

4、 在project.dat里加入如下内容:
Directory("\Windows\Startup"):-File("Autorun.lnk","\Windows\Autorun.lnk")5、 选择Make Image生成映像(当然Build也可以,就是慢点儿),烧到FLASH里,开机运行,可以看到SD卡里的MyApp.exe被正确执行。
总结
  这种方法用起来比较方便,ShellExe.exe不用每次都重新编译,只要根据应用程序路径修改Autorun.lnk即可,可以加载Flash、U盘、SD卡里的应用程序。调试及升级应用程序就不用重新烧写内核了。

posted @ 2008-05-19 19:12 iJrong 阅读(24) | 评论 (0)编辑 收藏

2008年5月6日 #

一、关于企业计算方向

  企业计算(Enterprise Computing)是稍时髦较好听的名词,主要是指企业信息系统,如ERP软件(企业资源规划)、CRM软件(客户关系管理)、SCM软件(供应链管 理,即物流软件),银行证券软件,财务软件,电子商务/政务(包括各种网站),数据仓库,数据挖掘,商务智能等企业信息管理系统.

  企业计算领域对人才的需求显然永远是数量最大的,因为这是计算机应用最多的领域. 搞这方面的好处是:

  (1)人才需求量极大,从事企业计算的公司在IT企业中占了大多数。除非在专业上一无特长,一般在这一领域总能找到工作。

  (2)这方面的入门门槛相对较低(如果你的软件功底不是很深,可考虑这一领域)

  (3)这方面的大公司较多,大公司要赚大钱,所以多将精力花在企业计算业务上.如与正规高校软件学院同学目前实习的CitiCorp、HP、IBM、SAP、NEC等公司都属这一领域的公司。如果将来想到大公司找一份相对稳定的工作,从事这方面机会要大很多。

  但从事这一领域的缺点也是明显的:

  由于这方面的入门门槛相对较低,虽然这方面的人才需求量是最大的,但将来竞争对手会较多。您会发现,即使他原不是学IT专业的人,也许他突击几 个月后,做得照样像模像样。特别是当您年纪渐大后,您可能会发现,后面的年轻人可能很容易追上你的水平。如果您将来到国外去工作,你可能会发现从事这领域 的人更多且高手如云。当然,若您在这一领域经过多年企业经验,达到较高境界(如能设计软件架构),则身价永远是高的。国内在这方面人才领域的主要问题是, 有经验的高手太少,皮毛了解的人太多。

  从事企业计算领域,最重要的技能型技术课程是(1)J2EE架构与程序设计(2)大型数据库系统(如Oracle)(3)基于UML的系统分析 与设计。如果说还有什么重要的技能,还可将XML与Web Service技术包含进来,若您在这几个领域掌握较好,则不愁找不到工作。其中尤其以J2EE最为重要,目前J2EE已成为企业计算软件开发的最主要平 台,也是正规高校软件学院的最重要课程之一。尽管该课程只能作为选修课,我们希望正规高校软件学院同学无论将来想从事何种方向,都应学一下J2EE课程, 至少可为将来找工作备一手关键功夫。包括想从事嵌入式或其它领域的同学,也是很有必要学一下J2EE的,毕竟J2EE是目前最重要的平台之一,即使您将来 不想从事企业计算领域,了解一下J2EE也是必要的,就像一门常识课程一样。其它与企业计算关系较密切的技能还包括:Dot Net架构与程序设计、软件测试技术、软件配置管理,该领域较高层次的技能包括数据仓库技术、构件与中间件技术、设计模式等。像通信协议分析与网络程序设 计,Unix系统管理等也属有些关系的课程。02级本学期开设的企业计算课程不多,主要是J2EE、Oracle/MSSQL、UML等企业计算领域的最 关键技能型技术课程都已学完了(但不知有多少同学学得较深入,将来找工作时会用到这些技能)。下学期我们将在该领域中拟再开设XML与Web Service技术、软件配置管理等课程。本学期开设的企业计算领域课程的确不多,但您应在空余时间将J2EE,DB、UML等技术再深入地钻研下去,一 定要在某个领域有深入的掌握。只是跟着听课,即使学了再多课程也是没用处的,自己钻研下去才是最重要的。只一个J2EE便是博大精深的,足够你啃下去的, 钻研下去,您会发现你还要学的相关知识还有很多(包括EJB、XML、Web Service、Design Pattern等)。

  虽然从事企业计算的人才很多,但以下企业计算领域无论国内外都属稀缺人才:

  (1)掌握大型ERP系统,主要是SAP系统,包括SAP Basis(系统管理)或SAP ABAP(编程)或SAP功能模块实施(特别是财务模块FI的实施)。SAP顾问身价是最高的,而且非常难找。其它大型ERP系统,掌握 PeopleSoft、Oracle Finacial、J.D.Edward、Siebel等大型ERP软件系统的人也很值钱。这方面的人之所以身价奇高,主要是因为这些软件很专业,特别 大,很难有D版可学习,只有特大企业(如世界500强,90%以上使用SAP)才用得起,而且必须有实际工作经验才能掌握。如果是一个个人人都很容易有机 会接触的软件,那么这方面的人通常就不会稀缺。如果大家将来有机会接触学习这些大型ERP软件系统的机会,建议毫不犹豫地抓住,那将捧上一辈的金饭碗。在 国外,会SAP的人特别值钱。物以稀为贵,这永远是颠扑不破的真理。SAP的价值不仅是因为他是一个ERP软件, 而是其中体现的现代企业管理理念(如根据订货需求自动安排原料采购和生产计划等)。一般500强公司绝不会像国内很多企业那样,用J2EE从头设计企业的 ERP系统(即将是怎样的人力投入,而且设计出来的系统怎么可能是完善的?),一定都会使用SAP这样成熟的ERP软件。用不起SAP的公司可能会用 J2EE设计ERP系统。

  (2)掌握IBM大型机技术的人,如S/390主机,MVS操作系统,JCL作业控制语言,COBOL程序设计语言,DB2关系数据库或IMS 层次数据库,CISC中间件交易控制系统等IBM大型机专用技术。国内五大银行,以及国外绝大多数银行的后台系统使用的都是以上平台。IBM大型机号称永 不宕机而且平台相对封闭(这样最安全),所以这些要求在24*7环境中连续运行的关键应用(术语叫mission critical applications)都采用IBM大型机。这方面的人才之所以稀缺,是因为会大型机的人都是老人(90年代以前搞IT的人),全世界新毕业的IT毕 业生不可能再去学IBM大型机(这是一种相对“古老“的技术),没有新人补上,而银行的系统必须维持下去而且银行还要不断开发新业务(如新的存款品种), 虽然对IBM大型机人才的绝对需求量不很大,但相对恒定,银行到哪里找这方面的新人,很难找到. 若好找,花旗软件也不会花那么大的代价去培训我们的实习同学了(去年培训20多个人,听说公司就花了数十万元培训费). 如果您将来到国外找工作,会IBM大型机可能是最好找工作的领域之一了,而且保证找的都是大银行等好工作,我以前教过的计算机专业90-94级的一些同 学,凡是毕业后从事大型机开发的,现多在国外一些很好的公司工作(有几位同学在各国各公司跳来跳去,简直如履平地). 其实我觉得我们最幸福的同学就是在花旗软件做IBM大型机银行软件的同学,这样的机会太难得了.正规高校软件学院00级2+2班一位同学,当初放弃保研, 看准在花旗软件做大型机并且非常努力,还未毕业,公司便派她到国外参加一个项目的开发,成了项目骨干,我觉得她当初选择是完全正确的(01级一位女同学刚 刚也自愿放弃了保研机会去花旗做大型机,我们祝愿她将来也能有好的前景。其实像花旗软件主动安排并鼓励员工读在职研究生,这样开明的公司目前并不多的,在 职读研也是一种不错的选择,又不会失去自己喜欢的实习工作机会,能兼顾),读书的最终目地还是为了工作. 如果您将来在国外找工作,根本没人管您是什么文凭,国外企业绝不会花冤枉钱,只会招有领域工作经验能立即上手的人,用最少的钱在限定的时间完成项目. 而在国内,因为人力成本较低,公司招聘一很多高学历的人才,尽管可能根本用不到这么高的学历,但国内的人力太便宜了,为什么不高消费一下人才呢?这样公司 的门面还要好看些。

  (3)其它如掌握数据仓库技术的人在国内也很少. 目前最主流的数据仓库平台应是ORACLE的数据仓库工具. 在国外,会一些特殊数据仓库的人,如NCR/TEREDATA的人非常难找.

  我们的同学现在年纪都很轻,年轻人充满热情,喜欢追逐一些热门技术,这当然正确的,毕竟学习SAP和大型机的机会毕竟不多,毕业时先能找到一份 工作是重要的. 但我相信随着年纪的增长,大家将来慢慢都会思考的,掌握一项竞争对手较少的绝技的重要性,将来如果自己到国外工作什么技术最好找工作(对搞软件的人到国外 工作或移民是最容易的,也许您现在不想,但我相信工作多年以后,很大一部分同学可能想到国外闯荡一下),你要考虑你今后一生的出路,什么样的绝技是最稳定 最轻松最高收入的. 搞软件的人,当年纪大些时,您可能更向住像搞医学人的那样能更多靠经验吃饭,而不须整天像年轻人那样不得不去追逐不断出现的软件新技术,这个时候也许您也 许会发现,如果您在SAP或大型机等方面有些绝技,您会有很大优势,因为这些较偏的领域其技术变化是相对很缓慢的.

  我还记得在2000年时我曾在业余时间与一位德国人合作面试一些IT人才到德国去,那时德方各公司发来的需求有很多是SAP和IBM大型机的,我们在众多应聘者中最后也未找到一个在这方面有经验,甚至是有一点经验的. 相反,掌握流行技术的人因太多而不很值钱.

  我们的同学将来找工作时,不仅要盯着国内市场,还要有一种放眼全球的眼光,对搞软件的人您将来完全可能到其它国家去工作. 尤其是在欧美、日本、新加坡等国家,对SAP(包括IBM大型机)人才的需求是很大的。毕竟比同学见得多些,提醒同学将来多留意有学习这些绝技的机会,一 旦有机会建议当仁不让. 国内的人才市场可访问www.51job.com,国外的IT人才需求可访问www.hotjobs.comwww.workopolis.comwww.monster.com等著名网站。应经常访问这些网站,以了解市场对人才的具体需求,早做准备。

  以上对企业计算领域的观点,供大家参考.虽然观点未必正确,但确是直言不讳. 总之,每个人的脑袋都长在自己脖子上,每个人都应有自己的判断.

  还要注意,我以上纯粹是从将来就业的角度谈问题. 如果您将来准备到国外读书,则应重视基础课,像C,Assembly,OOP,Discrete Math,Data Structure,Opeating System,Database Principle,Network,Software Engineering,Compiler,Digital Circuit,Computer Graphics,Computer Component and Architecture等基础课,在国外大学IT专业中一般都能找到相同课程,若国内学过,到国外读书时一般可申请免修一部分. 但我也想提醒同学,如果您将来毕业时万一申请国外大学不成,不得不去找工作时,若只将精力花在这些IT专业学生都会的基础课上(传统IT教育模式), 未掌握一些像J2EE等技能型技术,是不容易找到一份工作的,我们已有同学有这样的教训。从找工作的角度讲,企业关心的不是您学过什么课程,而是关心您能 做什么,有什么技能,能做什么项目。

  二、关于嵌入式系统方向

  嵌入式系统无疑是当前最热门最有发展前途的IT应用领域之一。嵌入式系统用在一些特定专用设备上,通常这些设备的硬件资源(如处理器、存储器 等)非常有限,并且对成本很敏感,有时对实时响应要求很高等。特别是随着消费家电的智能化,嵌入式更显重要。像我们平常常见到的手机、PDA、电子字典、 可视电话、VCD/DVD/MP3 Player、数字相机(DC)、数字摄像机(DV)、U-Disk、机顶盒(Set Top Box)、高清电视(HDTV)、游戏机、智能玩具、交换机、路由器、数控设备或仪表、汽车电子、家电控制系统、医疗仪器、航天航空设备等等都是典型的嵌 入式系统。

  嵌入式系统是软硬结合的东西,搞嵌入式开发的人有两类。

  一类是学电子工程、通信工程等偏硬件专业出身的人,他们主要是搞硬件设计,有时要开发一些与硬件关系最密切的最底层软件,如 BootLoader、Board Support Package(像PC的BIOS一样,往下驱动硬件,往上支持操作系统),最初级的硬件驱动程序等。他们的优势是对硬件原理非常清楚,不足是他们更擅长 定义各种硬件接口,但对复杂软件系统往往力不从心(例如嵌入式操作系统原理和复杂应用软件等)。

  另一类是学软件、计算机专业出身的人,主要从事嵌入式操作系统和应用软件的开发。如果我们学软件的人对硬件原理和接口有较好的掌握,我们完全也 可写BSP和硬件驱动程序。嵌入式硬件设计完后,各种功能就全靠软件来实现了,嵌入式设备的增值很大程度上取决于嵌入式软件,这占了嵌入式系统的最主要工 作(目前有很多公司将硬件设计包给了专门的硬件公司,稍复杂的硬件都交给台湾或国外公司设计,国内的硬件设计力量很弱,很多嵌入式公司自己只负责开发软 件,因为公司都知道,嵌入式产品的差异很大程度在软件上,在软件方面是最有“花头“可做的),所以我们搞软件的人完全不用担心我们在嵌入式市场上的用武之 地,越是智能设备越是复杂系统,软件越起关键作用,而且这是目前的趋势。

  从事嵌入式软件开发的好处是:

  (1) 目前国内外这方面的人都很稀缺。一方面,是因为这一领域入门门槛较高,不仅要懂较底层软件(例如操作系统级、驱动程序级软件),对软件专业水平要求较高 (嵌入式系统对软件设计的时间和空间效率要求较高),而且必须懂得硬件的工作原理,所以非专业IT人员很难切入这一领域;另一方面,是因为这一领域较新, 目前发展太快,很多软硬件技术出现时间不长或正在出现(如ARM处理器、嵌入式操作系统、MPEG技术、无线通信协议等),掌握这些新技术的人当然很找。 嵌入式人才稀缺,身价自然就高,越有经验价格就越高。其实嵌入式人才稀少,根本原因可能是大多数人无条件接触,这需要相应的嵌入式开发板和软件,另外需要 有经验的人进行指导开发流程。

  (2) 与企业计算等应用软件不同,嵌入式领域人才的工作强度通常低一些(但收入不低)。搞企业应用软件的IT企业,这个用户的系统搞完了,又得去搞下一个用户 的,而且每个用户的需求和完成时间都得按客户要求改变,往往疲于奔命,重复劳动。相比而言,搞嵌入式系统的公司,都有自己的产品计划,按自己的节奏行事。 所开发的产品通常是通用的,不会因客户的不同而修改。一个产品型号开发完了,往往有较长一段空闲时间(或只是对软件进行一些小修补),有时间进行充电和休 整。另外,从事嵌入式软件的每个人工作范围相对狭窄,所涉及的专业技术范围就是那些(ARM、RTOS、MPEG、802.11等),时间长了这些东西会 越搞越有经验,卖卖老本,几句指导也够让那些初入道者琢磨半年的。若搞应用软件,可能下一个客户要换成一个完全不同的软件开发平台,那就苦了。

  (3) 哪天若想创业,搞自已的产品,那么嵌入式是一个不错的主意,这可不像应用软件那样容易被盗版。土木学院有一个叫启明星的公司开发出一个好象叫“工程e”的 掌上PDA(南校区门口有广告),施工技术人员用该PDA可当场进行土木概预算和其它土木计算,据说销路特好。我认识的某大学老师,他开发的饭馆用的点菜 PDA(WinCE平台,可无线连网和上网),据他说销路不错,饭馆点点PDA让客户点菜,多显派头档次。我记得00级2+2班当年有一组同学在学 Windows程序设计课程时用VC++设计了一个功能很强的点菜系统做为课程项目,当时真想建议他们将这个软件做成PDA,估计会有些销路(上海火车站 南广场的Macdonald便使用很漂亮的PDA给用户点食品,像摸像样的)。这些PDA的硬件设计一般都是请其它公司给订做(这叫“贴牌”:OEM), 都是通用的硬件,我们只管设计软件就变成自己的产品了。

  从事嵌入式软件开发的缺点是:

  (1) 入门起点较高,所用到的技术往往都有一定难度,若软硬件基础不好,特别是操作系统级软件功底不深,则可能不适于此行。

  (2) 这方面的企业数量要远少于企业计算类企业。特别是从事嵌入式的小企业数量较多(小企业要搞自己的产品创业),知名大公司较少(搞嵌入式的大公司主要有 Intel、Motorola、TI、Philip、Samsung、Sony、Futjtum、Bell-Alcatel、意法半导体、 Microtek、研华、华为、中兴通信、上广电等制造类企业)。这些企业的习惯思维方式是到电子、通信等偏硬专业找人。由于正规高校软件学院以前毕业生 以企业计算为主,所以正规高校软件学院与这些企业联系相对较少。正规高校软件学院正积极努力,目前已与其中部分公司建立了联系,争取今后能有正规高校软件 学院同学到这些企业中实习或就业。

  (3)有少数公司经常要硕士以上的人搞嵌入式,主要是基于嵌入式的难度。但大多数公司也并无此要求,只要有经验即可。

  正规高校软件学院同学若学习嵌入式,显然应偏重于嵌入式软件,特别是嵌入式操作系统方面,应是我们的强项。对于搞嵌入式软件的人,最重要的技术显然是(实际上很多公司的招聘广告上就是这样写的):

  (1) 掌握主流嵌入式微处理器的结构与原理

  (2) 必须掌握一个嵌入式操作系统

  (3) 必须熟悉嵌入式软件开发流程并至少做过一个嵌入式软件项目。

  正规高校软件学院在嵌入式软件方面最重要的课程包括:

  (1) 嵌入式微处理器结构与应用:这是一门嵌入式硬件基础课程,正规高校软件学院用这门课取代了传统的“微机原理与接口”课程(目前国内已有少部分高校IT专业 这样做了,因为讲x86微机原理与接口很难找到实际用处,只为教学而已)。我们说过,嵌入式是软硬件结合的技术,搞嵌入式软件的人应对ARM处理器工作原 理和接口技术有充分了解,包括ARM的汇编指令系统。若不了解处理器原理,怎么能控制硬件工作,怎么能写出节省内存又运行高速的最优代码(嵌入式软件设计 特别讲究时空效率),怎么能写出驱动程序(驱动程序都是与硬件打交道的)?很多公司招聘嵌入式软件人员时都要求熟悉ARM处理器,将来若同学到公司中从事 嵌入式软件开发,公司都会给你一本该设备的硬件规格说明书 (xxx Specification),您必须能看懂其中的内存分布和端口使用等最基本的说明(就像x86汇编一样),否则怎么设计软件。有些同学觉得嵌入式处理 器课程较枯燥,这主要是硬件课程都较抽象的原因,等我们的嵌入式实验室10月份建好后,您做了一些实验后就会觉得看得见摸得着。还有同学对ARM汇编不感 兴趣,以为嵌入式开发用C语言就足够了。其实不应仅是将汇编语言当成一个程序设计语言,学汇编主要是为了掌握处理器工作原理的。一个不熟悉汇编语言的人, 怎么能在该处理器写出最优的C语言代码。在嵌入式开发的一些关键部分,有时还必须写汇编,如Bootloader等(可能还包括BSP)。特别是在对速度 有极高要求的场合(如DSP处理器的高速图像采集和图像解压缩),目前主要还要靠汇编写程序(我看到过很多公司是这样做的)。当您在一个嵌入式公司工作 时,在查看描述原理的手册时,可能很多都是用汇编描述的(我就遇到过),这是因为很多硬件设计人员只会写或者喜欢用汇编描述,此时您就必须看懂汇编程序, 否则软硬件人员可能就无法交流。很多嵌入式职位招聘时都要求熟悉汇编。

  (2) 嵌入式操作系统类课程

  除了WinCE的实时性稍差外,大多数嵌入式操作系统的实时性都很强,所以也可称为实时操作系统Real Time Operating System.从事嵌入式的人至少须掌握一个嵌入式操作系统(当然掌握两个更好),这在嵌入式的所有技术中是最为关键的了。目前最重要的RTOS主要包 括:

  第一类、传统的经典RTOS:最主要的便是Vxworks操作系统,以及其Tornado开发平台。Vxworks因出现稍早,实时性很强(据 说可在1ms内响应外部事件请求),并且内核可极微(据说最小可8K),可靠性较高等,所以在北美,Vxworks占据了嵌入式系统的多半疆山。特别是在 通信设备等实时性要求较高的系统中,几乎非Vxworks莫属。Vxworks的很多概念和技术都和Linux很类似,主要是C语言开发。像Bell- alcatel、Lucent、华为等通信企业在开发产品时,Vxworks用得很多。但Vxworks因价格很高,所以一些小公司或小产品中往往用不 起。目前很多公司都在往嵌入式Linux转(听说华为目前正在这样转)。但无论如何,Vxworks在一段长时间内仍是不可动摇的。与Vxworks类似 的稍有名的实时操作系统还有pSOS、QNX、Nucleus等RTOS。

  第二类、嵌入式Linux操作系统:Linux的前途除作为服务器操作系统外,最成功的便是在嵌入式领域的应用,原因当然是免费、开源、支持软 件多、呼拥者众,这样嵌入式产品成本会低。Linux本身不是一个为嵌入式设计的操作系统,不是微内核的,并且实时性不强。目前应用在嵌入式领域的 Linux系统主要有两类:一类是专为嵌入式设计的已被裁减过的Linux系统,最常用的是uClinux(不带MMU功能),目前占较大应用份额,可在 ARM7上跑;另一类是跑在ARM 9上的,一般是将Linux 2.4.18内核移植在其上,可使用更多的Linux功能(当然uClinux更可跑在ARM 9上)。很多人预测,嵌入式Linux预计将占嵌入式操作系统的50%以上份额,非常重要。缺点是熟悉Linux的人太少,开发难度稍大。另外,目前我们 能发现很多教材和很多大学都以ucOS/II为教学用实时操作系统,这主要是由于ucOS/II较简单,且开源,非常适合入门者学习实时操作系统原理,但 由于ucOS/II功能有限,实用用得较少,所以正规高校软件学院不将其作为教学重点,要学习就应学直接实用的,比如 uClinux就很实用。况且熟悉了Linux开发,不仅在嵌入式领域有用,对开发Linux应用软件,对加深操作系统的认识也有帮助,可谓一举多得。据 我所知,目前Intel、Philip都在大搞ARM+LINUX的嵌入式开发,Fujitum则是在自己的处理器上大搞Linux开发。目前在嵌入式 Linux领域,以下几个方面的人特别难找,一是能将Linux移植到某个新型号的开发版上;二是能写Linux驱动程序的人;三是熟悉Linux内核裁 减和优化的人。正规高校软件学院在该嵌入式Linux方面的课程系列是:本科生操作系统必修课,然后是Linux程序设计选修课,最后是嵌入式Linux 系统选修课。正规高校软件学院在Linux方面目前已有较强力量,魏老师和张老师熟悉Linux开发,金老师和唐老师熟悉Linux系统管理。

  第三类、 Windows CE嵌入式操作系统:Microsoft也看准了嵌入式的巨大市场,MS永远是最厉害的,WinCE出来只有几年时间,但目前已占据了很大市场份额,特别 是在PDA、手机、显示仪表等界面要求较高或者要求快速开发的场合,WinCE目前已很流行(据说有一家卖工控机的公司板子卖得太好,以至来不及为客户裁 减WinCE)。WinCE目前主要为4.2版(.NET),开发平台主要为WinCE Platform Builder,有时也用EVC环境开发一些较上层的应用,由于WinCE开发都是大家熟悉的VC++环境,所以正规高校软件学院学过Windows程序 设计课程的同学都不会有多大难度,这也是WinCE容易被人们接受的原因,开发环境方便快速,微软的强大技术支持,WinCE开发难度远低于嵌入式 Linux。对于急于完成,不想拿嵌入式Linux冒险的开发场合,WinCE是最合适了(找嵌入式Linux的人可没那么好找的),毕竟公司不能像学生 学习那样试试看,保证开发成功更重要。根据不同的侧重点 ,WinCE还有两个特殊版本,一个是MS PocketPC操作系统专用于PDA上(掌上电脑),另一个是MS SmartPhone操作系统用于智能手机上(带PDA功能的手机),两者也都属于WinCE平台。在PDA和手机市场上,除WinCE外,著名的PDA 嵌入式操作系统还有Palm OS(因出现很早,很有名)、Symbian等,但在WinCE的强劲冲击下,Palm和Symbian来日还能有多长?正规高校软件学院可能是全国高校 中唯一一家开设专门的“Windows CE嵌入式操作系统“课程的学校,这主要是基于以下原因:正规高校软件学院本身前面便有Windows程序设计课程,同学学过VC++后再学WinCE, 非常方便自然,通过学习WinCE同样也可了解嵌入式软件的一般开发过程,对Linux有惧怕心理的同学也很合适。很显然,嵌入式Linux永远不可能替 代WinCE,而且将来谁占份额大还很难讲,毕竟很多人更愿意接受MS的平台,就像各国政府都在大力推LINUX已好长时间,但您能看到几个在PC机上真 正使用LINUX的用户?据我观察,目前在嵌入式平台上,LINUX是叫得最响,但还是WinCE实际用得更多.嵌入式LINUX可能更多地是一些有长远 产品计划的公司,为降低成本而进行长远考虑; 二是微软亚洲研究院对正规高校软件学院WinCE课程的支持计划,我们也很希望将来正规高校软件学院能有同学通过微软的面试去实习。WinCE和多媒体 (如MPEG技术)是微软亚洲工程院目前做得较多的项目领域之一,他们很需要精通WinCE的人。

  总结关于嵌入式操作系统类课程,若您觉得自己功底较深且能钻研下去,则可去学嵌入式Linux;若您觉得自己VC++功底较好且想短平快地学嵌入式开发,则正规高校软件学院的WinCE课程是最好的选择。

  (3) 嵌入式开发的其它相关软件课程

  搞嵌入式若能熟悉嵌入式应用的一些主要领域,这样的人更受企业欢迎。主要的相关领域包括:

  A、数字图像压缩技术:这是嵌入式最重要最热门的应用领域之一,主要是应掌握MPEG编解码算法和技术,如DVD、MP3、PDA、高精电视、机顶盒等都涉及MPEG高速解码问题。为此,正规高校软件学院已预订了一位能开设数字图像处理课程的博士。

  B、通信协议及编程技术:这包括传统的TCP/IP协议和热门的无线通信协议。首先,大多数嵌入式设备都要连入局域网或Internet,所以 首先应掌握TCP/IP协议及其编程,这是需首要掌握的基本技术;其次,无线通信是目前的大趋势,所以掌握无线通信协议及编程也是是很重要的。无结通信协 议包括无线局域网通信协议802.11系列,Bluetooth,以及移动通信(如GPRS、GSM、CDMA等)。

  C、网络与信息安全技术:如加密技术,数字证书CA等。正规高校软件学院有这方面的选修课。

  D、DSP技术:DSP是Digital Signal Process数字信号处理的意思,DSP处理器通过硬件实现数字信号处理算法,如高速数据采集、压缩、解压缩、通信等。数字信号处理是电子、通信等硬件 专业的课程,对于搞软件的人若能了解一下最好。目前DSP人才较缺。如果有信号与系统、数字信号处理等课程基础,对于学习MPEG编解码原理会有很大帮 助。

  (4)嵌入式开发的相关硬件基础

  对于软件工程专业的学生,从事嵌入式软件开发,像数字电路、计算机组成原理、嵌入式微处理器结构等硬件课程是较重要的。另外,汇编语言、C/C ++、数据结构和算法、特别是操作系统等软件基础课也是十分重要的。我们的主要目地是能看懂硬件工作原理,但重点应是在嵌入式软件,特别操作系统级软件, 那将是我们的优势。

  我们的研究生里有些是学电子、通信类专业过来的,有较好的模拟电路和单片机基础,学嵌入式非常合适。嵌入式本身就是从单片机发展过来的,只是单 片机不带OS,而现在很多嵌入式应用越来越复杂,以至不得不引入嵌入式操作系统。另外,为追求更高速的信号处理速度,现在在一些速度要求较高的场合,有不 少公司是将一些DSP算法,如MPEG压缩解压缩算法等用硬件来实现,这就涉及到HDL数字电路设计技术及其FPGA/IP核实现技术,这方面的人目前市 场上也很缺。

  (5) 题外话

  另外,能写驱动程序的人目前是非常紧缺的(驱动程序也可归于嵌入式范畴),包括桌面Windows中的DDK开发环境和WDM驱动程序。公司每 时每刻都要推出新产品,每一个新产品出来了,要能被操作系统所使用,是必须写驱动程序的。写驱动程序就必须掌握操作系统(如Windows或Linux) 的内部工作原理,还涉及到少量硬件知识,难度较大,所以这方面的人很难找。想成为高手的同学,也可从驱动程序方面获得突破。我可说一下自己的经历,三年前 我曾短暂地在一家公司写过WinCE驱动程序(正是因为知道这方面的人紧缺,所以才要做这方面的事),尽管那以前从未做过驱动程序,应聘那个职位时正是看 准了公司是很难招聘到这方面的人,既然都找不到人,驱动还得有人做,这正是可能有机会切入这一领域的大好机会。面试时大讲自己写过多少万行汇编程序,对计 算机工作原理如何清楚,简历中又写着我曾阅读完两本关于Windows Driver Model的两本英文原版书,写过几个小型的驱动程序练习程序(其实根本没写过,我们的同学将来千万不要像我这样,早练就些过硬功夫,就不至于沦落到我这 等地步,就不用像我那样去“欺骗”公司了,我这是一个典型的反面教材),居然一切都PASS(当然最重要的是笔试和面试问题还说得过去),这只能说明这一 领域找人的困难程度。公司本就未指望找到搞过驱动的人,找个有相关基础的人就算不错了。做了以后,发现也并不是怎样难的。其实搞驱动程序的工作是很舒服 的,搞完一个版本就会空一段时间,只有等公司新的芯片推出或新的OS出现后,才需要再去开发新一版驱动,那时有将近一个月时间空闲着在等WinCE .NET Beta版推出,准备将驱动程序升级到CE .NET上,现在在软件学院工作整日忙,无限怀念那段悠闲时光。

  很巧合,最近本人无意中再次体会到了嵌入式的迷人之处。上周我那用了3年的手机终于不能WORK了。此次更新,除要求有手机常见功能外,最好有 MP3功能(现在很多英语听力都有MP3文件),最好有英汉词典,最好还能读WORD文档。最后选了个满足以上条件的最便宜的手机DOPOD 515(斩了我2.2K,但想想这也算自己对嵌入式事业的支持,这样便也想开了),算得上最低档的智能手机了。回来一查,手机的about显示,本手机 Processor是ARM,其OS是MS Smartphone(即WinCE .NET 4.2),这么巧合,简直可做为学习嵌入式课程的产品案例了(等我们的WinCE课程开得有声有色后,希望能从微软研究院搞些Smartphone来开发 开发)。有OS的手机果然了得,金山词霸、WORD、EXCEL、REGEDIT等居然都有smartphone版的,PC上的MP3、DOC等居然在 download时都可被自动转换成smartphone格式,真是爽。完全可用Windows CE自己开发一些需要的程序download到自己的手机上。现在市面销售PDA智能手机火爆,MS总是财源滚滚。但我已发现国产的ARM+LINUX手 机出现在市面上,价格只1.2K。

  在GOOGLE网上能搜索太多的关于嵌入式系统的讨论了,我刚发现一个http://www.embyte.com 非常不错,有很多有经验者谈自己的体会,投入到其中的论坛中,你会切身感到嵌入式学习的热潮。

  要么走ARM+WinCE,要么走ARM+LINUX,要么走ARM+VXWORKS。每个搞嵌入式的人都可选一条路,条条大路通罗马。

  三、关于游戏软件方向

  将游戏软件人才称为数字媒体软件人才可能更好听些,包括游戏软件策划(最缺游戏策划的人)、游戏软件美术设计、游戏软件程序设计等多方面的人才,对软件学院,游戏软件程序设计当然是最合适的了。

  游戏软件人才的确目前很缺,听说很多游戏软件公司苦于没新人才补充,特别是没有高手补充,不得不相互挖人才,以至将游戏软件人才身价越抬越高。 网上说日本教育部刚刚批准成立了日本第一家专门培养四年制游戏软件人才的本科大学。其实国内很多大学,特别是软件学院都有搞游戏软件人才的设想,但目前很 少有做成的,主要原因是找不到能上游戏软件课的教师,听说有个学校只能花很大的价钱从Korea找老师来上课,果真缺到此等地步?

  已有很多青少年沉湎于网游而颓废的实例,好在还不至于上升到制造精神鸦片的高度,所以开发游戏软件的人也不必每日惭悔(但开发儿童益智类游戏软 件的人是不需惭悔的),如果想想这是为发展民族软件产业做贡献,那反倒是一件有意义的事情了。不过听一家游戏软件公司的老板讲,搞游戏软件开发是非常辛苦 的。

  若想自己创业,搞搞游戏软件是不错的主意。现在网上网站或公司都在收购游戏软件(特别是手机游戏软件,因为手机游戏用户可选从网站上 download到手机上,不像网游那么复杂),按download次数分成或一次性收购的都有。我们的同学在校期间是否也可发点小财?搞得好,说不定可 卖到国外网站,直接挣$$$呢。

  大致游戏分成以下几类:

  (1) PC类游戏,包括单机和网游。这类游戏开发平台基本上都是基于VC++和DitrectX(如DirectShow,DirectDraw,D3D等, DirectX资料可直接到MS网站上查)。DirectX和OpenGL是两个主要的图形标准,OpenGL跨平台(Unix/Windows上都可 跑),尽管很多搞研究的人对OpenGL赞不绝口,将DirectX骂得一文不值,但事实是,在Windows平台上,DirectX是最快最方便的,所 以在Windows平台上的游戏还是DirectX当家。

  (2) 手机游戏:目前手机游戏主要开发平台有两类:

  第一类手机游戏是J2ME平台(Java 2 Micro Edition),J2ME本是为嵌入式平台设计的Java,但由于Java生来就需要Java虚拟机(JVM)来解释,所以在嵌入式产品很少用J2ME (太慢太耗内存)。但在手机游戏中J2ME倒有用武之地,我想这可能主要是Java可跨OS平台的原因,因为手机的OS是千奇百怪的。我对J2ME完全外 行,但上次听Square Enix公司的人说,J2ME与我们同学学过的J2EE还是有较大差别的。据我所知,目前手机中用的较多的是KJava语言,KJava是运行在一种叫K Java Virtual Machine的解释器上(K JVM是SUN早期为演示J2ME在嵌入式系统应用而开发的一个虚拟机),所以将在K JVM上运行的J2ME叫KJava。尽管SUN说今后不保证支持K JVM,将开发新的更高性能的J2ME虚拟机取而代之,但由于KJava出现较早,很多早期的手机游戏软件都将K JVM假想成J2ME虚拟机的标准了,所以目前有大量的KJava手机游戏软件存在,而且还在用KJava继续开发。特别是日本的手机游戏软件由于开发较 早(像叫什么docomi的日本最大的电信运营商手机游戏搞得很火),多是基于KJava的。所以目前市场上在招聘手机游戏软件人才时,很多要求掌握 KJava。有关J2ME请到Sun的网站上找资料。

  另一类手机游戏是BREW平台,BREW是美国高通公司(Qualcomm,CDMA核心技术都是该公司开发的,有无数移动通信技术专利)发明 的,据说可编译成二进制代码,那当然快了。主要的开发语言是C/C++。但迫于被指责为较封闭的压力,目前Qualcomm已推出BREW平台上的 J2ME虚拟机(但可想像那将是怎样慢的速度)。Qualcomm搞定了很多手机制造商签定BREW授权许可协议,最狠的是Qualcomm与中国联通绑 在一起大堆基于BREW的手机游戏,所以有些公司招聘时要求掌握BREW也就不奇怪了。

  去年00级2+2班毕业答辩时,有一位同学讲的是在公司做的KJava游戏(那是一家日本游戏软件公司),还一位同学讲的是另一家公司做的 BREW游戏,看来不同的公司有不同的选择。将来谁会更火,我估计随着手机硬件资源的不断提高,不会在乎一个JVM的开销,J2ME应更有前途,毕竟它是 更开放的。

  (3) 专用游戏机:如电视游戏,XBOX等,我不太了解,不过这些游戏也太偏了。

  从著名游戏公司发来的对网游和手机游戏的人才需求,很有代表性。从中我们可看出,游戏公司对人才的需求主要是以下技术:

  (1) 计算机图形学,特别是3D编程与算法,包括DirectX或OpenGL。开发平台是VC++/DIRECTX或KJAVA。

  (2) 公司说,手机游戏因手机资源有限,必须对图像进行压缩,所以若有一些图像压缩算法知识比较好。像若能有MPEG压缩算法较好,手机上采用的是比MPEG压缩得更狠的一些特殊算法,但触类旁通。

  (3) TCP/IP Socket编程是搞网游开发的人必须掌握的。

  (4) 人工智能知识:复杂游戏可能需要一些AI算法。

  (5) 网络与信息安全知识:网游要防外挂。

  一般游戏公司的网游服务器是基于Linux平台的,所以还提出了对游戏服务器端软件工程师的技术需求(精通MSSQL、ORACLE、 MYSQL等数据库,精通Linux Programming,特别是Socket编程)。还有对维护游戏网站人才需求(ASP .NET和数据库)。详细请同学自己看服务器。注意一条,最好有自己的游戏软件作品,若您应聘时能带一个DirectX作品,那将有多强的竞争力,所以最 重要的是现在就要行动,实践,实践,再实践。

posted @ 2008-05-06 19:54 iJrong 阅读(71) | 评论 (0)编辑 收藏

2008年5月5日 #

从网上看到用SetDevicePower可以开关WIFI,但郁闷的是不同的OEM厂商,甚至不同型号的手机,WIFI设备名称都可能不一样。

不过我想了想,还是搞定了 Windows Mobile 中开关 WIFI 的“通用”代码,基本上在各种WM手机中都能正常工作。

看下面的代码,主要是枚举所有的通讯设备,如果设备显示名称中包含特定字符串(如Wi-Fi,802.11等)就认为是WIFI设备,接下来的问题迎刃而解,OK,“完美”收工:)

#pragma once
#include "UniBase.h"
#include "Reg.h"
#include <PM.h>

class CWiFiHelper
{
private:
    TCHAR m_tzDevice[MAX_NAME];

public:
    CWiFiHelper()
    {
        TCHAR tzKey[MAX_PATH];
        CReg reg(HKEY_LOCAL_MACHINE, TEXT("Comm"));
        for (UINT i = 0; reg.EnumKey(i, tzKey) == S_OK; i++)
        {
            if (tzKey[UStrLen(tzKey) - 1] == '1')
            {
                CReg dev(reg, tzKey);
                TCHAR tzDisplayName[MAX_PATH];
                if (dev.GetStr(TEXT("DisplayName"), tzDisplayName))
                {
                    if (UStrStrI(tzDisplayName, TEXT("WiFi")) ||
                        UStrStrI(tzDisplayName, TEXT("Wi-Fi")) ||
                        UStrStrI(tzDisplayName, TEXT("Wireless")) ||
                        UStrStr(tzDisplayName, TEXT("802.11")))
                    {
                        UStrPrint(m_tzDevice, PMCLASS_NDIS_MINIPORT TEXT("
\\%s"), tzKey);
                        //MessageBox(NULL, m_tzDevice, STR_AppName, MB_ICONINFORMATION);
                        return;
                    }
                }
            }
        }
        m_tzDevice[0] = 0;
    }

    BOOL IsEnabled()
    {
        CEDEVICE_POWER_STATE s = D4;
        if (m_tzDevice [0])
        {
            GetDevicePower(m_tzDevice, POWER_NAME, &s);
        }
        return s == D0;
    }

    HRESULT Enable(BOOL bEnable = TRUE)
    {
        if (m_tzDevice [0])
        {
            return SetDevicePower(m_tzDevice, POWER_NAME, bEnable ? D0 : D4);
        }
        else
        {
            return ERROR_DEVICE_NOT_AVAILABLE;
        }
    }
};


附件是一个小程序,无参数执行自动切换 WIFI 开关SP/PPC通用。
点击这里下载文件: RadioSwitch.rar

posted @ 2008-05-05 23:13 iJrong 阅读(58) | 评论 (0)编辑 收藏

内存管理
       如果你在写Windows CE 程序中遇到的最重要的问题,那一定是内存问题。一个WinCE 系统可能只有4MB 的RAM,这相对于个人电脑来说是十分少的,因为个人电脑的标准配置已经到了128MB 甚至更多。事实上,运行WinCE 的机器的内存十分缺乏,以至于有时候有必要在写程序的时候为节约内存而牺牲程序的整体性能。
       幸运的是,尽管WinCE系统的内存很小,但可用来管理内存的函数却十分完善。WinCE实现了Microsoft Windows XP和Microsoft Windows Me中可用到的几乎全部的Win32内存管理API。WinCE支持虚拟内存(virtual memory)分配,本地(local)和分离(separate)的堆(heaps),甚至还有(memory-mapped files)内存映射文件。
       像Windows XP一样,WinCE支持一个带有应用程序间内存保护功能的32位平面地址空间,但是WinCE是被设计来应用于不同场合,所以它底层的内存结构不同于Windows XP。这些不同能够影响到你如何设计一个WinCE 应用程序。在这一章中,我将讲述最基础的WinCE内存结构。我也将讲述包括WinCE中可用的内存分配方式中的不同点以及如何使用这些不同的内存类型来最小化你的程序的内存占有率。
内存基础
       对所有的电脑来说,系统地运行一个WinCE,需要ROM(只读存储器)和RAM(随机存储器)。但不论如何,在WinCE系统中,ROM和RAM的使用还是稍微有些不同于个人电脑环境。
关于RAM
       RAM在WinCE 系统中被分为两个区域:第一个是程序的存储区(program memory),也叫做系统堆(system heap)。第二个是对象存储区(object store)。这个对象存储区有点像一个永久的虚拟RAM磁盘。不同于PC上的旧式的虚拟RAM磁盘,对象存储区保留存储的文件甚至当系统被关闭以后。(脚注)这种安排的原因是WinCE 系统,例如Pocket PC代表性地具有一个主电池和一个备用电池。当用户更换主电池的时候,备用电池的工作是提供电源给RAM以便维持文件在对象存储区的存储。当用户按了重启键之后,WinCE核心就开始寻找在关闭系统前建立的对象存储区,如果找到的话就将继续使用它。
       RAM中的另一个区域则用作程序存储区。程序存储区有点像个人电脑中的RAM,它为正在运行的应用程序保存堆和栈的内容。在对象存储区和程序存储区之间的分界线是可以通过移动它来改变的,用户可以在控制面板中找到改变这条分界线的设置。在可用内存降低的(low-memory)条件下,系统将会弹出对话框询问用户是否要将对象存储区RAM划分一些给程序存储区RAM以满足要运行的应用程序的需求。
关于ROM
       在个人电脑中,ROM是用来存储BIOS(基本输入输出系统)并且只有64-128KB。在WinCE系统中,ROM大小可以从4MB到32MB并且存放整个操作系统以及和系统捆绑在一起的应用程序。在这种情况下,ROM在WinCE系统中就好像一个只读的硬盘。
       在一个WinCE系统中,存储在ROM之上的程序能够以现场执行(Execute in Place,XIP)的方式运行。换句话说,程序可以直接从ROM中执行而不必先加载到RAM中再执行。这种能力对小型系统来说,使之在两个方面具有巨大的优势。代码直接从ROM中执行意味着程序代码不会占据更有价值的RAM。同样,程序在执行前也不必先复制到RAM中,这样就只需要很少的时间来启动一个应用程序。不在ROM中,但是被包含在对象存储区(译者注:上文将对象存储区比作永久的RAM磁盘,故此处要说明,只有Intel力推的nor flash memroy类型才能以XIP方式执行,ROM其实也是一种nor flash memory类型)或闪存卡(Flash memory storage card)中的程序将不能以现场方式执行,它们将被复制到RAM中再执行。
关于虚拟内存
       WinCE 实现了系统的虚拟内存管理,在一个虚拟内存系统中,应用程序主要处理这个分离(译者注:物理上可能分离,但系统将它们联系起来),虚拟的地址空间,因此并不涉及到由硬件管理的物理内存。操作系统使用微处理器的内存管理单元来处理虚拟地址和物理地址间的实时转换。
       这种虚拟内存方法的优势能从MS-DOS系统复杂的地址空间看出来。一旦请求的RAM超过最初PC设计的640-KB限制,程序设计者将不得不作出像扩展内存一样的计划以便增加可用内存的数量。OS/2 1.x(译者注:IBM研制的操作系统)和Windows 3.0采用了一种基于段(segment-based)的虚拟内存系统来解决问题。应用程序使用虚拟内存不需要知道实际物理内存的位置,只要有内存可用就行。在这些系统中,虚拟内存以一种段的方式被实现了,即可移动的内存块(译者注:段其实就是内存分块)大小从16字节到64KB。64-KB的限制并不是由于段本身原因,而是由于Intel 80286的特性所致,这就是Windows3.x和OS/21.x的分段式虚拟内存系统结构。
分页存储
     
Intel 80386支持的段大小已经超过64KB,但是Microsoft和IBM开始设计OS/2 2.0,他们选择了一种不同的虚拟内存系统,随后也被386所支持,这就是分页式虚拟内存系统。在一个分页存储的系统中,最小的可被微处理器管理的单元是页(page)。对于Windows NT和OS/2 2.0系统来说,页大小都被设置为386处理器默认的4096字节。当一个应用程序存取一个页的时候,微处理器将转换该页的虚拟内存地址到实际的ROM或RAM中的物理页(译者注:这就是实现了地址映射和转换,将虚拟的和实际的存储单元一一对应),这一页同时被标记以便其他程序对该页的访问将被排斥。操作系统决定虚拟内存页是否有效,如果有效,将做一个物理内存页到虚拟页的映射。
WinCE实现了一个和其他Win32操作系统类似的分页式虚拟内存系统。在WinCE中,一页的大小可以从1024字节到4096字节,基于微处理器的不同而不同。这和Windows XP不同,Windows XP页面尺寸是Intel微处理器所支持的4096字节。对WinCE所支持的CPU类型来说,有486,Intel的Strong-ARM,和Hitachi SH4 都是是用了4096-byte 的页面。NEC 4100在Windows CE 3.0中使用了4-KB的页面尺寸但是在较早期的开放式系统版本中使用了1-KB的页面大小。
虚拟内存页可以处在三种状态:自由(free),保留(reserved),或被提交(committed),)。自由页就像它的名称一样,自由并且可被分配。保留页是虚拟地址已经被保留,并且不能被操作系统或进程中的其他线程重新分配。保留页不能用在别处,但是它同样不能被当前程序使用,因为它没有被映射到物理内存。要想执行映射,它必须被提交,一个提交页能被应用程序保留,并且直接映射到物理地址。
所有我刚才讲述的内容对有经验的Win32 程序员们来说是些陈旧的知识。对Windows CE 程序员来说最重要的东西是学习Windows CE 是如何改变这些因素的。当Windows CE 实现了大部分和它的老大哥Win32一样的内存API集的时候,Windows CE下面的基础结构将影响到上面的程序。在分开来看Window CE 应用程序的内存结构之前,让我们先来看看一些提供系统内存全局状态的函数。
查询系统的内存
       如果一个应用程序知道系统当前的内存状态,它将可以较好地管理可用到的资源。WinCE实现了Win32的GetSystemInfo和GlobalMemoryStatus函数,GetSystemInfo函数原型如下:
VOID GetSystemInfo (LPSYSTEM_INFO lpSystemInfo);
它传递了一个指针给SYSTEM_INFO结构,定义如下
typedef struct {
    WORD wProcessorArchitecture;
    WORD wReserved;
    DWORD  dwPageSize;
    LPVOID lpMinimumApplicationAddress;
    LPVOID lpMaximumApplicationAddress;
    DWORD  dwActiveProcessorMask;
    DWORD  dwNumberOfProcessors;
    DWORD  dwProcessorType;
    DWORD  dwAllocationGranularity;
    WORD  wProcessorLevel;
    WORD  wProcessorRevision;
} SYSTEM_INFO;
    wProcessorArchitecture参数表示系统微处理器的架构。它的值是定义在Winnt.h中,例如PROCESSOR_ARCHITECTURE_INTEL。Windows CE扩展了这些常数,包括PROCESSOR_ARCHITECTURE_ARM,PROCESSOR_ARCHITECTURE_SHx,等等。增加的常数包括像Win32操作系统支持的网络CPU(net CPU)。跳过一些参数,我们看dwProcessorType参数,它来自于特定的微处理器类型。常数有Hitachi SHx架构中的PROCESSOR_HITACHI_SH3和PROCESSOR_HITACHI_SH4。最后两个参数,wProcessorLevel和wProcessorRevision,指明了CPU类型的特征。wProcessorLevel参数类似于dwProcessorType参数,它一个指定的微处理器系列中被定义了,dwProcessorRevision告诉你模式(model)和芯片的步进级别(stepping level)。
       dwPageSize参数说明了微处理器页面的大小,以字节为单位。知道这个值,将会在你直接处理虚拟内存API的时候带来方便,在此我只作简短说明。lpMinimumApplication­Address和lpMaximumApplicationAddress参数说明了应用程序可用到的最小和最大的虚拟内存地址。dwActiveProcessorMask和dwNumberOfProcessors参数显示被Window XP系统支持的多个处理器数量。因为Windows CE 只支持一个处理器,所以你可以忽略这个参数。dwAllocationGranularity参数说明了一个完整的虚拟内存区域分配的界限。像Windows XP,Windows CE 规定虚拟区为64-KB的界限(译者注:作者此处64-KB的意思是即使你只分配一个字节的内存,系统也将会保留64-KB的虚拟地址空间给它,这个值一般是由硬件代码实现的,但是不同硬件可能不同值)。
       第二个方便的检测系统状态的函数如下:
void GlobalMemoryStatus(LPMEMORYSTATUS lpmst);
它返回一个MEMORYSTATUS结构,定义为
typedef struct {
    DWORD dwLength;
    DWORD dwMemoryLoad;
    DWORD dwTotalPhys;
    DWORD dwAvailPhys;
    DWORD dwTotalPageFile;
    DWORD dwAvailPageFile;
    DWORD dwTotalVirtual;
    DWORD dwAvailVirtual;
} MEMORYSTATUS;
    dwLength参数在调用这个函数之前必须初始化。dwMemoryLoad参数是一个不确定的值;这是一个可用的一般性的参数指示了当前系统的内存使用情况(译者注:该参数是一个近似的百分比值,指明了物理内存的使用情况)。dwTotalPhys和dwAvailPhys参数指明了RAM有多少页被分配给了程序存储区RAM,和还有多少可用(译者注:实际上是以字节为单位)。这些值不包括分配对象存储区的RAM。
       dwTotalPageFile和dwAvailPageFile参数在Windows XP下和Windows Me下指明了当前页面文件(paging file)的状态。因为Windows CE不支持页面文件,所以这些参数总是0。dwTotalVirtual和dwAvailVirtual参数指明了总共的和可用的可被应用程序存取的虚拟内存页的数量(译者注:参数都是以字节为单位,而不是指页数,dwAvailVirtual是指未保留和未提交的内存)。
       通过GlobalMemoryStatus返回的信息可以验证Windows CE内存结构,通过在有32MBRAM的HP iPaq Pocket PC上调用函数,返回值如下:
dwMemoryLoad       0x18          (24)
dwTotalPhys        0x011ac000    (18,530,304)
dwAvailPhys        0x00B66000    (11,952,128)
dwTotalPageFile    0
dwAvailPageFile    0
dwTotalVirtual     0x02000000    (33,554,432)
dwAvailVirtual     0x01e10000    (31,522,816)
 
    dwTotalPhys参数表明了系统的32MB RAM,分配了18.5MB给程序存储区RAM,其中12MB仍然可用。注意这对应用程序来说,并不是通过这次调用,就知道了另外14MB的RAM是分配给对象存储区的。要检测分配给对象存储区的RAM的大小,要使用GetStoreInformation。
       dwTotalPageFile和dwAvailPageFile参数是0,表明页面文件不被Windows CE所支持。dwTotalVirtual参数十分有趣,因为它显示了Windows CE 强制给程序的32-MB的虚拟内存限制。其间,dwAvailVirtual参数显示了只使用了32MB虚拟内存的一小部分(译者注:即33,554,432-31,522,816=2,031,616)。
应用程序的地址空间
尽管和Windows XP的应用程序设计类似,但Windows CE应用程序地址空间有一个巨大的不同影响到应用程序。在Windows CE之下,一个应用程序被限制在虚拟内存空间中它自己的32MB slot(槽)和 32MB 的slot 1中,slot 1用来加载基于XIP的DLL(译者注:Windows CE将虚拟地址空间分为33个slot,每个slot 32MB,序号从0-32 ,附插图c-1,c-2)。当系统只有4MB RAM的时候,分配给应用程序32MB的虚拟地址空间看起来是比较合理的,Win32的程序员在使用这个2-GB的虚拟地址空间的时候,必须记住对Windows CE应用程序的虚拟地址空间限制。
图7-1展示了一个应用程序的64-MB虚拟地址空间,其中包括32MB用于XIP的DLL空间。

       图7-1     Windows CE的内存映射图
 
       要注意的是应用程序是以一个64-KB的内存区域开始从0x10000映射,记得最低的64KB地址空间是由Windows为所有应用程序保留的。文件映象包括代码,静态数据段和资源段。在实际过程中,当应用程序启动时代码页(code pages)不会载入进来,只有在需要该页面被载入的时候,代码才被载入进来。
       只读静态数据段(read-only static data segment)和可读写静态数据区(read/write static data areas)只占很少的页面。这些段都是排列在一起的。如同代码一样,只有当这些数据段被应用程序读或者写的时候才会提交给RAM。应用程序的资源将被载入到一些分离的页面中,这些资源是只读的,并且只有当它们被应用程序获取的时候才会分页进入RAM。
       应用程序的栈(stack)被映射到资源段之上。栈的段位置很容易被找到,因为它提交的页就在被保留的区域的尾部。栈的表现是从高地址到低地址增长(译者注:即从高到低填满地址)。如果该应用程序有超过一个线程,那么应用程序的地址空间就会保留超过一个的栈的段。
       紧接着栈的就是本地堆(local heap)。引导程序保留了大量的页,大约有几百K交给heap来使用,但是只提交满足malloc,new,LocalAlloc函数调用分配的内存(译者注:这里是说,被分配多少内存才可以提交多少内存,没被分配的不能用作提交)。从本地堆的保留页尾部到non-XIP DLL开始的部分剩余保留页面将被映射为自由的保留空间,如果RAM允许,应用程序可以提交这些保留页。Non-XIP DLLs 就是不能被在ROM中现场执行的DLL将被从上至下载入到32MB的地址空间。Non-XIP DLLs 包括那些被压缩存储在ROM中的DLL。被压缩的ROM 中的文件在被载入到RAM中执行前必须先解压缩。
       被保留给XIP DLLs的32MB 应用程序地址空间的较高位置。Windows CE 映射这些XIP DLLs的代码进入这个空间(译者注:即较高空间),而可读写段被映射进较低位置。从Windows CE 4.2开始,在ROM中的纯资源的DLL将被载入到应用程序64MB空间之外的虚拟内存空间。
 
脚注
在PocketPC这样的移动式系统中,当用户按下关闭按钮时系统将不会被真正的关闭,系统进入一种低功耗的挂起状态。 
 
内存分配的不同类型
       一个Windows CE 应用程序有许多不同的内存分配方式。在内存食物链的底端是Virtualxxx 函数,它们直接保留,提交和释放(free)虚拟内存页。接下来的是堆(heap) API。堆是系统为应用程序保留的内存区域。堆有两种风味:当应用程序启动时自动默认分配的本地堆(local heap),以及能够由程序手动创建的分离堆(separate heap)。在堆API之后是静态数据,数据块是被编译器定义好的或者由程序手动创建的。最后,我们来看栈,这是程序为函数存储变量的区域。
       一个Windows CE不支持的Win32 内存API是全局堆(global heap)。全局堆API包括Global­Alloc,GlobalFree和GlobalRealloc,将不会出现在Windows CE中(译者注:很奇怪,我在Windows CE 中仍然可以使用这几个API,并且工作正常,好像Microsoft并没有把它们完全去掉)。全局堆只是从Windows 3.x的Win16 时期继承而来。在Win32中,全部和本地的堆很类似,全局内存一个独特用法是,为剪贴板的数据分配内存,在Windows CE中已经被本地堆替代并加上了句柄。
       在Windows CE中最小化内存使用的关键是选择与内存块使用模型相匹配的恰当的内存分配策略。我将回顾一下这些内存类型然后讲述Windows CE应用程序中的最小化内存使用策略。
虚拟内存
       虚拟内存是内存类型中最基础的。系统调用虚拟内存API来为其他类型内存分配内存。包括堆和栈。虚拟内存API,包括VirtualAlloc,VirtualFree和VirtualReSize函数,这些可以直接操作应用程序虚拟内存空间的虚拟内存页面。页面可以保留,提交给物理内存,或使用这些函数释放。
分配虚拟内存
       分配和保留虚拟内存是同过这个函数完成的:
LPVOID VirtualAlloc (LPVOID lpAddress, DWORD dwSize,
                     DWORD flAllocationType,
                     DWORD flProtect);
VirtualAlloc的第一个参数是要分配内存区域的地址。当你使用VirtualAlloc来提交一块以前保留的内存块的时候,lpAddress参数可以用来识别以前保留的内存块。如果这个参数是NULL,系统将会决定分配内存区域的位置,并且围绕64-KB的范围(译者注:就是前面说提及的最小内存分配尺寸)。第二个参数是dwSize,要分配或者保留的区域的大小。这个参数以字节为单位,而不是页,系统会根据这个大小一直分配到下页的边界。
flAllocationType参数指定了分配的类型,你可以指定或者合并以下标志:MEM_COMMIT,MEM_AUTO_COMMIT,MEM_RESERVE和MEM_TOP_DOWN。MEM_COMMIT标志分配程序使用的内存,MEM_RESERVE保留虚拟地址空间以便以后提交。保留的页不能存取直到调用VirtualAlloc的时候再次指定了MEM_COMMIT标志。第三个标志,MEM_TOP_DOWN,告诉系统从最高可允许的虚拟地址开始映射应用程序。
The MEM_AUTO_COMMIT标志是唯一一个Windows CE最方便的标志,当这个参数被指定了之后,内存块立即被保留,当其中的页被第一次存取的时候,系统将自动提交该页。这允许你分配大块的虚拟内存而不需要顾及系统和实际RAM分配直到当前页被第一次使用。自动提交内存的缺点是,物理RAM需要退回当页面被第一次访问时可能不可用的页面。在这种情形下,系统将产生一个异常(exception)(译者注:可能会出现因为无法访问而出错)。
       VirtualAlloc可以通过并行多次调用提交一个区域的部分或全部来保留一个大的内存区域。多重调用提交同一块区域不会引起失败。这使得一个应用程序保留内存后可以随意提交将被写的页。当这种方式不在有效的时候,它会释放应用程序通过检测被保留页的状态看它是否在提交调用之前已经被提交。
       flProtect参数指定了被分配区域的访问保护方式。这些不同的标志被总结在下面的列表中:
PAGE_READONLY
该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访问。
PAGE_READWRITE
区域可被应用程序读写。
PAGE_EXECUTE
区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。
PAGE_EXECUTE_READ
区域包含可执行代码,应用程序可以读该区域。
PAGE_EXECUTE_READWRITE
区域包含可执行代码,应用程序可以读写该区域。
PAGE_GUARD
区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限。
PAGE_NOACCESS
任何访问该区域的操作将被拒绝。
PAGE_NOCACHE
RAM中的页映射到该区域时将不会被微处理器缓存(cached)。
PAGE_GUARD和PAGE_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便设备驱动使用直接内存访问方式(DMA)来共享内存块。
区域和页
       在我继续谈论虚拟内存API之前,我需要说明一个比较细微的差异。虚拟内存在区域内被保留是以64KB为基础的。在区域内的页面能够一页一页地被提交(译者注:前面说到在Windows CE中每页是4096字节或1024字节)。你可以直接提交一页或者几页而不是保留区域的全部页。但是对页或几页来说,直接提交的仍是以64-KB为单位(译者注:可以直到被提交的页数量足够填满64KB才真正提交),因为这个原因,最好保留一块64-KB的虚拟内存,然后提交那些需要的页到区域里。
       因为对每个进程32MB虚拟内存地址空间的限制,这就有了一个最大值 32MB/64KB-1=511,这是虚拟内存在内存溢出前能被保留的最大值。接下来,有个例子,代码段如下:
#define PAGESIZE 1024   // Assume we're on a 1-KB page machine
for (i = 0; i < 512; i++)
pMem[i] = VirtualAlloc (NULL, PAGESIZE, MEM_RESERVE │ MEM_COMMIT,PAGE_READWRITE);
代码分配512个单页的虚拟内存。甚至你系统还有一半的可用RAM,VirtualAlloc也会在完成分配前失败。因为它的运行已经超出了应用程序的虚拟地址空间。发生这种情况是因为每1-KB的块要占用64-KB的空间,接下来应用程序的代码,栈,和本地堆也要映射到同样的32-MB虚拟地址空间,可用的虚拟分配区域通常不超过475个。
     一个比较好的分配512块特殊内存的方法是这样做:
#define PAGESIZE 1024   // Assume we're on a 1-KB page machine.
// Reserve a region first.
pMemBase = VirtualAlloc (NULL, PAGESIZE * 512, MEM_RESERVE,
                         PAGE_NOACCESS);
 
for (i = 0; i < 512; i++)
    pMem[i] = VirtualAlloc (pMemBase + (i*PAGESIZE), PAGESIZE,
                            MEM_COMMIT, PAGE_READWRITE);
     代码首先保留了一块区域,页面将在以后被提交。因为区域已经被先保留了,提交页就不受64-KB限制(译者注:只有保留页最小值受64KB限制),等等,如果你系统中有512KB的可用内存,分配将会成功。
     尽管我刚才给你看的是一个人为的例子(还有比直接分配虚拟内存更好的方法来分配1-KB的内存块),这中内存分配方法验证了一个重要的不同(对于其他Windows系统)。在桌面版本的Windows中,工作中的应用程序有一个完全的2-GB的虚拟地址空间。在Windows CE中,一个程序员必须明白每个应用程序只被保留了较小的32-MB虚拟地址空间。
释放虚拟内存
       你可以通过调用VirtualFree来取消提交,或释放虚拟内存。从物理RAM页中取消提交或者取消映射,但是保持页被保留的状态。函数原型如下:
    BOOL VirtualFree (LPVOID lpAddress, DWORD dwSize,                  DWORD dwFreeType);
lpAddress参数是一个指针,指向要被释放或取消提交的虚拟内存的区域。dwSize参数指明要取消提交区域的大小,以字节为单位。如果区域要被释放,这个值必须是0,dwFreeType参数包含了操作类型标志,MEM_DECOMMIT标志指定了区域将被取消提交但是仍被保留,MEM_RELEASE标志说明区域要取消提交并且释放。
在区域中的所有的页通过VirtualFree被释放必须处在同样的情况下。更确切地说,区域中的全部页要被释放,那这些页要么都是被提交的页,要么都是被保留的页。如果有些页被提交,有些页被保留,那么VirtualFree函数调用就会失败。
改变和查询权限
       你可以通过调用VirtualProtect来修改最初通过VirtualAlloc指定的虚拟内存区域的访问权限。这个函数只能改变被提交的页的访问权限。函数的原型如下:
BOOL VirtualProtect (LPVOID lpAddress, DWORD dwSize,
                     DWORD flNewProtect, PDWORD lpflOldProtect);
开始的两个参数lpAddress和dwSize,指定了函数作用的块的大小。flNewProtect参数包含区域的新的保护标志。这些标志和我前面提到的VirtualAlloc函数使用的一样。lpflOldProtect参数指向一个DWORD,将返回旧的保护标志(译者注:如果此处为NULL或指向一个无效的变量,函数将会失败)。
当前区域的保护权限可用通过下面的调用查询:
DWORD VirtualQuery (LPCVOID lpAddress,
                    PMEMORY_BASIC_INFORMATION lpBuffer,
                    DWORD dwLength);
    lpAddress参数包含区域开始查询的地址。lpBuffer指针指向我很快就要提到的一个PMEMORY_BASIC_INFORMATION结构。第三个参数dwLength,必须包含PMEMORY_BASIC_INFORMATION结构的大小。
       PMEMORY_BASIC_INFORMATION结构被定义如下:
typedef struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress;
    PVOID AllocationBase;
    DWORD AllocationProtect;
    DWORD RegionSize;
    DWORD State;
    DWORD Protect;
    DWORD Type;
} MEMORY_BASIC_INFORMATION;
MEMORY_BASIC_INFORMATION结构的第一个字段是BaseAddress,是传递给VirtualQuery函数的一个地址。AllocationBase字段包含使用VirtualAlloc函数分配的区域的基地址,AllocationProtect字段包含区域原来被分配时的保护属性。RegionSize字段包含从传递给VirtualQuery的指针开始到一系列具有相同属性的页为结尾的区域大小(译者注:这里是从基地址开始)。State字段包含区域中页的状态-自由,保留,提交。Protect字段可以包含MEM_PRIVATE标志,指明该区域包含应用程序私有的数据;MEM_MAPPED指明该区域被映射为一个内存映射文件;MEM_IMAGE指明该区域被映射为一个EXE或DLL模块。
理解VirtualQuery最好的方式是看例子,比方说一个应用程序保留了16,384字节(在以页面大小为1-KB的机器中占16页)。系统从地址0xA0000开始保留这16-KB的块。后来应用程序从最初的区域中提交了从第2048字节(2页)开始的9216字节(9页)。图7-2显示了这个假设的情况。
图7-2被保留的区域有9页被提交
       如果一个对VirtualQuery的调用中,lpAddress指向第四页的区域(地址0xA1000),返回值如下:
BaseAddress          0xA1000
AllocationBase       0xA0000
AllocationProtect    PAGE_NOACCESS
RegionSize           0x1C00    (7,168 bytes or 7 pages)
State                MEM_COMMIT
Protect              PAGE_READWRITE
Type                 MEM_PRIVATE
BaseAddress字段包含传递给VirtualQuery的地址,值为0xA1000,在最初的区域中是第4096字节。AllocationBase字段包含最初区域的地址。当AllocationProtect设为PAGE_NOACCESS时,指明区域是最初被保留的,而不是直接提交。RegionSize字段包含传递给VirtualQuery的指针0xA1000开始,到被提交的页结束地址0xA2C00的字节数。State和Protect字段包含的标志表明当前的页状态。Type字节表明区域被应用程序分配给自己使用。

       很明显,以页为单位分配内存对应用程序来说效率是很低的。为了优化内存的使用,应用程序需要以字节为单位分配和释放内存,或者至少以每8字节为单位。系统通过堆来实现这种分配方式。使用堆可以免去处理由Windows CE支持的不同微处理器的不同页面大小。一个应用程序可以简单地在堆中分配一块内存,由系统来处理分配需要的页数。
       就像我前面提到的,堆是系统为应用程序保留的虚拟内存区域。系统提供大量的函数来在堆中分配和释放内存块,并且间隔比页要小(译者注:例如每页大小为4KB,而堆分配可以字节为单位)。当内存由应用程序的堆分配时,系统自动分配调整堆大小来满足需要,当堆中的内存块被释放时,系统会查看是否整页被释放,如果是的话,那么该页将被回收。
       不同于Windows XP,Windows CE只支持在堆中分配固定(fixed)的块。这简化了内存块在堆中的处理,但是这使得堆在分配和释放一段时间后会产生碎片。当堆里已经清空的时候,仍然会占用大量的虚拟内存页,因为系统不能在堆中内存页没有完全释放的时候回收这些页(译者注:因为堆以字节为单位,一页中可能有的块需要被释放,其他的块不需要,所以整页都不会被释放)。
       当应用程序启动的时候,每个程序都会有一个由系统创建的默认或本地堆。本地堆中的内存块,可以通过LocalAlloc,LocalFree和LocalRealloc来分配,释放和改变大小。一个应用程序也可以建立分离堆。这些堆和本地堆有着相同的属性,但是是通过一组Heapxxxx函数来管理的。
本地堆
       在默认情况下,Windows CE最初会保留192,512字节给本地堆,但是只提交被分配的页。如果应用程序在本地堆中分配了超过188KB,系统将会分配更多的空间给本地堆。增加堆大小将需要一个分离的,不连续的保留地址空间作为堆的附加空间。应用程序不应该假设本地堆被包含在一块虚拟地址空间里。因为Windows CE 的堆只支持固定的块,Windows CE执行的只是Win32本地堆函数的子集,提供必要的分配,改变大小,释放固定的本地堆内存块。
在本地堆中分配内存
       你可以通过一下调用在本地堆中分配一块内存:
HLOCAL LocalAlloc (UINT uFlags, UINT uBytes);
调用返回一个HLOCAL,这是本地内存块的句柄,但是由于内存块是固定分配的,所以返回值可以被简单地看作是一个指向块的指针。
uFlags参数描述了内存块的特征。标志由于Windows CE被限制固定分配操作,只支持以下内存:
LMEM_FIXED
在本地堆中分配一个固定内存块,因为本地堆分配已经固定,所以是多余的。
LMEM_ZEROINIT
初始化内存内容为0。
LPTR
合并LMEM_FIXED和LMEM_ZEROINIT标志。
uBytes参数指定了要分配的内存块的大小,以字节为单位。块大小要补齐,但是只针对后面8字节范围。
释放本地堆的内存
       你可以通过以下调用释放内存块:
HLOCAL LocalFree (HLOCAL hMem);
函数需要本地堆内存句柄,成功会返回NULL。如果调用失败,会返回内存块的句柄。
改变和查询本地堆内存的大小
       你可以通过调用改变本地堆的分配:
HLOCAL LocalReAlloc (HLOCAL hMem, UINT uBytes, UINT uFlag);
hMem参数是一个由LocalAlloc返回的指针(句柄)。uBytes参数是内存块的新大小。uFlag参数包含给新内存块的标志。在Windows CE中,有两个新标志与之相关,LMEM_ZEROINIT和LMEM_MOVEABLE。LMEM_ZEROINIT表示调用函数后内存块中新增加的区域被初始化为0。LMEM_MOVEABLE标志告诉Windows,当内存块增加后,没有合适的空间容纳内存块时,函数可以立即移动内存块。如果没有这个标志,当你没有合适的空间来满足需要的时候,LocalRealloc将会出现out-of-memory的错误而失败,如果你指定了LMEM_MOVEABLE标志,调用将会返回句柄(实际是指向内存块的指针)。
内存块的大小可以通过以下调用查询:
UINT LocalSize (HLOCAL hMem);
返回内存块最少需要的内存大小。像我前面提到的,Windows CE本地堆自动以8个字节来补齐(译者注:就是分配1字节要占8字节)。
分离堆
       为了避免本地堆的碎片,并且如果你要分配连续的内存块,较好的办法是建立分离堆,但将花费一定的时间。一个例子就是,文本编辑器为要编辑的文件建立多个分离堆。当文件被打开或者关闭的时候,堆随之建立和销毁。
在Windows CE下的堆和Windows XP下有着同样的API。唯一值得注意的不同是缺少HEAP_GENERATE- _EXCEPTIONS标志。在Windows XP下,该标志表示系统在分配请求不合适的时候产生一个异常。
建立一个分离堆
       你可以通过以下调用建立一个分离堆。
HANDLE HeapCreate (DWORD flOptions, DWORD dwInitialSize,
                   DWORD dwMaximumSize);
在Windows CE中,第一个参数flOptions必须为空或包含HEAP_NO_SERIALIZE标志。默认情况下,Windows堆管理程序防止一个进程中的两个线程在同意时间访问堆。这个串行参数防止系统用来跟踪堆中内存块分配的堆指针被破坏。在其他版本的Windows中,当你不需要这种保护时可以使用HEAP_NO_SERIALIZE标志。在Windows CE中,该标志是为了兼容性而提供的,所有的堆访问都是串行的(译者注:串行即非并行,只能依次访问)。
其他两个参数,dwInitialSize和dwMaximumSize,指定了最初的大小和预期的堆最大值。dwMaximumSize的值确定虚拟内存空间保留给堆多少页。如果你想让Windows来决定有多少页可以保留,你可以把这个参数设为0。默认一个堆的大小是188KB,dwInitialSize参数决定了有多少这些保留的页将被提交。如果该参数为0,表示堆将一页一页提交。
在分离堆中分配内存
       你可以通过以下调用分配内存
LPVOID HeapAlloc (HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
       注意,返回值是一个指针,而不是和LocalAlloc函数一样的句柄。分离堆总是分配固定的内存块,甚至在Windows XP和Windows Me中也是一样。第一个参数是通过HeapCreate调用返回的句柄。dwFlags参数可以是两个自说明的(self-explanatory)标志之一HEAP_NO_SERIALIZE和 HEAP_ZERO_MEMORY。最后一个参数dwBytes指定了要分配的内存块字节数。大小要和DWORD补齐。
释放分离堆中的内存
       你可以通过以下调用释放内存块:
BOOL HeapFree (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);
dwFlags参数唯一的标志是HEAP_NO_SERIALIZE,当hHeap包含堆句柄时,lpMem参数指向要释放的内存块。
改变和查询分离堆中内存的大小:
       你可以通过以下调用改变堆大小。
    LPVOID HeapReAlloc (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem,                    DWORD dwBytes);
    dwFlags参数包含三种标志的组合:HEAP_NO_SERIALIZE,HEAP_REALLOC_IN_PLACE_ONLY和HEAP_ZERO_ MEMORY。其中较新的标志是HEAP_REALLOC_IN_PLACE_ONLY,这个参数告诉堆的管理者,找不到要分配的块的空间,重分配操作失败。这个标志方便的地方在于当你有了一些指向内存数据块的指针,并且你不想改变内存块。lpMem参数是一个指向要改变大小的内存块的指针,dwBytes参数是被请求的新内存块的大小。注意,HeapReAlloc中HEAP_REALLOC_IN_PLACE_ONLY标志提供和LocalReAlloc中LMEM_MOVEABLE相反的作用。HEAP_REALLOC_IN_PLACE_ONLY防止在分离堆中对内存块默认的移动操作。而LMEM_MOVEABLE允许本地堆中对内存块的默认移动操作。如果HeapReAlloc成功,就返回一个指向内存块的指针,否则就返回NULL。除非你指定内存块不可重新定位,那么当内存块因为堆中空间不足时将不得不重定位,因此造成返回指针的值将与原来不同。
       要决定实际的内存块大小,你可以作以下调用:
DWORD HeapSize (HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);
参数就像你想象的:有堆的句柄,单选标志HEAP_NO_SERIALIZE,和指向内存块的指针。
销毁一个分离堆
       你可以通过以下调用完全释放一个堆:
BOOL HeapDestroy (HANDLE hHeap);
在堆中单个的内存块并不需要在销毁堆前释放。
最后一个是写DLL时比较有价值的函数:
HANDLE GetProcessHeap (VOID);
返回的是调用DLL时进程的本地堆的句柄。这个函数允许一个DLL在调用者进程的本地堆中分配内存。GetProcessHeap返回的句柄可以供其他堆调用使用,HeapDestroy除外。

       栈是Windows CE内存类型中最容易使用的(自行管理)。在Windows CE中的栈像其它操作系统一样,是被引用函数的临时变量存储区。操作系统也用栈来存储函数的返回地址和在异常处理中微处理器寄存器的状态。
       在系统中,Windows CE给每个线程一个分离的栈。默认情况下,系统中每个栈大小最大被限制为58KB。在一个进程中,每个分离的线程可以增加栈的大小直到58-KB的限制。
这个限制使得要我们要知道Windows CE如何对栈管理。当线程被建立的时候,Windows CE保留一个64-KB的区域给每个线程的栈。栈增加时,提交虚拟内存页是从上至下的。当栈减小时,系统将处于的低内存环境(low-memory),会回收在栈下面未使用但是仍然被提交的页。58KB的限制来源于64-KB的区域减去用来防止栈的上溢和下溢的页面数量。
       当一个应用程序建立一个新的线程时,栈的最大尺寸可以通过建立线程时CreateThread调用来指定。应用程序的主线程的栈大小可以通过应用程序被连接时的连接器开关(linker switch)来指定。同样会有一些页用作防护,但是栈的大小可以指定至1MB。注意,这个指定大小同样会被用作所有分离线程栈的默认栈大小。那就是说,如果你指定主栈为128KB,程序中所有其他的线程栈大小也限制为128KB,除非在用CreateThread建立线程时指定一个不同的大小。
       当你计划如何在应用程序中使用栈的时候,另一个要值得考虑事情的是。当应用程序调用一个需要栈空间的函数时,Windows CE会试图立即提交满足要求的当前栈之下的页面,如果没有物理RAM可用,需要栈空间的线程将会暂时停止。如果请求在短时间内得不到允许,可能产生一个异常。但是如果系统不发生异常的化,Windows CE将会最大限度释放请求的页。我将简短地说明一下低内存环境,但现在你只需要记住在的内存环境中不要尝试使用大量的栈空间。
静态数据
       C和C++应用程序有一个预先定义好的内存块,这是由应用程序被装载时自动分配的。这些块被用来存储静态分配的字符串,缓冲区和全局变量,同时也包括通过静态连接到应用程序的静态库函数中的缓冲区。这些对C程序员来说都不陌生,但是在Windows CE下,这是最后一块可以在RAM之外压缩的空间(译者注:作者的意图是尽可能压缩内存占有率)。
       Windows CE分配给应用程序两块RAM中的内存块存放静态数据,一个是可读写数据(read/write data)和只读数据(read only data)。因为这些区域是基于页分配的,所以你可以在一页的静态数据开始到下一页开始之间找到一些剩余空间。细微调整Windows CE应用程序就是要写满这些剩余的空间。如果你在静态数据区有空间,最好把一个或两个缓冲区放到静态数据区,避免动态分配缓冲区。
       另一个值得考虑的事情是你是否在写一个基于ROM的应用程序。你要把尽可能多的数据移到只读静态数据区。Windows CE不会分配只读的RAM给基于ROM的应用程序。并且,ROM页会直接映射到虚拟地址空间。这实际上就给你了一个无限制的只读空间,而且不会影响到应用程序对RAM的需求。
       确定静态数据区大小的方法是查看连接器产生的映象(map)文件。映象文件主要用于调试(debug)目的来确定函数和数据的位置。但是如果你知道查看什么地方的话,它也可以用来显示静态数据的大小。列表7-1显示了一个由Visual C++产生的示例映象文件的一部分。
列表7-1。映象文件的顶部显示了应用程序数据段的大小
memtest
Timestamp is 34ce4088 (Tue Jan 27 12:16:08 1998) Preferred load address is 00010000
 Start                  Length          Name                 Class
0001:00000000 00006100H .text                     CODE
0002:00000000 00000310H .rdata                   DATA
0002:00000310 00000014H .xdata                   DATA
0002:00000324 00000028H .idata$2                DATA
0002:0000034c 00000014H .idata$3                DATA
0002:00000360 000000f4H  .idata$4                DATA
0002:00000454 000003eeH .idata$6                DATA
0002:00000842 00000000H .edata                   DATA
0003:00000000 000000f4H  .idata$5                DATA
0003:000000f4  00000004H .CRT$XCA         DATA
0003:000000f8  00000004H .CRT$XCZ         DATA
0003:000000fc  00000004H .CRT$XIA          DATA
0003:00000100 00000004H .CRT$XIZ          DATA
0003:00000104 00000004H .CRT$XPA        DATA
0003:00000108 00000004H .CRT$XPZ        DATA
0003:0000010c 00000004H .CRT$XTA        DATA
0003:00000110 00000004H .CRT$XTZ        DATA
0003:00000114 000011e8H .data                  DATA
0003:000012fc  0000108cH  .bss                   DATA
0004:00000000 000003e8H  .pdata               DATA
0005:00000000 000000f0H   .rsrc$01           DATA
0005:000000f0  00000334H  .rsrc$02           DATA

Address                  Publics by Value           Rva+Base     Lib:Object
0001:00000000       _WinMain                   00011000 f   memtest.obj
0001:0000007c       _InitApp                      0001107c f   memtest.obj
0001:000000d4       _InitInstance                000110d4 f   memtest.obj
0001:00000164       _TermInstance             00011164 f   memtest.obj
0001:00000248       _MainWndProc           00011248 f   memtest.obj
0001:000002b0       _GetFixedEquiv           000112b0 f   memtest.obj
0001:00000350       _DoCreateMain           00011350 f   memtest.obj. 

       在列表7-1中的映象文件指出了EXE文件有五个区。区0001是文本段,包含程序中可执行的代码。区0002包含只读(read-only)静态数据。区0003包含可读写(read/write)静态数据。区0004包含调用其他DLL的固定表。最后,区0005是资源区,包含应用程序的资源,例如菜单和对话框模板。

让我们来看看.data,.bss和.rdata行。.data区包含已初始化的可读写数据。如果你这样初始化了一个全局变量:
static HINST g_hLoadlib = NULL;
g_loadlib变量将结束在.data段末尾。.bss段包含未初始化的可读写数据。一个缓冲被定义如下:
static BYTE g_ucItems[256];
以.bss段为结尾。最后一个段.rdata,包含只读数据。你使用const关键字定义的静态数据结束在.rdata段。有一个结构的例子,使我用来作消息查询表的:
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_CREATE, DoCreateMain,
    WM_SIZE, DoSizeMain,
    WM_COMMAND, DoCommandMain,
    WM_DESTROY, DoDestroyMain,
};
    .data和.bss块被折叠进0003区,如果你将第三区的所有块大小加起来,总共为0x2274,或8820字节。为和下页对齐,读写数据区将占9页,那么就有396字节未使用(译者注:1024*9-8820=396)。因此在这个例子中,把一个或者两个缓冲区放入静态数据区比较合适。只读数据段0002区,包括.rdata,占0x0842或2114字节,占3页,剩余958字节,几乎是一整页。在这种情况下,移动75字节的常量数据从只读段到可读写段将在应用程序加载时节约一页的RAM。
字符串资源
有一个经常忘记的只读区域时应用程序的资源段,像我前面在第四章提到的Windows CE的新特性有一个LoadString函数,值得再次重复。如果你调用LoadString时指向缓冲区的指针写0,函数将返回一个指向资源段中字符串的指针。例子如下:
LPCTSTR pString;
 
pString  = (LPCTSTR)LoadString (hInst, ID_STRING, NULL, 0)
返回的字符串是只读的,但是它允许你应用字符串而不需要分配一个缓冲给字符串。这里警告一下,字符串不能以0结尾,除非你在资源编译器命令行中加了-n开关。不管如何,单词必须是先于字符串资源长度(译者注:作者此处意思可能是说长度包含字符串资源的长度)。
选择适当的内存类型
       现在我们已经看过了不同类型的内存,是时候来考虑最好的使用办法了。对大的内存块来说,直接分配虚拟内存是最好的办法,一个应用程序可以保留很多的地址空间(直到应用程序32MB的限制)但是只能在一个时间提交必须的页。直接分配虚拟内存是最灵活的内存分配方式,它把页间隔(granularity)的负担以及对保留页和提交页都交由我们负担。
       本地堆是很方便的,它不需要创建并且会自动随着需求扩大。但碎片是这里的问题。但是要考虑到Pocket PC的应用程序可能会运行几星期或几个月的时间。在Pocket PC上没有关闭电源的按钮,只有挂起命令。因此,你考虑内存碎片的时候不要假设用户会打开应用程序,改变一个项目,然后关闭它。用户可能打开程序然后让它一直运行以至于程序就像一个快捷方式(quick click away)。
       分离堆的优点是当你不用时可以销毁,把碎片消灭在萌芽状态。有一点不好的就是分离堆需要手动创建和销毁。
       静态数据区是放置一两个缓冲区的好地方,因为页面是已经被分配的。管理静态数据的关键是使静态数据段大小尽可能地接近,但是要超过你目标处理器的页面的大小。当常量数据在只读段中,往往较好的办法是把它移到可读写段中。但当应用程序被烧到ROM中时,你不要这么做。常量数据越多会比较好,因为它不占RAM。只读段方便应用程序从对象存储区启动,因为只读页能通过操作系统丢弃和重载。
       栈用起来比较简单而且到处存在。唯一要考虑的是栈的最大尺寸和在的内存环境下扩大栈的问题。确定你的应用程序在关闭的时候不需要大量栈空间。当程序被关闭时,如果系统挂起你程序中的一个线程,用户可能会丢失数据。这会使顾客不满意。
低内存环境
       当系统运行在一个低RAM环境中,应用程序将调整并最小化它们的内存使用。Windows CE运行在一个几乎永久的低内存环境中。Pocket PC被特意设计为运行低内存环境。在Pocket PC中的应用程序没有关闭按钮,当系统需要更多内存时,外壳(shell)自动关闭这些程序。正因为如此,Windows CE有许多方法来管理运行在低内存系统中的程序。
WM_HIBERNATE 消息
       Windows CE第一个最明显的变化时是增加了WM_HIBERNATE消息。Windows CE的shell发送消息给最顶层的有WS_OVERLAPPED式样(那就是说,既没有WS_POPUP也没有WS_CHILD式样)和WS_VISIBLE式样的窗口。这些限制将允许大多数程序至少有一个窗口可以接受WM_HIBERNATE消息。有一个例外就是,当应用程序不能真正结束程序而只是简单隐藏所有窗口。这种方式允许应用程序可以快速启动,因为它下次只是显示窗口。但是这就意味着,当用户想关闭它们的时候仍然占据着RAM。这对程序设计来说是正确的,但是不应用在Windows CE中,这种方式会造成程序被隐藏时总处在冬眠(hibernate)模式,因为它们永远接收不到WM_HIBERNATE消息。
       Shell发送WM_HIBERNATE消息给最顶层的窗口在Z轴相反的位置(reverse Z-order)直到内存被释放,使可用内存超过系统预先的限制。当应用程序接收到一个WM_HIBERNATE消息,它会尽可能减少内存占有程度。这包括释放被缓冲(cached)的数据;释放GDI对象,例如字体,位图和画刷;并销毁任何窗口控件。从本质上来说,应用程序将会减少内存到维持它内部状态的最小值。
       如果发送WM_HIBERNATE消息给后台的应用程序不能释放足够的内存以便使系统离开内存被限制的状态。WM_HIBERNATE消息将会发送给前台程序。如果你正在冬眠的程序开始销毁窗口的控件,你必须确保它不是前台的程序,控件消失不会给用户带来兴奋的感觉而是困惑。
内存限度
       Windows CE监视系统自由的RAM,并对越来越少的RAM作出响应。当很少内存可用时,Windows CE首先发送WM_HIBERNATE消息,接下来会限制可能的内存分配。下面的两个表显示了Explorer shell和Pocket PC引发的低内存事件的自由内存级别。Windows CE定义了是个内存状态:normal,limited,low和critical。系统的内存状态依赖于整个系统有多少内存可用。这些限制都比4-KB页要高,因为系统具有内存最小分配限制,就像7-1和7-2的表。
表7-1 Explorer Shell的内存限度
事件                           自由内存1024-Page Size  自由内存4096-Page Size    注解
Limited-memory state  128 KB                              160 KB                               发送WM_HIBERNATE 消息给in reverse Z-order的应用程序。
                                                                                                                         释放栈空间并回收利用。
Low-memory state      64 KB                                96 KB                                 限制虚拟内存分配为16 KB。 显示Low-memory对话框。
Critical-memory state  16 KB                                48 KB                                 限制虚拟内存分配为8KB。


表7-2 Pocket PC的内存限度
事件                           自由内存1024-Page Size   自由内存4096-Page Size    注解
Hibernate threshold      200 KB                               224 KB                                发送 WM_HIBERNATE 消息给in reverse Z-order的应用程序。
Limited-memory state  128 KB                               160 KB                                 开始关闭在 reverse Z-order上的应用程序。释放栈空间并回收利用。
Low-memory state       64 KB                                 96 KB                                  限制虚拟内存分配为16 KB。
Critical-memory state   16 KB                                 48 KB                                  限制虚拟内存分配为8 KB。


 
       这些内存状态的影响是共享剩余的财富。首先,WM_HIBERNATE消息被发送给应用程序,并请求减少它们的内存占有率,当应用程序被发送了一个WM_HIBERNATE消息后,系统将检测内存级别,确认是否可用内存在限度之上,如果可用内存不足,WM_HIBERNATE消息将被发送给下一个程序。这会持续到所有程序被发送了WM_HIBERNATE消息。
       Exlporer shell和Pocket PC的低内存策略在这点上有区别。如果Explorer shell运行时,系统会显示OOM(out of memory)对话框,并请用户确认是否关闭一个应用程序或把对象存储区的RAM重新划分给程序内存。如果用户选择了其中之一,仍然没有足够的内存,out of memory对话框将会再次出现,这个过程会重复,直到H/PC有足够的在限度之上的内存。
       对Pocket PC来说,操作稍微有些不同。Pocket PC shell自动开始关闭最近最少使用的应用程序,而不询问用户。如果关闭除了前台程序和shell之外的所有程序,仍然没有足够内存,系统将会使用其他的技术来从栈开始清理自由的页,并限制虚拟内存分配。
       如果在任何一个系统上,应用程序被请求关闭却没