关于作者

用户名:bossbear
笔名:bossbear
地区: 北京-北京

日历  

快速登录

+ 用户名:
+ 密 码:

我的博采 我的论坛 我的RSS

文章索引

在线留言



最新评论

访问统计:48
文章个数:3
评论个数:0
留言条数:0



Powered by BlogDriver 2.1

bossbear的博客

 

you will do it if you want, if you want day and night, you will do it, until you do it actully.

文章

Windows CE开发之起步停车(转载自aawolf的blog)

Windows CE开发之起步停车

 

       最近一段时间,移动设备开发越来越多的成为了程序员社区的话题。移动设备主要包括智能手机和PDA,是嵌入式开发中很重要的一个方向。在智能手机领域被大多数手机厂商支持的J2ME无疑是领头羊,随着多普达推出了515手机,基于微软CE平台的SmartPhone也逐渐成为关注焦点。一直不温不火的PDA市场,也在行业应用领域有所收获,Pocket PC由于其开发与Windows平台的一致性而得到了开发人员的青睐。

       在长期关注程序员论坛的过程中,我发现由于Windows CE开发的独特性,加之多个版本并存、缺乏中文参考资料,所以论坛上充斥着大量相同的入门问题。我希望在这里能够为刚转入Windows CE开发的程序员明晰一些概念,将现有的Windows CE版本与开发工具之间的关系给大家解释清楚,在最后介绍一下,关于Windows CE开发的中文书籍和国外网站的情况。

Windows CE与平台开发

       Windows CE是微软为嵌入式设备打造的操作系统,而嵌入式设备可谓多种多样,这就要求CE操作系统必须是可定制的,所以微软将Windows CE设计为模块化的操作系统。说简单点,我们可以把Windows CE想像成一盒积木,你可以用积木搭建出任何物体,但不一定要把所有的积木都用上。

Windows CE搭建出来的物体就是平台,是适应某种有固定标准的嵌入式设备的操作系统子集,最著名的平台就是Pocket PC了,是提供给没有键盘的掌上电脑使用的平台。由于平台和硬件的一致性,所以有时候我们也用平台的名称来称呼整个系统——硬件与操作系统的总和。

我们也可以自己开发平台,开发工具是微软提供的Platform BuilderPlatform Builder的版本号是和Windows CE的版本号一致的。

更多程序员关心的是应用程序的开发,而应用程序开发是针对特定平台的,我们在开发之前必须安装目标平台的SDK,才能够开发出适应目标平台的开发工具。

Windows CE开发环境综述

初学者另外一个比较糊涂的概念是版本的问题,现在市面上能够见到Windows CE的两代产品,它们的内核分别基于Windows CE 3.0Windows CE.NET(即4.0)。

微软将今年刚面世的Pocket PC 2003Smart Phone 2003统称为Windows Mobile 2003,我们大多数时候还是习惯地沿用老称谓。

而市面上经常见到的Pocket PC 2002是基于Windows CE 3.0的平台,而Pocket PC 2003则是基于Windows CE.NET的平台,需要注意的是,Pocket PC 2003的内核是Windows CE.NET 4.2。而SmartPhone2003也是基于Windows CE.NET的。SmartPhone的最初版本是2002,基于Windows CE 3.0的,但是微软没有推出SmartPhone2002的中文版。

 

清晰了平台与CE之间的关系,解释平台与开发工具之间的关系就很容易了。微软提供给应用程序开发者的工具包括:Embedded Visual Tools 3.0,其中包括Embedded Visual C++ 3.0Embedded Visual Basic 3.0Embedded Visual C++ 4.0Visual Studio.NET

       开发工具的版本号是与Windows CE的版本号对应的。EVC3.0EVB3.0是用来开发基于Windows CE 3.0平台的应用程序的,比较常见的平台有:Pocket PC 2002Pocket PC 2000Palm-size PCHPC。而EVC4.0是用来开发Windows CE.NET平台的程序的,主要包括Pocket PC 2003SmartPhone 2003

Visual Studio.NET针对嵌入式设备开发需要SDE的支持,而VS.NET 2003中包括了SDE,不需要另外安装。Visual Studio.NET开发的程序需要目标平台支持.NET Compact Framework。现在支持.NET Compact Framework的平台有Pocket PC 2002Pocket PC 2003。这里需要注意的是SmartPhone 2003是不支持.NET Compact Framework的。

EVB开发入门

       微软已经宣布EVB不再支持Windows CE.NET,所以EVB的最终版本是3.0。但由于EVB的易上手性和快速开发的特点,在VS.NET横空出世之前,它成为Windows CE平台上快速开发的不二之选。现在EVB仍然适合Windows CE 3.0平台上小型应用程序的快速开发。如果您不是专职的Windows CE程序员,而只是需要在Windows CE平台上开发整个系统的一部分,那么EVB可以让您用很短的时间开发出您想要的程序。

       EVB的开发环境的搭建也是十分简单,您可以从微软的网站上下载EVT 2002,其中包含了EVC 3.0EVB 3.0Pocket PC 2002 SDKSmartPhone 2002 SDK。按照提示将EVBPocket PC 2002 SDK安装好后就可以进行开发了。SDK中包含模拟器,在没有实际设备的情况下,可以利用模拟器来调试程序。

这里需要注意的是,开发环境和模拟器之间是通过网络连接协议进行通讯的,所以开发所用的计算机上必须有一个活动的网络连接。如果没有,可以安装微软的虚拟网卡,具体过程请参照我在CSDN专栏里的文章《在无网络的环境下使用模拟器》。

 

      

       EVB的开发环境与VB类似,因为Windows CE应用程序需要在模拟器或者实际设备上调试,所以我们必须选择程序的输出目标。如果您选择了Emulation,在您按下运行(或F5)后,EVB将自动启动模拟器,并把程序下载到模拟器中。

 

       由于新的Windows CE.NET将不再支持EVB,微软建议EVB程序员使用VB.NET开发新的程序,而对于原有的EVB程序也给出了迁移路径,关于这方面的论述,您可以参考MSDN的文章《Moving from eMbedded Visual Basic to Visual Basic .NET》。

EVC开发入门

       无论是Win32平台还是WinCE平台,Visual C++都是一个强大的开发工具。而EVC也是WinCE上的主流开发工具。EVC支持MFC类库的子集,可以给开发者提供最强大的支持,也使Win32平台上的VC程序员可以很容易地迁移到WinCE平台上。但由于MFC类库需要一个DLL,所以对某些存储空间有限的嵌入式设备来说,这是个很大的负担,所以SmartPhone就不支持MFC

       说这么多,让我们来创建一个EVC的工程。是不是和VC很像,需要提醒大家注意的是,由于嵌入式设备支持的CPU种类很多,我们在选择创建工程类型的同时,也要把该工程所支持的CPU类型选择好。创建工程的过程和VC是一样的。当然不同的平台支持的工程类型是不同的,比如Pocket PC 2003有支持MFCAPI的两种工程,而SmartPhone 2003则只有支持API的一种工程。

      

 

       EVC中比VC环境中多了一行下拉菜单的选项,分别用来选择:工程、SDKCPU类型和输出设备。以Pocket PC为例,在实际设备上调试应该选择Win32(WCE ARMV4)Debug ,而在模拟器上则需要选择Win32(WCE emulator)Debug

VS.net开发入门

       又来到我们的.NET时间了,我怎么说又?最近大家都被JAVA.NET搞得头昏脑胀了吧?不管大家怎么吵,.NET Compact Framework对于手中缺少开发利器的嵌入式程序员无疑是一大福音。Visual Studio .NET 2003完全支持对移动设备的开发,好了,让我们开始一段奇幻的.NET之旅吧。

打开VS.net 2003,选File - New – Project,就打开了上面的界面。让我们来建立一个Visual C#的工程,然后选择Smart Device Application,然后OK

你在这里要选择目标设备:Pocket PCSmartPhoneWindows CE(指的是其他平台),下面则是选择创建的工程类型,我们选择“Windows Application”,左边是选择的平台所支持的模拟器。最后点击OK,我们就可以进入VS.NET的主界面了。

       选择输出设备的情况和EVB十分类似,只需要选择输出设备,而不用选择CPU类型。当然了,因为.NET是运行在虚拟机上的了。在CPU类型众多的嵌入式领域,.NETJAVA才能真正发挥自己的强项。

 

       当然,我们也可以选择VB.NET作为开发智能设备的语言,情况和C#完全一样。目前智能设备开发只支持C# VB.NET。爱好C++的程序员可能还要等上一段时间。

- 作者: bossbear 2005年04月23日, 星期六 14:19  回复(0) |  引用(0) 加入博采

追根究底,剖析MFC六大关键技术(第一部分) (转载)
     

追根究底,剖析MFC六大关键技术
题外话:
我并不认为MFC减轻了程序员们的负担,MFC出现的目的虽然似乎是为了让程序员不用懂 得太多就可以进行视窗编程,但本人在MFC里徘徊了很久很久(因为那时没有书本详细介绍MFC的原理),毫无收获。可能朋友们会说,怎么一定要了解MFC 的具体呢,"黑箱"作业不行吗?这不是微软的初衷吗?
不行!!!如果这样,我宁愿永远不选择MFC!在学电脑之前,本人学习的东西大都与艺术不无关系,小学时参加过全国书画比赛获银奖。儿时的爱好就是在一张纸上随心所欲地画画!MFC"黑箱"就象一幅硕大的抽象画(抽象到你不能理解),它用铅笔勾画好线条,然后请你填颜色。
我们怎么能忍受"黑箱"作业?我们选择C++,就是因为它够自由,够艺术,我们可以在此放飞幻想。所以,我们要攻克MFC。
伟大孙老师在剖析MFC的时候虽然尽心尽力,但可能由于篇幅所限,说得并不大清楚(我相信许多学员都有这方面的感受)。在此,我突发奇想,想与大家一同分享一下著名的MFC六大关键技术。
从什么地方开始讲起好呢?我觉得回到最初摸索MFC的时候,从基本谈起最好。
因为我知道,一个走过来程序员,总是忘记了当初自己是怎么走过来的,忘记了一个学员最想知道的是什么。一个小小的问题(一两句话就可以解释的),足学以令手无寸铁的学员头大半个月,所以,我努力回忆当初是怎么让自己豁然开朗的。
转入正题:
MFC的六大关键技术包括:
MFC程序的初始化过程。
运行时类型识别(RTTI)。
动态创建。
永久保存。
消息映射。
消息传递。
MFC程序的初始化过程
1、设计一个简单完整MFC程序,产生一个窗口。当然这不能让AppWizard自动为我们生成。我们可以在Win32 Application工程下面那样写:
#include <afxwin.h>
class MyApp : public CWinApp
{
public:
 BOOL InitInstance()  //②程序入点
 {
  CFrameWnd *Frame=new CFrameWnd();//构造框架
  m_pMainWnd=Frame; //将m_pMainWnd设定为Frame;
  Frame->Create(NULL,"最简单的窗口");//建立框架
  Frame->ShowWindow(SW_SHOW);  //显示框架
  return true;         //返回
 }
};
MyApp theApp;  //①建立应用程序。

设定链接MFC库,运行,即可看见一个窗口。

从上面,大家可以看到建立一个MFC窗口很容易,只用两步:一是从CWinApp派生一个应用程序类(这里是MyApp),,然后建立应用程序对象(theApp),就可以产生一个自己需要的窗口(即需要什么样就在InitInstance()里创建就行了)。
整个程序,就改写一个InitInstance()函数,创建那么一个对象(theApp),就是一个完整的窗口程序。这就是"黑箱"作业的魅力!!!!
在 我们正想为微软鼓掌的时候,我们突然觉得心里空荡荡的,我们想知道微软帮我们做了什么事情,而我们想编自己的程序时又需要做什么事情,那怕在上面几行的程 序里面,我们还有不清楚的地方,比如,干嘛有一个m_pMainWnd指针变量,它从哪里来,又要到哪里去呢?想一想在DOS下编程是多么美妙的一件事 呵,我们需要什么变量,就声明什么变量,需要什么样的函数,就编写什么样的函数,或者引用函数库......但是现在我们怎么办!!!
我们可以逆向思维一 下,MFC要达到这种效果,它是怎么做的呢?首先我们要弄明白,VC不是一种语言,它就象我们学c语言的时候的一个类似记事本的编辑器(请原谅我的不贴切 的比喻),所以,在VC里面我们用的是C++语言编程,C++才是根本(初学者总是以为VC是一门什么新的什么语言,一门比C++先进很多的复杂语言, 汗)。说了那么多,我想用一句简单的话概括"MFC‘黑箱'就是帮助我们插入了‘C++代码'的东西"。
既然MFC黑箱帮我们插入了代码,那么大家想想它会帮我们插入什么样的代码呢?他会帮我们插入求解一元二次方程的代码吗?当然不会,所以它插入的实际上是每次编写窗口程序必须的,通用的代码。
再往下想,什么才是通用的呢?我们每次视窗编程都要写WinMain()函数,都要有注册窗口,产生窗口,消息循环,回调函数......即然每次都要的东西,就让它们从我们眼前消失,让MFC帮忙写入!
要知道MFC初始化过程,大家当然可以跟踪执行程序。孙老师的第三课跟踪了很长一段时间,我相信大家都有点晕头转向。本人觉得那怕你理解了MFC代码,也很容易让人找不着北,我们完全不懂的时候,在成千上万行程序的迷宫中如何能找到出口?
我们要换一种方法,不如就来重新编写个MFC库吧,哗!大家不要笑,小心你的大牙,我不是疯子(虽然疯子也说自己不疯)。我们要写的就是最简单的MFC类库,就是把MFC宏观上的,理论上的东西写出来。我们要用最简化的代码,简化到刚好能运行。
既然,我们这一节写的是MFC程序的初始化过程,上面我们还有了一个可执行的MFC程序。程序中只是用了两个MFC类,一个是CWinApp,另一个是 CFrameWnd。当然,还有很多同样重要MFC类如视图类,文档类等等。但在上面的程序可以不用到,所以暂时省去了它(总之是为了简单)。
好,现在开始写MFC类库吧......唉,面前又有一个大难题,就是让大家背一下MFC层次结构图。天,那张鱼网怎么记得住,但既然我们要理解他,总得知道它是从那里派生出来的吧。
考虑到大家都很辛苦,那我们看一下上面两个类的父子关系(箭头代表派生):
CObject->CCmdTarget->CWinThread->CWinApp->自己的重写了InitInstance()的应用程序类。
CObject(同上)->CCmdTarget(同上)->CWnd->CFrameWnd

看到层次关系图之后,终于可以开始写MFC类库了。按照上面层次结构,我们可以写以下六个类(为了直观,省去了构造函数和析构函数)。
/////////////////////////////////////////////////////////
class CObiect{};//MFC类的基类。
class CCmdTarget : public CObject{};
------------------------------------------------
class CWinThread : public CCmdTarget{};
class CWinApp : public CWinThread{};
------------------------------------------------
class CWnd : public CCmdTarget{};
class CFrameWnd : public CWnd{};
/////////////////////////////////////////////////////////
大 家再想一下,在上面的类里面,应该有什么?大家马上会想到,CWinApp类或者它的基类CCmdTarget里面应该有一个虚函数virtual BOOL InitInstance(),是的,因为那里是程序的入口点,初始化程序的地方,那自然少不了的。可能有些朋友会说,反正InitInstance() 在派生类中一定要重载,我不在CCmdTarget或CWinApp类里定义,留待CWinApp的派生类去增加这个函数可不可以。扯到这个问题可能有点 越说越远,但我想信C++的朋友对虚函数应该是没有太多的问题的。总的来说,作为程序员如果清楚知道基类的某个函数要被派生类用到,那定义为虚函数要方便 很多。也有很多朋友问,C++为什么不自动把基类的所有函数定义为虚函数呢,这样可以省了很多麻烦,这样所有函数都遵照派生类有定义的函数就调用派生类 的,没定义的就调用基类的,不用写virtual的麻烦多好!其实,很多面向对象的语言都这样做了。但定义一个虚函数要生成一个虚函数表,要占用系统空 间,虚函数越多,表就越大,有时得不偿失!这里哆嗦几句,是因为往后要说明的消息映射中大家更加会体验到这一点,好了,就此打往。
上面我们自己解决了一个问题,就是在CCmdTarge写一个virtual BOOL InitInstance()。
大家再下想,我们还要我们MFC"隐藏"更多的东西:WinMain()函数,设计窗口类,窗口注册,消息循环,回调函数......我们马上想到封装想封装他们。大家似乎隐约地感觉到封装WinMain()不容易, 觉得WinMain()是一个特殊的函数,许多时候它代表了一个程序的起始和终结。所以在以前写程序的时候,我们写程序习惯从WinMain()的左大括写起,到右大括弧返回、结束程序。
我们换一个角度去想,有什么东西可以拿到WinMain()外面去做,许多初学者们,总觉得WinMain()函数天大的函数,什么函数都好象要在它里面才能真正运行。其实这样了解很片面,甚至错误。我们可以写一个这样的C++程序:
////////////////////////////////////////////////////
#include <iostream.h>
class test{
public:
 test(){cout<<"请改变你对main()函数的看法!"<<endl;}
};
test test1;
/**************************/
void main(){}
////////////////////////////////////////////////////
在 上面的程序里,入口的main()函数表面上什么也不做,但程序执行了(注:实际入口函数做了一些我们可以不了解的事情),并输出了一句话(注:全局对象 比main()首先运行)。现在大家可以知道我们的WinMain()函数可以什么都不做,程序依然可以运行,但没有这个入口函数程序会报错。
那么WinMain()函数会放哪个类上面呢,请看下面程序:
#include <afxwin.h>
class MyApp : public CWinApp
{
public:
 BOOL InitInstance()  //②程序入点
 {
  AfxMessageBox("程序依然可以运行!");
  return true;
 }
};

MyApp theApp;  //①建立应用程序。

大家可以看到,我并没有构造框架,而程序却可以运行了——弹出一个对话框(如果没有WinMain()函数程序会报错)。上面我这样写还是为了直观起见,其实我们只要写两行程序:
#include <afxwin.h>
CWinApp theApp;  
//整个程序只构造一个CWinApp类对象,任可事情,程序就可以运行!
所 以说,只要我们构造了CWinApp对象,就可以执行WinMain()函数。我们马上相信WinMain()函数是在CWinApp类或它的基类中,而 不是在其他类中。其实这种看法是错误的,我们知道编写C++程序的时候,不可能让你在一个类中包含入口函数,WinMain()是由系统调用,跟我们的平 时程序自身调用的函数有着本质的区别。我们可以暂时简单想象成,当CWinApp对象构造完的时候,WinMain()跟着执行。
现在大家明白了,大部分的"通用代码(我们想封装隐藏的东西)"都可以放到CWinApp类中,那么它又是怎样运行起来的呢?为什么构造了CWinApp类对象就"自动"执行那么多东西。
大家再仔细想一下,CWinApp类对象构造之后,它会"自动"执行自己的构造函数。那么我们可以把想要"自动"执行的代码放到CWinApp类的构造函数中。
那么CWinApp类可能打算这样设计(先不计较正确与否):
class CWinApp : public CWinThead{
public:
virtual BOOL InitInstance(); //解释过的程序的入点
  CWinApp ::CWinApp(){   //构造函数
   ////////////////////////
   WinMain();   //这个是大家一眼看出的错误
   Create();    //设计、创建、更新显示窗口
   Run();     //消息循环
   //////////////////////
}
};
写 完后,大家又马上感觉到似乎不对,WinMain()函数在这里好象真的一点用处都没有,并且能这样被调用吗(请允许我把手按在圣经上声明一下: WinMain()不是普通的函数,它要肩负着初始化应用程序,包括全局变量的初始化,是由系统而不是程序本身调用的,WinMain()返回之后,程序 就结束了,进程撤消)。再看Create()函数,它能确定设计什么样的窗口,创建什么样的窗口吗?如果能在CWinApp的构造函数里确定的话,我们以 后设计MFC程序时窗口就一个样,变得写程序变有必要。再看Run()函数,它能在WinMain()函数外面运行吗?
回过头来,我们可以让WinMain()函数一条语句都不包含吗?不可以,我们看一下WinMain() 函数的四个参数:
WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
其中第一个参数指向一个实例句柄,我们在设计WNDCLASS的时候一定要指定实例句柄。我们窗口编程,肯定要设计窗口类。所以,WinMain()再简单也要这样写:
int WinMain(HINSTANCE hinst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{  hInstance=hinst }
既然实例句柄要等到程序开始执行才能知道,那么我们用于创建窗口的Create()函数也要在WinMain()内部才能执行[因为如果等到WinMain()执行完毕后,程序结束,进程撤消,当然Create()也不可能创建窗口]
那么Run()(消息循环)放在那里执行好呢?众所周知,消息循环就是相同的那么几句代码,但我们也不要企图把它放在WinMain()函数之外执行。
所以我们在WinMain()函数里面,我们程序要象以下这样写
WinMain(......)
{
  ......窗口类对象执行创建窗口函数......
  ......程序类对象执行消息循环函数......
}
对于WinMain()的问题,得总结一下,我们封装的时候是不可以把它封装到CWinApp类里面,但由于WinMain()的不变性(或者说有规律可循),MFC完全有能力在我们构造CWinApp类对象的时候,帮我们完成那几行代码。
转了一个大圈,我们仿佛又回到了SDK编程的开始。但现在我们现在能清楚地知道,表面上MFC与SDK编程截然不同,但实质上MFC只是用类的形式封装了 SDK函数,封装之后,我们在WinMain()函数中只需要几行代码,就可以完成一个窗口程序。我们也由此知道了应如何去封装应用程序类(CWinApp)和主框架窗口类(CFrameWnd)。下面把上开始设计这两个类。
为 了简单起见,我们忽略这两个类的基类和派生类的编写,可能大家会认为这是一种很不负责任的做法,但本人觉得这既可减轻负担,又免了大家在各类之间穿来穿 去,更好理解一些(我们在关键的地方作注明)。还有,我把全部代码写在同一个文件中,让大家看起来不用那么吃力,但这是最不提倡的写代码方法,大家不要学 哦!

#include <windows.h>
HINSTANCE hInstance;

class CFrameWnd 
{
 HWND hwnd;
public:
 CFrameWnd();   //也可以在这里调用Create()
 virtual ~CFrameWnd();
 int Create();    //类就留意这一个函数就行了!
 BOOL ShowWnd();
};
class CWinApp1 
{
public:
 CFrameWnd* m_pMainWnd;//在真正的MFC里面
//它是CWnd指针,但这里由于不写CWnd类
//只要把它写成CFrameWnd指针
 CWinApp1* m_pCurrentWinApp;//指向应用程序对象本身
 CWinApp1();
 virtual ~CWinApp1();
 virtual BOOL InitInstance();//MFC原本是必须重载的函数,最重要的函数!!!!
 virtual BOOL Run();//消息循环
};
CFrameWnd::CFrameWnd(){}
CFrameWnd::~CFrameWnd(){}

int CFrameWnd::Create()   //封装创建窗口代码
{
 WNDCLASS wndcls;
 wndcls.style=0;
 wndcls.cbClsExtra=0;
 wndcls.cbWndExtra=0;
 wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
 wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
 wndcls.hIcon=LoadIcon(NULL,IDC_ARROW);
 wndcls.hInstance=hInstance;
 wndcls.lpfnWndProc=DefWindowProc;//默认窗口过程函数。
//大家可以想象成MFC通用的窗口过程。
 wndcls.lpszClassName="窗口类名";
 wndcls.lpszMenuName=NULL;
 RegisterClass(&wndcls);

 hwnd=CreateWindow("窗口类名","窗口实例标题名",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);
  return 0;
}

BOOL CFrameWnd::ShowWnd()//显示更新窗口
{
 ShowWindow(hwnd,SW_SHOWNORMAL);
 UpdateWindow(hwnd);
 return 0;
}

/////////////
CWinApp1::CWinApp1()
{
 m_pCurrentWinApp=this;
}
CWinApp1::~CWinApp1(){}
//以下为InitInstance()函数,MFC中要为CWinApp的派生类改写,
//这里为了方便理解,把它放在CWinApp类里面完成!
//你只要记住真正的MFC在派生类改写此函数就行了。
BOOL CWinApp1::InitInstance()
{
 m_pMainWnd=new CFrameWnd;
 m_pMainWnd->Create();
 m_pMainWnd->ShowWnd();
 return 0;
}

BOOL CWinApp1::Run()//////////////////////封装消息循环
{
 MSG msg;
 while(GetMessage(&msg,NULL,0,0))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 return 0;
} //////////////////////////////////////////////////////封装消息循环

CWinApp1 theApp;   //应用程序对象(全局)

int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hPrevInstance,   LPSTR lpCmdLine,  int nCmdShow)
{
 hInstance=hinst;
 CWinApp1* pApp=theApp.m_pCurrentWinApp;
//真正的MFC要写一个全局函数AfxGetApp,以获取CWinApp指针。
 pApp->InitInstance();
 pApp->Run();
 return 0;
}

代码那么长,实际上只是写了三个函数,一是CFrameWnd类的Create(),第二个是CWinApp类的InitInstance()和 Run()。在此特别要说明的是InitInstance(),真正的MFC中,那是我们跟据自己构造窗口的需要,自己改写这个函数。
大 家可以看到,封装了上面两个类以后,在入口函数WinMain中就写几行代码,就可以产生一个窗口程序。在MFC中,因为WinMain函数就是固定的那 么几行代码,所以MFC绝对可以帮我们自动完成(MFC的特长就是帮我们完成有规律的代码),所以我们创造MFC应用程序的时候,看不到WinMain函 数。
写到这里,MFC六大关键技术之一:MFC程序的初始化过程(模拟),就差不多写完了。回头看一下,居然写了八千多字,原本以为写完六大关键技术也不用写那么多字,现在还觉得庆幸的是不把文档、视类牵连进去,否则更不知写到何时。
还有五大关键技术没有写,我还应该写下去吗?上面写了八千多字,都是我一个字一个字地敲进去,每个例子都是自己生硬地想出来。用了十多个小时,换来的可能更多是论坛中朋友们的漫骂,讥讽!
但 我觉得还是值得的,我一向认为VC没有敌人,只有朋友,放眼周围,发觉学VC的朋友越来越少,也没有发现多少招收VC程序员的地方。记得读大学的时候,我 遇到一位搞美术的师兄,本来同行如敌国(我曾经搞过美术)。师兄美术功底很好,但他从来没有在学校获过美术一等奖,原因评奖的不懂得欣赏他的作品。我的出 现,他深刻地体会到了:多一个朋友,会少一分孤独!有时觉得学习VC的朋友是英雄(但我不是英雄,因为我学VC多年来无甚突破),是值得尊敬的人物,大家 交流一下,纠正一下自己的错误,真是一种福份......
本人的QQ:14653353,E_mail:liyi268@163.net (这是两男两女共用的邮箱,可能不一定是我收到,若注明小李先生收另当别论)欢迎联系。



- 作者: bossbear 2005年04月20日, 星期三 16:07  回复(0) |  引用(0) 加入博采

Microsoft Windows CE 编程的十点忠告(转载)
                                                     Microsoft Windows CE 编程的十点忠告



     最近两周我们花了大部分时间将已有的应用程序移植到Microsoft Windows CE中。一般说来,这个计划不是太难。我们起步于Microsoft Win32代码,当然 Windows CE是基于Win32应用程序接口(API)的。有利的是,我们的应用程序(即Raima 数据管理器)有方便的使用接口,并包含一个大约由150个子函数组成的库,这些函数都是由C语言写成,可以用来创建、管理和访问数据库。

  按建立应用程序的方式来说,我们原以为将它移植到Windows CE中是一项相对简单的C语言编程练习。然而,我们不久便遇到好些困难。从粗心大意的错误开始,比如在基于Windows NT 的Windows CE仿真器上使用Microsoft Windows NT库,接着又违背Windows CE的编程戒律,如"千万不要给Unicode(国际标准组织10646标准)字符分配奇数内存地址"。

  大约有百分之九十的问题或多或少地与Unicode有关。尽管Unicode编程不难,但是,当给单字节字符编写代码时,很容易出错(我有过许多次错误)。

  下面这些忠告是根据我们在Windows CE上编写Raima 数据管理器的经验总结出来的,但我相信,在做任何其它Windows CE程序之前,它们都值得借鉴。毕竟大多数Windows开发者,当他们创建第一个Windows CE应用程序时,真正运用的是已掌握的Win32知识。

1. 不要在仿真器上使用Windows NT库

  这里所讨论的第一个错误实在太愚蠢了,但我还是陷了进去,也许你也会。当用Microsoft VC++(5.0版)创建一个Windows CE程序时,你会发现,包含路径(include)、库路径(library)、及可执行程序路径被自动调整以匹配反应目标环境的选择。因此,比如说为Windows CE模拟器建立应用程序时,你会发现,include路径没有指向Win32的包含文件(在VC目录下),而是指向Windows CE包含文件(在WCE目录下)。千万别去修改。

  由于Windows CE在Windows NT下运行,所以仿真器上运行的程序能够调用任一Windows 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函数中指定变量大小为bytes,然而_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_SIZE的大小。假设UNICODE定义了,那么该字符串就是一个 widechar字符串。如果HEADER_SIZE是一个偶数,这段代码就会正常工作,但如果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()和 WideCharToMultiByte () 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)中,你也许得求助于hack,如下所示:

/*
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 o­n 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()函数要容易,因为使用wsprintf()函数更难以限制目标指针所指向的字符串的长度。

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 CE均支持。

   几乎象所有的数据库引擎一样,Raima数据库管理器在数据库文件中以二进制形式保存数字数据。这就意味一个记录无论何时写入数据库或从数据库读出,均 被当作一系列的字节来处理,不管它域的内容。只要数据库文件不要传给别的任何系统,数字数据的字节状态问题就解决了。如果数据库文件被一个来自原始系统且 带有不同字节状态的处理器访问,数字数据将被误解。

  无论何时,当你在拥有不同处理器的机器上传输文件时,就会出现这个问题。在这个问题上,值得高兴的是所有类型的处理器都使用相同的字节状态。

  在使用Windows CE时,这10点忠告应该引起你足够的重视,避免学习时走弯路。

为了学会更多的Windows CE开发工具,请访问Microfoft Windows CE Toolkits站点:http://msdn.microsoft.com/cetools/


- 作者: bossbear 2005年04月19日, 星期二 22:16  回复(0) |  引用(0) 加入博采