饮水思源 - Embedded精华区文章阅读
发信人: vagrant (小狮子~simba), 信区: Embedded
标  题: Penbex OS简介(zz from nju)
发信站: 饮水思源 (Sun Nov 18 22:27:10 2001) , 转信

lyman (懒人,懒得去想) 于Sun Nov 18 10:06:19 2001)
提到:

1.1 Penbex OS简介
   Penbex OS是一个基于PDA的、实时的、多任务的图形界面操作系统。首先介绍一般PDA
的硬件结构。
下面的框图表明了一般PDA 的硬件结构。
    在上面的框图中,ROM指的是广义上的ROM,包括FlashROM和MaskROM等设备,Key-Pad
是指PDA上的按键,PDA上的CPU与PC上的CPU不同,一般是专用的低功耗的芯片,例如Moto
rola 68K系列等。从上图可以看出,一般的PDA上是没有硬盘或者与硬盘相当的存储设备的
,PDA上的数据一般是存储在RAM区或者FlashROM区中。
   由于Penbex OS是专为PDA而设计的操作系统,该OS在具有一些当前操作系统通用的特点
和功能的同时,还具有一些PDA上OS的比较特殊的方面(由于PDA上硬件资源的限制,基于
PDA的OS必须足够精简)。
   下图是Penbex OS的软件体系结构:
     
   在OS的结构中,我们看到OS提供的核心API是非常少的,仅仅包括任务调度等功能,在
这些核心的功能之上主要是系统提供的一些功能性模块,包括文件系统,数据库系统,控
件以及网络功能等。文件系统模块在FlashROM或者RAM中维护着一个FAT16的文件系统结构
,数据库模块使得开发者可以在PDA上建立、维护和操作一个数据库文件,控件模块为开发
者提供了一整套比较完备的GUI控件集合,网络和通讯模块使得开发者可以在基于Penbex 
OS的PDA上开发网络和通讯程序。在后面的几章中,我们将会详细的讲解这些模块。
1.2 Penbex OS上应用程序的开发:
  目前,Penbex OS已经提供了一套完整的API和封装好的可视化对象集,在此基础上,互慧
公司推出了基于Windows环境的SDK,该SDK提供了在PC机的Visual C++环境下开发和调试基
于Penbex OS的应用程序的环境。由于C语言的高效性和流行性,PenbexSDK的开发语言选用
了标准C,而之所以选用Visual C++,是因为它的完备的IDE环境和强大的调试功能。
对于PDA上的应用程序,在模拟器环境中完成了开发和调试只是第一步,还需要在PC上运用
跨平台编译器将源文件编译成PDA上的可执行文件(在Penbex OS上为.pbx文件),并通过
串口线将PC上的.pbx文件传输到PDA上,这样才能够在PDA上执行该程序。
   整个开发流程如下图所示:
 
Note: 在上面的开发流程中,Windows系统最好是Win98或者Win95,因为在NT或者Win2000
下运行模拟器时,由于NT系统的一些保护机制可能会出现一些警告。

Remark:在上面所说的开发流程中,通过GNU跨平台编译器生成.pbx文件时,GNU编译器支
持ANSI C的语法规范,所以在VC下开发时,也必须使用ANSI C的语法(这样源文件均为.c
或者.h文件)
1.3 Penbex OS SDK 简介  
   Penbex OS SDK包含一个VC6.0中的工程文件,在VC下编译该工程文件即可以得到一个W
indows环境下的模拟器。这个工程文件由以下几个部分组成
1.	库文件(主要是提供了Penbex OS上所有API的模拟实现)
2.	头文件(pbxos.h中包含了大部分Penbex OS API说明,NetApi.h中给出了与网络有关
的API的声明,Msg.h中给出了所有消息的定义,Ui.h中给出了与UI控件相关的API声明,而
pbxall.h中包括了以上的头文件)
3.	示例源码(示例源码中包含了Penbex OS中大部分的控件使用范例)
   为了运行Penbex OS SDK,首先打开Visual C++ 6.0(注意:Penbex OS SDK是基于VC6
.0的,用以前的VC版本编译工程文件会出错,同时Windows系统若是NT,则运行模拟器时会
出现一些警告信息,所以我们推荐使用Windows98),然后打开Penbex OS SDK中的工程文
件(这里是Penbex OS.dsw文件),编译并执行该工程,就可以看到一个Windows环境下的
模拟器。
下面我们看一下Penbex OS SDK:
 
Penbex OS中的API:
    Penbex OS所提供的API主要分成以下几类:
    1.对ANSIC中标准库函数的支持,例如数学函数,字符串处理函数,内存分配函数等,
这些函数的原型定义可在ansilib.h中找到。
    2.与Penbex OS系统相关的函数,如获取消息,发送消息的函数,创建控件的函数等等
,这些函数的原型定义可在pbxos.h中找到。
3.与具体控件相关的函数,如设置按钮的标题等,这些函数的原型定义可在ui.h中找到

4.网络API(目前封装了SLIP、PPP、TCP/IP、SMTP和POP3协议等)
5.文件API(提供标准的文件流操作)
6.数据库操作API(提供了对数据库的操作)
可视化对象:
    Penbex OS所提供的可视化对象主要包括容器和控件两类
  容器:用于容纳各种控件的可视化对象,Penbex OS中主要容器为Container、Tab-Cont
ainer和Pop-Container。每个应用程序至少包括一个容器,每个容器中可包含多个控件。
(Penbex OS中的容器与Windows系统中的窗口非常类似,但是我们这里之所以称为容器,而
不称为窗口,是因为容器是窗口的简化,例如,Penbex OS中所有的容器都是全屏的,并且
不能够调整大小,当然也就没有重叠,这样处理的好处是符合PDA上LCD屏较小的现状,并
且可以将OS做的比较精简)
   控件:Penbex OS中基本的可视化UI对象 ,Penbex OS主要控件包括:编辑控件(含单行
和多行控件),软键盘控件,下拉控件,按钮,滚动条,组合框等。
Note: 需要指出的是,在Penbex OS的图形界面中,只有容器(container)和控件(control
,也称object)的概念,而没有窗口的概念。这是由于Penbex OS的图形界面中没有窗体重叠
(overlap),也没有最大化、最小化等操作。

编写Penbex OS程序的基本模式: 
  Penbex OS中每个应用有唯一的入口函数,该函数的两个参数(DWORD argc和void *argv
)由操作系统填充并传给入口函数。OS上有一个Shell程序,就是开机后看到的桌面程序(
在SDK的模拟环境下,就是编译并执行SDK附带的工程文件后所看到的第一个界面程序)。
程序是通过用户点击桌面(desktop)上相应的按钮而被操作系统调用的。Penbex OS操作系
统中最多只能有一个应用程序在运行。
  Penbex OS中的应用程序是基于消息处理的,并且在Penbex OS中每个容器都有唯一的消
息队列,因此一个应用程序至少拥有一个容器,这样才能从该容器的消息队列中取得消息
。
同时在程序执行的任何时候,只能够有一个容器处于激活状态(即系统从该容器的消息队
列中取消息)。下面是Penbex OS上的消息循环
     
为了程序能够顺利执行,应用程序的入口函数必须
1.设定容器及该容器的消息处理函数
2.设定当前活动容器
3.激活消息循环。

下面我们介绍一些Penbex OS中的消息。在Penbex OS中,消息主要分为以下几类:
1.	输入消息和定时器消息(其中输入包括Pen Message,Keyboard Message).
2.	串口消息和红外口消息
   3. 系统消息(其中最主要的两种为容器初始化和退出的消息,其Message type分别为
MT_SYS_CONT_BEGIN和MT_SYS_CONT_END)   
   4.与控件有关的消息(其Message Type为MT_CtrlName_COMMAND,其中CtrlName为控件名
)
这里顺便介绍一下SDK中关于消息的命名规则。以MT_SYS_CONT_BEGIN为例,MT是message 
type的缩写,SYS是System的缩写,CONT是Container的缩写,这个就表明是当系统容器初
始化时发送的消息类型。照此类推,MT_BUTTON_COMMAND应该是当按钮被按下时系统发送的
消息类型。
   在以上四类消息中,第一、二类消息是直接与硬件相关的消息,由中断处理过程发送,
第三类和第四类消息为系统发送的消息。
   
Penbex OS中的内存占用
  在PDA中,内存区域分成ROM区和RAM区 (这里所说的ROM指FlashROM和MaskROM等部分)。
操作系统以及核心程序驻留在FlashROM区,而下载程序存储在RAM区或者FlashROM区中,程
序中对内存的分配都是在RAM区中进行的。  
NOTE:对于使用Penbex OS的PDA,当用户按下PDA上的电源按钮时,CPU进入省电模式运行,
在此模式下LCD显示屏的电源关闭,但是RAM仍然处于上电的状态,因此下载程序可以存储
在RAM区域中

移植应用程序到PDA上
   通过Penbex OS SDK开发的程序不仅能在模拟器上运行,而且还能够下载到PDA上运行.要
将Penbex OS SDK上开发的程序移植到PDA.只需要将SDK中的程序的入口函数改为PenbexMa
in,然后用专用编译器重新编译、链接而生成PDA(基于Penbex OS)上可运行的二进制代
码,再通过串口通讯程序将二进制代码传到PDA上即可。事实上,在最新的SDK中,已经将
GNU编译器集成到了VC的环境下,可以直接在VC环境下生成最终的.pbx文件,在填写入口函
数时,应该使用一个条件编译开关,如下所示:
#ifdef WIN32
void HelloMain(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif

练习:
1.安装PenbexSDK。
2.在VC6.0环境下打开模拟器的工程文件,编译并运行该工程。
3.利用PenbexSDK中的Add-In生成pbx文件(Hint:可参考第二章第三节的内容)
问题:
1.	比较以下目前主流的PDA OS,它们各自有什么优缺点。
2. 您认为一个PDA上的OS应该具有什么功能和特点


lyman (懒人,懒得去想) 于Sun Nov 18 10:07:02 2001)
提到:

2.1 第一个Penbex OS上的程序
我们首先编写一个类似”hello,world”之类的小程序。该程序只是在LCD屏幕上打印一个
”hello,world”字符串。这里,我们的程序名为FirstProg.c和FirstProg.h.
首先,在SDK中的PDA AP子目录(指工程文件中的虚拟目录)结构中添加一个新的文件夹,
取名为FirstProg,然后创建一个C源文件,命名为FirstProg.c,再创建一个头文件,命名
为FirstProg.h,在FirstProg.c中键入以下内容:
#include “pbxall.h”
#include “FirstProg.h”

FirstProgVar cFirstProgVar;
FirstProgVar *pFirstProgVar;

static BOOL MsgHandler (SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type)
      GmTextOut(30,30, "hello,world");     
   return 0;
}

void FirstProgMain(unsigned long argc,void *argv)
{
   SysMsg msg;
   if (APP_RUN_NORMAL==argc) {
      SetContainerEntry (100,MsgHandler);
      SetActiveContainer(100);
      while (GetMsg (&msg) )
         if (!SysProcess (&msg)) 
           AppProcess (&msg);
   }
}
然后编辑头文件FirstProg.h,键入以下内容

#ifndef  _FirstProg_h //[
#define  _FirstProg_h

#ifdef __cplusplus //[
extern "C"{
#endif  // ] __cplusplus

typedef struct tagSmTemplateVar{
	  int x,y;
}FirstProgVar;

extern FirstProgVar cFirstProgVar;
extern FirstProgVar *pFirstProgVar;
extern void FirstProgMain(DWORD,unsigned long *);

#ifdef __cplusplus //[
}
#endif //] __cplusplus
#endif //] ifndef
  我们分析一下所输入的代码:
  首先,在FirstProg.c中,我们有一个FirstProgMain函数,这个函数是这个应用程序的
入口函数(您可能觉得奇怪,随便的一个函数FirstProgMain怎么能作为入口函数,事实上
,我们还需要在SDK中输入一些代码将FirstProgMain设为入口函数),在这个入口函数中
,首先调用了SetContainerEntry设定应用程序的容器的ID值和应用程序的消息处理函数(
也称回调函数),然后是一个循环,这个循环不断的从消息队列中取消息并进行处理(若
您有过Windows或其他事件驱动的程序编程的经验,那么您对这种处理不会陌生)。在消息
处理函数MsgHandler中,程序判断消息类型是否为MT_SYS_CONT_BEGIN,若是,则在LCD屏的
(30,30)位置处打印”hello,world”。
   接下来我们分析以下FirstProg.h中的代码:
 #ifndef  _FirstProg_h //[ 
 #define  _FirstProg_h
 …………
 ………….
 #endif //] ifndef
上面的语句是为了防止对FirstProg.h的重复引用,而条件编译语句中的extern “C” { 
}是为了保证编译的目标文件中的函数名等使用标准C的规范。
在FirstProg.h文件中定义了一个结构FirstProgVar,程序中的全局变量可以封装到这个结
构中(注意:即使您的应用程序中没有用到任何全局变量,也必须填充这样一个结构,这
是由于expreap.c文件中的LoadPreloadAP函数需要调用AppAdd函数将用户的程序添加到模
拟器的桌面上,而AppAdd函数需要该结构)。接着是三个声明,分别声明了一个FirstProg
Var类型的变量,一个指向FirstProgVar型变量的指针和FirstProgMain函数。
至此,我们已经基本上完成了在Penbex OS SDK上的第一个程序,之所以只是基本完成是因
为我们还无法在模拟器上看到它的执行情况,为了将该程序添加到模拟器上,必须对SDK的
工程文件中的expreap.c文件进行改动。
#include "tstdesk.h"
#include "FirstProg.h"
void LoadPreloadAP (void)
{
 AppAdd(XX,XX,"Desk.px","Desk",APDesktop,0,(void*)&cDeskVar,\
sizeof(DeskVar),(void**)&pDesk);
 AppAdd(XX,XX, "FirstProg.px","Hello",FirstProgMain,0,(void *)&\
cFirstProgVar,(void **)&pFirstProgVar);
}
 现在可以明白为什么FirstProgMain为入口函数以及必须封装一个结构。在VC6.0中编译整
个工程文件,就可以看到如下的模拟环境:
                     
   (图2.1.1)                            (图2.1.2)
其中Hello即是AppAdd的第四个参数,点击模拟器的Hello图标,即调用了我们前面所写的
程序。其执行效果如上右图所示。
练习:
2.1.1	编写一个程序,在LCD屏的30,30位置输出”你好”(Hint:参考GmSetFont函数)

2.1.2	试着在本节中的程序加入断点,在VC下用F5进行调试
问题:
2.1.1若本节中例子程序中的输出字符串长度很大,会出现什么情况?
2.2 “hello,world”程序中的消息处理
  我们再来看一下”hello,world”程序中的消息处理过程。从操作系统调用FirstProg程
序的入口函数FirstProgMain开始至程序退出为止,程序流程一直是在消息循环中(典型的
是GetMsg,SysProcess,AppProcess循环调用),其中GetMsg负责不停的从消息队列中取消
息,SysProcess对消息作预处理,AppProcess会调用用户的回调函数。在这个程序中,回
调函数是MsgHandler,它的参数是一个指向消息结构的指针。
  现在我们要对FirstProg程序功能进行扩充,每当用户用笔点击一下LCD屏时,出现一个
FloatBox(FloatBox是一种控件,是一种显示一段时间后自动消失的按钮)。于是,向回
调函数中加入以下的代码:
static BOOL MsgHandler (SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type)
      GmTextOut(30,30, "hello,world");     
   else if (MT_PEN_DOWN==pMsg->type)
      FloatBox (“Pen is down”,DELAY300ms,GM_FONT_MIDDLE);
  
 return 0;
}
重新编译、运行,在程序中,每当笔点在触摸屏上,都会弹出一个FloatBox。
如下图所示:
 
     (图2.2.1)
   这样的程序事实上还是功能太弱了,下面向其中加入控件。Penbex OS底层提供了许多
UI设计时所需的控件,包括菜单、滚动条、工具条、按钮、编辑控件、
软键盘、树形图、进度条等。现在向FirstProg程序中加入按钮控件。
   加入控件的代码在消息处理函数中,对MsgHandler作如下改动:
static BOOL MsgHandler (SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type)
   {
      ContAddObj(btnNew(1,50,50,40,20,”Button”,BTNSTYLE_STRING);
      ContRedraw();
      GmTextOut(30,30, "hello,world");     
   }

   else if (MT_PEN_DOWN==pMsg->type)
      FloatBox (“Pen is down”, DELAY300ms,GM_FONT_MIDDLE);
  
 return 0;
}
  消息处理函数在收到MT_SYS_CONT_BEGIN消息时,向容器中添加了一个按钮控件,该按钮
的ID值为1,左上角的坐标为(50,50),宽度为40,高度为20,该按钮上显示文本”But
ton”。然后调用ContRedraw对容器中的所有控件进行重画。编译并运行,可以看到如下的
结果:
 (图2.2.2)
注意:若没有ContRedraw这一行代码,程序运行时将无法看到按钮,但事实上,系统是知
道在(50,50)处有一个宽度为40,高度为20的按钮的,如果笔点在按钮所在的矩形区域内,
该区域会反色。效果如下图所示:
 
  (图2.2.3)
  现在,我们成功地向容器中添加了一个按钮控件,并且注意到要想正确地显示该控件,
必须进行重绘(不一定调用ContRedraw),这也是大多数UI程序设计时必须处理的一步。

下面,我们看看消息处理函数的返回值,在前面的例子里,MsgHandler的返回值为0。必须
注意的是,消息处理函数的返回值是有意义的,一般的,应该返回0。特别的,对于Pen_D
own消息的处理大多数情况下应返回0。我们可以将前面代码中的在处理了Pen_Down消息后
的返回值设为1,即对代码作如下修改:
 static BOOL MsgHandler (SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type)
   {
      ContAddObj(btnNew(1,50,50,40,20,”Button”,BTNSTYLE_STRING);
      ContRedraw();
      GmTextOut(30,30, "hello,world");     
   }

   else if (MT_PEN_DOWN==pMsg->type) {
      FloatBox (“Pen is down”,DELAY300ms,GM_FONT_MIDDLE);
      return 1;
}
  
 return 0;
}
重新编译并运行程序,进入FirstProg程序,用笔点在按钮上,发现按钮没有响应。为什么
会这样呢?首先必须明确控件消息和笔的输入消息在生成上是不同的。笔的输入是由触摸
屏接受到,然后通过中断线传给CPU,最后CPU调用中断程序处理笔的输入,可以认为笔的
输入消息是由硬件中断产生的,而控件消息是由系统对底层的由硬件中断产生的消息进行
分析的基础上而产生并发送的。若处理的笔的输入消息以后MsgHandler返回1,则系统认为
没有必要对Pen_Down消息作进一步的处理,从而不会在此基础上产生控件消息,因此按钮
没法激活。
Note:一般的,在消息处理函数中,处理了笔的输入消息后应该返回0
练习:
2.2.1 若笔按到KeyPad上,Penbex OS是否会发送Pen_Down消息?通过程序来验证你的想法
。
2.2.2 改写本节中的程序,使得当笔点在LCD上时,通过一个FloatBox来显示落笔位置的(
x,y)坐标。
问题:
  2.2.1 什么情况下会用到FloatBox函数?
 
2.3  生成pbx文件
   生成pbx文件最简单的方法是通过Add-In(在安装SDK后,可以向VC6.0中添加一个Add-
In,具体方法如下所示:在VC6.0中,点击菜单Tools,选中Custom,在弹出的对话框中选
择Add-Ins and Macros属性页,在该页中选中Penbex Developer Add-In,再点击Close按
钮即可在VC6.0中弹出一个工具条)。首先在VC6.0中打开模拟器的工程文件,然后点击工
具条最左边的条目 ,弹出如下的对话框:
 
  
在上面的对话框中选择相应的程序,点击Rebuild按钮即可生成pbx文件,生成pbx文件的过
程可以在VC的输出中看到,如下图所示:
 练习:
2.3.1.将本章的程序和您已经编写的程序编译成pbx文件
问题:
2.3.1  为什么C文件中程序的入口函数必须通过预编译语句来进行设置?
2.3.2  在模拟器上运行与在真正的PDA上运行有什么区别,为什么?
2.3.3	在模拟器上的程序中调用malloc(10000000)语句并执行,malloc(10000000)函数返
回值为多少?若调用Malloc(10000000)呢,为什么这样(Hint:考虑一般PDA的内存大小)
?
2.3.4	试着将2.3.3中使用了malloc(10000000)的程序编译成pbx文件,会出现什么情况,
为什么?


lyman (懒人,懒得去想) 于Sun Nov 18 10:09:55 2001)
提到:

3.1  UI控件总括
UI设计中,控件是程序与用户通讯的重要手段,一般的常用控件有按钮、单选框、复选框
、菜单、工具条、滚动条、树形图、表格、文本框、下拉框、组合框、消息框、进度条等
。Penbex OS底层提供了对这些控件的支持。第二章中介绍的就是按钮控件。下面是这些控
件的主要功能:
1.	按钮:
  为提供用户进行确定、取消等输入的手段
2.	单选框
为用户提供多选一的操作
3.	复选框
为用户提供多选多的操作
4.	菜单
让用户能够选择不同的操作
5.	工具条
为用户提供图形化的操作选择
6.	滚动条
当显示区域小于内容所占的总的区域时,为用户提供一个让显示区域在总的区域内切换的
工具
7.	树形图
为用户提供查看树形结构的工具
8.	表格
对于二维表提供了可视化的显示组件
9.	文本框
输入和输出文本的工具
10.	下拉框
   显示多行内容以供用户参考或选择
11.组合框
   为用户提供了多选一的手段,与单选框相比,组合框的内容一般是可变的。
12.消息框
   用于显示警告、提示等信息
13.进度条
   用于显示进度的控件
14.白板
   用于绘制图形的控件
   Penbex OS中还提供了一种特殊的控件,称为浮动按钮(FloatBox)。该控件显示一个方
框,方框中带有指定的输出,过一段时间后,该方框会自动消失。这种
控件在某种程度上提供了消息框的功能,而且该控件用于输出调试信息是非常方便的。

   下面分别演示各种控件:
 
          (图3.1.1)
  这些控件都由系统在底层提供支持,并且创建这些控件是很简单的。在前一章里,我们
调用btnNew就创建了一个按钮,创建其他控件的过程与创建按钮在本质上没有什么不同。
下面对不同的控件,分别说明其使用方法:
3.2 软键盘控件
   由于一般的PDA都不带专用的键盘输入设备,因此软键盘在PDA上是非常有用的一种控件
,它为用户提供了手写输入之外的另一种选择。为了创建一个软键盘控件,程序员的工作
是很少的。下面,我们编写一个SoftKBD程序。首先,进入Penbex OS SDK的VC6.0环境,在
PDA AP目录中添加一个新的目录,取名为SoftKBD,然后向其中添加SoftKBD.c和SoftKBD.
h文件(起初是空文件),
在SoftKBD.h中,键入以下内容:
  #ifndef  _SoftKBD_H
  #define  _SoftKBD_H       
  
  #ifdef  __cplusplus
  extern "C" {
  #endif
  
  typedef struct tagSoftKBDVar {
     int useless_stuff;
}SoftKBDVar;

extern SoftKBDVar cSoftKBDVar;
extern SoftKBDVar *pSoftKBDVar;
extern void SoftKBDMain(unsigned int argc,void *argv);

  #ifdef  __cplusplus
  }
  #endif

  #endif

在SoftKBD.c中键入以下内容:
  #include "pbxall.h "
  #include "SoftKBD.h "
  
  SoftKBDVar cSoftKBDVar;
  SoftKBDVar *pSoftKBDVar;

  static BOOL SoftKBD_Handler(SysMsg *pMsg)
  {
if (pMsg->type==MT_SYS_CONT_BEGIN) {
   ContAddObj(SysEngKB(12));
   ContRedraw();        
}
return 0;
  }
  
  static void SoftKBDMain(unsigned long argc,void *argv)
  {
SysMsg msg;
if (APP_RUN_NORMAL==argc) {
   SetContainerEntry(100,SoftKBD_Handler);
   SetActiveContainer(100);
   while (GetMsg(&msg)) 
      if (!SysProcess(&msg))
         AppProcess(&msg);
}
  }

  向expreap.c中的LoadPreloadAP函数中加入以下的代码:
AppAdd(XX,XX,"ap02.px","SoftKBD",(void*)&cSoftKBDVar,sizeof(SoftKBDVar),(void 
**)&pSoftKBDVar);  
   然后,不要忘了在expreap.c的前面部分加上#include "SoftKBD.h"语句。
注:AppAdd的参数中的"ap02.px"只是一个字符串,区别SDK中的其他程序。因此,如果您
的SDK中已经加入了一个应用程序并且该应用程序用"ap02.px"标识,则需要换一个标识的
字符串。  
  编译并运行该程序,可以看到如图(3.2.1)所示的输出:
     (图3.2.1)                                 (图3.2.2)
   这时,点击软键盘就可以进行输入。不过,由于并没有接受输入的控件,我们看不到键
盘输入的效果,下面,我们向主容器中加入一个单行编辑控件以接受用户的软键盘输入。
在SoftKBD.C中的SoftKBD_Handler函数中加入以下代码:
   static BOOL SoftKBD_Handler(SysMsg *pMsg)
  {
if (pMsg->type==MT_SYS_CONT_BEGIN) {
   ContAddObj(SysEngKB(12));   ContAddObj(sedNew(13,30,32,36,ES_STYLE_NORMAL|\

ES_STYLE_NO_BORDER, GM_FONT_MIDDLE, ES_UI_DOCK_TOP));
   ContRedraw();        
}
return 0;
  } 
  运行修改后的程序,可以看到如图(3.2.2)所示的效果。该程序中已经有了接受软键盘
输入的控件,但用户将笔点在软键盘上仍然不能够在单行编辑控件上产生输入,这是因为
单行编辑控件尚未获得当前的输入焦点。要想解决这个问题,有两个方法,第一个是在程
序中显式的指定单行编辑控件为当前键盘的输入焦点。第二个方法更简单,只需要用户用
笔在单行编辑控件上点一下,系统就可以将输入焦点设为该控件。下面我们用第一种方法
来实现,事实上,只需要在SoftKBD.c文件中对SoftKBD_Handler函数作如下修改即可:

   static BOOL SoftKBD_Handler(SysMsg *pMsg)
  { UIOBJ  *pUI:
if (pMsg->type==MT_SYS_CONT_BEGIN) {
   ContAddObj(SysEngKB(12));   
        pUI=sedNew(13,30,32,36,ES_STYLE_NORMAL|\
ES_STYLE_NO_BORDER, GM_FONT_MIDDLE, ES_UI_DOCK_TOP)
ContAddObj(pUI);
sedSetFocus(pUI);
  ContRedraw();        
}
return 0;
  } 

Note:事实上,在涉及到键盘输入时,基本上不需要程序员编写任何代码来创建软键盘,这
是由于系统在底层做了许多工作,若当前容器中有Edit控件,点击KeyBoard区域的KeyBd按
键可以调出软键盘,并且系统会自动将软键盘的输出作为Edit控件的输入,如下图所示:
 

在本小节的最后,列出与软键盘控件相关的各项API及消息:
   相关API:
   SysEngKB:创建一个英文软键盘
 相关消息:
   MT_KEYBOARD_COMMAND:点击软键盘时发送此消息,该消息结构的ID域值为软键盘上的
键,该值与标准的ASCII码值一致
   MT_KEYBOARD_FUNC:
   MT_KEYBOARD_FOCUS_IN:当笔点在软键盘上时发送此消息(笔处于按下的状态)
   MT_KEYBOARD_FOCUS_OUT:将笔从软键盘上释放时发送此消息(笔处于抬起的状态)  
 

练习:
3.2.1	修改本节中的程序,去掉软键盘控件,通过系统的软键盘控件来向单行编辑控件中
输入。
3.2.2	创建一个多行编辑控件的程序
3.2.3	在3.2.1中创建的程序的基础上,添加一个按钮控件,使得用户点击在按钮上时,通
过FloatBox来显示单行编辑控件中输入的内容。(Hint:sedGetEditText函数返回一个Ha
ndle,该Handle事实上指向单行编辑控件中的文本的头指针,即Handle是指向指针的指针
)
问题:
3.1.1  单行编辑控件于多行编辑控件有什么区别,在那些情况下应使用单行编辑控件,那
些情况下反之?


lyman (懒人,懒得去想) 于Sun Nov 18 10:11:07 2001)
提到:

3.3 RatioBox&CheckBox and Button
   一般的,RatioBox,CheckBox和Button是作为不同的控件来进行处理的,但是在Penbex
 OS SDK中,上述三种控件的创建可以通过一个函数来实现,那就是btnNewEx。在第二章中
,我们已经知道btnNew可以创建通常意义上的按钮(Button)。而btnNewEx这个API则是更
广义上的,btnNewEx比btnNew函数多了三个参数,分别表示button中的字符串显示的位置
(是偏左,偏右还是居中),字体的大小,以及风格(是普通的按钮,还是RatioBox,或
者是CheckBox)。
下面用btnNewEx构造出RatioBox,CheckBox和普通的按钮。
在SDK工程中的PDA AP目录中新建一个MiscBtn的目录,向该目录中添加MiscBtn.c和MiscB
tn.h文件。(此处略去在expreap.c中所作的操作,可以参考前面的例子)。下面我们向M
iscBtn.h文件中输入以下内容:
#ifndef  _MiscBtn_H
#define  _MiscBtn_H
#ifndef  __cplusplus
extern "C" {
#endif

typedef struct tagMiscBtnVar {
   int useless_stuff;
}MiscBtnVar;

extern MiscBtnVar cMiscBtnVar;
extern MiscBtnVar *pMiscBtnVar;
extern void MiscBtnMain(unsigned long argc,void *argv);

#ifdef  __cplusplus
}
#endif

#endif
   
在MiscBtn.c中键入以下内容:
   #include "pbxall.h"
   #include "MiscBtn.h"

  MiscBtnVar cMiscBtnVar;
  MiscBtnVar *pMiscBtnVar;
  
  static BOOL MiscBtn_Handler(SysMsg *pMsg)
  {
if (MT_SYS_CONT_BEGIN==pMsg->type) {
 ContAddObj(btnNewEx(11,20,20,40,20,"b1",LABSTYLE_STRING,\
BTNSTRPOS_LEFT,GM_FONT_MIDDLE,BTN_KIND_CHECKBOX_RADIO));
 ContAddObj(btnNewEx(12,20,50,40,20,"b2",LABSTYLE_STRING,\
BTNSTRPOS_RIGHT,GM_FONT_MIDDLE,BTN_KIND_CHECKBOX_CHECK));
ContAddObj(btnNewEx(13,20,80,40,20,"b3",LABSTYLE_STRING,\
BTNSTRPOS_RIGHT,GM_FONT_MIDDLE,BTN_KIND_BUTTON));
      ContAddObj(btnNewEx(14,20,110,40,20,"b4",LABSTYLE_STRING,\        BTNSTR
POS_MIDDLE,GM_FONT_MIDDLE,BTN_KIND_CHECKBOX_NORMAL));
      ContRedraw();
}
}

static void MiscBtnMain(unsigned long argc,void *argv)
{
   SysMsg msg;
   if (APP_RUN_NORMAL==argc) {
       SetContainerEntry(100,MiscBtn_Handler);
       SetActiveContainer(100);
       while (GetMsg(&msg))
          if (!SysProcess (&msg))
             AppProcess (&msg);
}
}
 运行该程序,可以看到三种不同的按钮。     
 (图3.3.1)
  在以上的程序中,b1是RatioBox,b2是CheckBox,b3是普通的按钮,b4是下压框。
  在本小节的最后,列出与Button控件相关的API以及消息:
  相关API:
  btnNew:创建按钮
  btnNewEx:创建各种按钮,是btnNew的增强
  btnSetCaption:设置按钮的标题(设置了标题后必须调用重绘函数)
  btnClearCaption:清除按钮的标题(清除标题后也必须调用重绘函数)
  btnFrame:设置是否显示按钮的边框
  btnSetRepeat:设置产生MT_BUTTON_REPEAT消息的参数
  btnGetCheckBoxState:获取CheckBox的状态(是否选中)
  btnSetCheckBoxState:设定CheckBox的状态
  btnSetThreeD:设定按钮的三维效果
  btnSetRadioBtnGroup:设定单选按钮所在的组
 相关消息:
MT_BUTTON_COMMAND:点击按钮时系统将发出这个消息(注意:这里的点击按钮的事件意味
着用户首先将笔点在按钮上,并且用户抬起笔时,笔的位置仍在按钮的区域内)
MT_BUTTON_FOCUS_IN:当用户将笔点在按钮上时系统发出此消息
MT_BUTTON_FOCUS_OUT:当用户抬起笔时,若笔的位置在按钮的区域内,系统将发出这个消
息
MT_BUTTON_REPEAT:  当用户将笔点在按钮上并且维持一段时间后,系统将发送此消息

   
练习:
 3.2.1 创建一个中文按钮,按钮高度尽可能小。
 3.2.2 创建一个容器,容器中可以让用户选择性别,职业等等(使用按钮、标签等控件)
 
 3.2.3 用按钮创建一个数字小键盘 
 3.2.4 创建一个图形按钮,当用户点到图形按钮上时改变按钮上的图形
问题:
 3.2.1 什么情况下系统会发送MT_BUTTON_COMMAND消息,验证你的猜测。
 3.2.2 什么情况下系统会发送MT_BUTTON_FOCUS_IN和MT_BUTTON_FOCUS_OUT消息,验证你
的猜测。
 3.2.3 什么情况下用户会用到MT_BUTTON_FOCUS_IN和MT_BUTTON_FOCUS_OUT消息?
3.3	  进度条控件 
  下面我们演示如何创建一个进度条控件:
   首先,我们创fldnew建一个Progress.c和Progress.h文件,将他们加入到SDK的工程文
件中(具体步骤可以参考前面的例子),然后编辑expreap.c文件(具体的步骤请读者自己
完成)。接着向Progress.c文件中键入以下内容:
   #include "pbxall.h"
   #include "Progress.h"

  ProgressVar cProgressVar;
  ProgressVar *pProgressVar;
 
 static BOOL ScroBar_Handler(SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type) {
      ContSetTitle("Progress bar Demo");
ContAdd(progsNew(11,10,40,100));
ContRedraw();
} 
return 0;
} 
  void ProgressMain (unsigned long argc,void *argv) 
{
     SysMsg msg;
         if (APP_RUN_NORMAL==argc) 
{
   SetContainerEntry(100,Scrobar_Handler);
   SetActiveContainer(100);
   while (GetMsg(&msg)) 
      if (!SysProcess(&msg)) 
          AppProcess(&msg);
}
}
  然后仿造前面的例子,向Progress.h中写入相应的内容。
执行该程序,可以看到如下结果:
(图3.3.1)                      (图3.3.2)
在上面的例子中,进度条的默认值为0,代表0%。一般的,在程序中,可以改变进度条的值
,这一点可以通过调用progsIncrease函数来实现。注意,progsIncrease函数只是将进度
条的当前位置加一,并不一定代表增加1%(事实上,在进度条的创建函数中的最后一个参
数设置了进度条的总逻辑长度,该数值越大,调用progsIncrease函数所得到的进度条前进
越小)。在上面程序的消息处理函数中加上以下一段代码:
  static BOOL ScroBar_Handler(SysMsg *pMsg) 
{
   if (MT_SYS_CONT_BEGIN==pMsg->type) {
      ContSetTitle("Progress bar Demo");
ContAdd(progsNew(11,10,40,100));
ContRedraw();
EnableTimerMsg();
} 
else if (MT_TIMER==pMsg->type) 
  progsIncrease();

return 0;
} 
  我们处理了MT_TIMER消息,并且每当收到定时器消息时,均调用progsIncrease函数来更
新进度条(注意,为了使得系统发送定时器消息,首先必须打开定时器,所以在初始化的
过程中,我们先调用了EnableTimerMsg)。运行该程序,发现程序中已经可以对进度条进
行更新。(运行结果可以参见图3.2.2)
   这样,我们实现了对进度条的简单的控制,在本小节的结尾,给出关于进度条控件的A
PI以及关于它们的简单的解释(具体关于API的具体解释,可参见Penbex OS SDK API大全
)
  progsNew:创建进度条,若成功,返回值为指向一个进度条结构的指针,否则,返回NUL
L
  progsReset:对进度条进行重置,也就是清零
  progsChangeMaxRange:重新设置进度条的总的逻辑长度
  progsIncrease:将进度条的逻辑值加一
  progsSetPercent:设置进度条的当前百分比

练习:
3.3.1 编写一个使用进度条控件的应用程序,通过Timer消息来更新进度条,使得当进度条
走到100%时会逐步退至0%,然后逐渐增加到100%,如此循环。
3.3.2 改变创建进度条函数中使用的各个参数以创建不同风格的进度条。
问题:
3.3.1 一般的安装程序都会用进度条来显示安装进度,内部是如何实现的,它们的进度显
示很精确吗?
3.4  菜单控件
  一个菜单是由以下几个部分组成的:
1.	MenuBar
2.	MenuSection
3.	MenuItem
其中MenuBar是一个菜单条,其上有一些MenuSections,例如"File", "About "等等,对于
每一个MenuSection,有一些MenuItems,如"File "下有"Open ", "Edit "等。下图表明了
三者的关系:
    (图3.4.1)
  我们试着创建一个简单的菜单。在SDK的工程文件的PDA AP目录中添加一个Menu的子目录
,向其中加入Menu.c和Menu.h两个文件。在Menu.c中,键入以下内容:
#include "pbxall.h"
#include "Menu.h"
static BOOL Menu_Handler(SysMsg *pMsg) 
{
    UIOBJ  *pUI;
    switch (pMsg->type) {
       case MT_SYS_CONT_BEGIN:
       pUI=menuNew(10,40,MBUI_MENUBAR_STYLE_ROUND,\ GM_FONT_SMALL);
menuAddMenuSection(pUI,"File");
menuAddMenuSection(pUI, "Help");
menuAddMenuItem(pUI,0,11, "Open","O");
menuAddMenuItem(pUI,0,12, "Save","S");
menuAddMenuItem(pUI,1,13, "About","A");
ContAddObj(pUI);
ContRedraw();    
break;
default:
  break;
}
return 0;
}
void MenuMain(unsigned long argc,void *argv) 
{
   SysMsg msg;
   if (APP_RUN_NORMAL==argc) {
      SetContainerEntry(100,Menu_Handler);
      SetActiveContainer(100);
      while (GetMsg(&msg)) 
        if (!SysProcess(&msg))
             AppProcess(&msg);
   }
}
然后在Menu.h中填入相应的内容(主要是声明一个全局变量结构,该结构的一个实例,该
结构的一个指针以及Menu.c中的入口函数MenuMain,请参考以前的例子自己完成)。最后
在expreap.c中作适当的修改(#include "Menu.h"并在LoadPreloadAp函数中添加一行App
Add函数),运行该程序,结果如图3.4.2所示:

(图3.4.2)                     图3.4.3)
  菜单为什么没有显示出来呢?这是PDA上的一些OS的特点。由于PDA的显示屏比较小(在
SDK中模拟器的显示屏的大小为160X160 pixels),为了节省显示空间,Penbex OS中的菜
单都是Pop型的,也就是说,平时是不显示的,需要显示时激活它,用户点选了MenuItem后
自动撤销(可以与Windows环境下鼠标右键弹出的菜单相对照)。基于此,我们还需在程序
中显式的激活菜单,最简单的办法是加一行menuPopupMenuBar()(在初始化语句后)。然
后再执行程序,就可以看到菜单了,如图3.4.3所示。
   上面是关于菜单的最简单的用法,事实上,菜单中的一些菜单项可能需要进行关闭,此
时需要对这些菜单项进行灰色显示,并且有时在不同的状态可能需要显示不同的菜单,这
些在Penbex OS上均是可以实现的。为了显示不同的菜单,首先必须创建不同的菜单,然后
调用menuSetActiveMenu函数设定当前的菜单,最后用menuPopupMenuBar()来进行显示。为
了对某些菜单项进行灰色显示,可以调用menuGrayItem来设置。
   在本小节的最后,列出与菜单控件相关的API
   menuNew:创建菜单
   menuAddMenuSection:添加MenuSection,如"File","About"等
   menuAddMenuHelp:在菜单中加入帮助,具有帮助的菜单的最右边会出现一个"Help"的
MenuSection
   menuAddMenuItem:向菜单中添加一个MenuItem
 menuSetActiveMenu:设置当前的活动的菜单
   menuSetSectionFocus:设置默认状态下激活的MenuSection
 menuPopupMenuBar:弹出当前的活动菜单
   menuGrayItem:灰色显示一个MenuItem并且将其diable
 menuSendHotKey:
   menuGetSectionIndex: 
 menuGetItemIndex:

问题:
3.4.1  通过程序验证在Penbex OS中一个容器中最多能有多少个菜单?
3.4.2  Penbex OS在显示了一个菜单并将其隐藏后会对背景进行重新绘制,写一段代码来
验证这一点。Penbex OS在上面所说的重绘中,是进行全屏重绘吗?验证你的想法。

3.5 下拉框控件
   下拉框控件可以给用户提供一种多选一的手段。下面试着建立包含简单的下拉框控件的
应用程序。同前面的例子一样,我们向SDK中加入ListBox.c和ListBox.h文件。向ListBox
.c中键入以下内容:
   #include "pbxall.h"
   #include "ListBox.h"
   
   static BOOL ListBox_Handler(SysMsg *pMsg) 
   {
UIOBJ  *pUI;

if (MT_SYS_CONT_BEGIN==pMsg) {
 pUI= listNew(11,10,60,60,(BYTE)5,0,LIST_BAR_TEMPORAL);
 ContAddObj(pUI);
 listAddItem (pUI, 1, "First");
 listAddItem (pUI, 2, "Second");
 listAddItem (pUI, 3, "Third");
 listAddItem (pUI, 4, "Fourth");
 listAddItem (pUI, 5, "Fifth");
 listAddItem (pUI, 6, "Sixth");
 listSetItemActive(pUI,3);
 ContRedraw();
}
return 0;
   }
   void ListBoxMain(unsigned long argc,void *argv) 
{
   SysMsg msg;
   if  (APP_RUN_NORMAL==argc) {
       SetContainerEntry(100,ListBox_Handler);
       SetActiveContainer(100);
       while (GetMsg(&msg)) 
          if (!SysProcess(&msg))
             AppProcess(&msg);
   }
}
  向ListBox.h文件中加入相关的声明并在expreap.c文件中作相应的改动(可参考以前的
例子)
运行该程序,可以看到如图3.5.1的输出。

                                     
   (图3.5.1)                          (图3.5.2)
 用户可以拖动下拉框控件上的滚动条以显示未能够在框内显示的条目(滚动条的添加以及
对该滚动条的处理都是由系统完成的)。关于ListBox,一个有用的功能是动态地对ListB
ox中的内容进行添加和删除。例如,我们想实现如下的功能:建立两个ListBox,用户点击
左面的ListBox后,程序会删除该ListBox中选中的item并将该item添加到右面的ListBox中
。
  对ListBox.c中的回调函数作如下修改:
 //global variable declaration…
  UIOBJ *pUI;
  UIOBJ *pUI_tmp;

 static BOOL ListBox_Handler(SysMsg *pMsg) 
   {
// UIOBJ  *pUI;
static int  id_count=0;

if (MT_SYS_CONT_BEGIN==pMsg) {
 pUI1= listNew(11,10,60,60,(BYTE)5,0,LIST_BAR_TEMPORAL);
 ContAddObj(pUI);
 listAddItem (pUI, 1, "First");
 listAddItem (pUI, 2, "Second");
 listAddItem (pUI, 3, "Third");
 listAddItem (pUI, 4, "Fourth");
 listAddItem (pUI, 5, "Fifth");
 listAddItem (pUI, 6, "Sixth");
 listSetItemActive(pUI,3);
 pUI_tmp=pUI;
 pUI=listNew(12,80,60,60,(BYTE)5,0,LIST_BAR_TEMPORAL);
 ContAddObj(pUI); 
 ContRedraw();
}
else if (MT_LIST_COMMAND==pMsg->type) {
  if (11==pMsg->id) {
   listAddItem(pUI,id_count++, GetItemString(pUI_tmp,pMsg->data.w));
   listRemoveItem(pUI_tmp,pMsg->data.w);
   ContRedraw();
}
}
return 0;
   }
 我们增加了一个指针用于指向另一个ListBox结构,同时将指针pUI和pUI_tmp放到了全局
变量区域。最后,在消息处理中,每当收到第一个ListBox上的消息时,将选中的item加到
第二个ListBox中,然后从第一个ListBox中删除该item(注意,这里添加和删除的顺序不
能颠倒)。
 运行修改后的程序,结果如图3.5.2所示。
 作为本小节的结尾,我们列出与ListBox控件相关的API以及消息 
相关API:
 listNew:创建新的列表框
 listAddItemArray:向列表框中添加一组条目
 listSetItemText:设置列表框中条目的内容
 listSetItemActive:设置列表框中激活的条目
 listRealHeight:返回列表框总条目的实际高度
 listRemoveItemAll:删除列表框中的所有条目
 listRemoveItem:删除列表框中的一个条目
 listTotItem:返回列表框中的总条目数目
 listLastActive:
 GetItemString:获得列表框的一个条目的内容
listSetTopItem:设置列表框的最上面的条目
listPopUp:弹出一个列表框
listPopUpEx:弹出一个列表框,是对listPopUp的扩充
listMovePage:在列表框中移动一页
listMoveLine:在列表框中移动一行
相关消息
  MT_LIST_COMMAND:当用户点中了ListBox中的条目时系统发送此消息(注意:如果List
Box中没有任何条目,即使用户点在ListBox的框内,系统也不会发送任何与ListBox相关的
消息)。
MT_LIST_FOCUS_IN:
MT_LIST_FOCUS_OUT:
MT_LIST_FOCUS_MOVE

练习:
3.5.1 在程序中试着使用listPopUp函数。
问题:
3.5.1 在什么情况下会用到listPopUp函数,listPopUp实现的功能与一般的list有何不同
?
  3.6 多行编辑控件
  在介绍软键盘控件时我们介绍了单行编辑控件,这种控件的局限就是不能显示大量的文
本。为了解决这个问题,Penbex OS系统引入了多行编辑控件,它解决了区块文本的输入和
显示的问题。下面我们创建一个多行编辑控件。首先在SDK的工程文件中加入MultiEdit.c
和MultiEdit.h文件,在MultiEdit.c的消息处理函数中加入如下代码:
  static BOOL MultiEdit_Handler(SysMsg *pMsg)
  {
static UIOBJ *pUI;

if  (MT_SYS_CONT_BEGIN==pMsg->type) {
  pUI=medNew(11,0,0,150,3,EM_STYLE_HAVE_BORDER,\
GM_FONT_MIDDLE);
  ContAddObj(pUI);
  ContAddObj(SysEngKb(12));
  ContRedraw();
}

return 0;
  }
  我们首先向容器中加入了一个多行编辑器控件,然后加入了一个软键盘控件作为输入。
运行结果如图3.6.1所示。
                    
(图3.6.1)                        (图3.6.2)
   在上面的例子中,我们并未显式地在程序中为多行编辑控件设置焦点,因此实际运行时
,用户必须在编辑控件上用笔点一下才能够通过软键盘进行输入,在多行编辑控件中,若
用户输入超过了本身的行数,会自动生成竖直方向的滚动条以便用户拖动,效果如图3.6.
2所示。
   多行编辑控件可以作为大量文本输入的手段,也可以作为输出大量文本的手段。
   下面列出与多行编辑控件相关的消息以及API
相关消息:
   MT_MULTIPLE_EDIT_COMMAND:
  MT_MULEDIT_SELECTED:
  MT_MULEDIT_PENUP:

相关API:
medNew:创建新的多行编辑控件
medSetEditText:预先设置编辑控件中的文本内容
medSetEditTextEx:是对medSetEditText的扩充。??
medGetCursorCurrentPos:获得当前的光标位置
medSetFocus:设置某多行编辑控件获得输入焦点
medGetEditText:获得指向编辑文本的句柄
medGetTextLength:获得编辑文本的长度(以字节为单位)
medSetEditFont:设置编辑文本的字体
medGetModify:获得多行编译控件的Modify位
medSetModify:
medScrollUp:向上滚动一行
medScrollDown:向下滚动一行
medScrollPageUp:向上滚动一页
medScrollPageDown:向下滚动一页
medGetViewPos:
medSetViewPos:
medSetViewPosEx:
medAppendStr:
medAppendStrEx:
medDeleteStr:删除多行编辑控件中的内容
medActiveBackSpace:
medInsertStr:
medSetMaxCnt:
medGetMaxCnt:
练习:
3.6.1 用多行编辑控件和菜单实现一个简单的编辑器。 
3.6.2 用多行编辑控件输出<<三国演义>>的第一章。


lyman (懒人,懒得去想) 于Sun Nov 18 10:11:33 2001)
提到:

3.7滚动条控件
 滚动条控件提供了在多页之间进行滚动的手段,同时滚动条也可以用来进行输入(例如通
过拖动滚动条来确定一个数值)。  
  创建滚动条的函数为sbarNew(WORD id,int x,int y,int Width_Height,int Len,UINT 
iTotalSz,UINT iPageSz,WORD style),它的参数意义如下:
  id:控件的id值
  x,y:起始点的坐标
  Width_Height:对于水平的滚动条,表明高度,对于竖直的滚动条,表明宽度
  Len:对于水平的滚动条,表明宽度,对于竖直的滚动条,表明高度
  itotalSz:整个区域的范围(整个区域是个逻辑概念)
  iPageSz:一屏区域的范围(一屏区域也是个逻辑概念)
  style:说明滚动条的风格
   下面我们试着加入滚动条到程序中:
创建两个文件Scrobar.c和Scrobar.h,向Scrobar.c文件的消息处理函数中键入以下内容(
Scrobar.h中的内容和对expreap.c做的修改可参考前面的例子完成):
static BOOL Scrobar_Handler(SysMsg *pMsg)
{  static UIOBJ *pUI;

   if (MT_SYS_CONT_BEGIN==pMsg->type)
   {
      pUI=sbarNew(11,20,20,20,100,100,20, SBS_VERTICAL);
      ContAddObj(pUI);
      ContRedraw();
   }
}

   运行该程序,输出如图3.7.1所示:
   
   (图3.7.1)
  下面是与滚动条相关的消息及API
   相关消息:
   MT_SCROBAR_COMMAND:当用户点击滚动条时系统发出此消息
   MT_SCROBAR_POS_MOVE:当滚动条的位置移动时系统发出此消息
MT_SCROBAR_ON_TOP:当滚动条移动到顶部时系统发出此消息
   MT_SCROBAR_ON_BOTTOM:当滚动条移动到底部时系统发出此消息
   相关API:
     sbarNew:创建滚动条
     sbarSetTotalSize:重新设置滚动条的整个区域大小(逻辑值)
     sbarAddTotalSize:增加滚动条的整个区域大小
     sbarSubTotalSize:减少滚动条的整个区域大小
     sbarSetPageSize:设置滚动条一页的大小(逻辑值)
     sbarGetTotalSize:获得滚动条的总区域大小(逻辑值)
     sbarGetPageSize:获得滚动条一页的大小(逻辑值)
     sbarGetPos:    获得滚动条的位置
     sbarSetPos:     设置滚动条的位置
     sbarMovePage:  将滚动条向上或向下翻动一页
     sbarMoveLine:  将滚动条向上或向下翻动一行

练习:
3.7.1 使用滚动条来进行输入,即当用户拖动滚动条时,不断的显示滚动条的位置
问题:
3.7.1 滚动条控件与进度条控件有何不同?
3.8  树形图控件
  对于树形的信息,树形图是一种有效的显示手段。Penbex OS底层提供了对树形图控件的
支持。这一节里我们将演示如何创建一个简单的树形图。首先在Penbex OS SDK的工程中添
加TreeView.c和TreeView.h两个文件,然后向TreeView.c的消息处理函数中键入以下内容
:
 TREENODE Tree[]={
   {0,"First Tree"},

   {1,”1”},
   {2,”1.1”},
   {2,”1.2”},

   {1,”2”},
   {2,”2.1”},
   {2,”2.2”},
   {2,”2.3”},

   {0,},  //mark the ending of nodes,can’t be ignored!!
 };

 static BOOL TreeView_Handler(SysMsg *pMsg)
 {
   if (MT_SYS_CONT_BEGIN==pMsg->type) {
  ContAddObj(treeNew(11,10,10,100,10,OPEN_ALL,GM_FONT_SMALL,\
(TREENODE *)Tree));
  ContRedraw();
   }
    return 0;
 }
该程序显示了一个树形图。运行结果如下图所示:

    (图3.8.1)
(注意:若要在树形图中显示中文,调用treeNew时必须将字体设为中字体或大字体)
  在本节的最后,列出于树形图控件相关的API以及消息:
  相关消息:
  MT_TREE_COMMAND:当用户点击在树形图上时系统发出此消息
  MT_TREE_ITEM_FOCUS:当用户点击树形图中的条目时系统发出此消息
  相关API:
  treeNew:创建一个树形图
  treeRealHeight:返回树形图的实际高度
练习:
3.8.1 创建一个用户界面,使得用户可以选择输入树形图的各级节点数据,然后调用树形
图控件来显示用户选择的数据


lyman (懒人,懒得去想) 于Sun Nov 18 10:15:43 2001)
提到:

图形操作是现代操作系统必不可少的一部分,适当的图形可以使一个程序增色不少。作为
一个图形化的OS系统,Penbex OS提供了比较完善的图形操作界面,例如基本的绘图、截图
操作,以及选择不同的绘图模式。本章中我们将详细的介绍Penbex OS中的绘图功能。
4.1	基本绘图操作
   像大多数图形系统一样,Penbex OS中的图形部分也是由两部分组成。一部分是基本的
图形函数,例如画点、画线、画椭圆、设置绘图模式等等,另一部分是稍高一层的API,例
如区域的填充,抓图,对图形进行缩小、放大等等。本小节将介绍基本的绘图操作。
基本的绘图API包括GmSetPenWidth、GmSetPenPos、GmDrawDot、GmDrawLine、GmMoveTo、
GmLineTo、GmDrawRect,关于这些API的功能、参数和返回值请参考文档。
下面我们通过一个例子来介绍一下基本的绘图API。
在这个例子中,我们试图记录一个具有一定初速度的台球在160X160的桌子上(台球运动到
桌子边缘会反弹)不断反弹的轨迹(忽略摩擦),我们通过对Timer消息的处理来模拟台球
的匀速运动。在工程文件中的PDA AP目录下添加Snock子目录,在Snock目录下加入Snock.
c和Snock.h文件(Snock.h文件的内容这里不再给出,可参考附带的源码)
在Snock.c中键入以下内容:
/************************************************************************
   FileName:   Snock.c
   Author  :   Weyl   
************************************************************************/
#include “pbxall.h”
#include “Snock.h”

SnockVar   cSnockVar;
SnockVar  *pSnockVar;

Direction    OffsetVect={3,2};
Point        StartPos={20,45};
Point        PrePos={0,0};

static BOOL Snock_MsgHandler(SysMsg *pMsg)
{
    switch (pMsg->type) {
    case MT_SYS_CONT_BEGIN:
        EnableTimerMsg();
        SetTimerMsg(40);
        PrePos.x=StartPos.x;
        PrePos.y=StartPos.y;
        GmMoveTo(StartPos.x,StartPos.y);
        break;
    case MT_TIMER:
        if ( PrePos.x+OffsetVect.x>=0 && PrePos.x+OffsetVect.x<=159 )
            PrePos.x+=OffsetVect.x;
        else 
            OffsetVect.x*=-1;        
        
        if ( PrePos.y+OffsetVect.y>=0 && PrePos.y+OffsetVect.y<=159 )
            PrePos.y+=OffsetVect.y;
        else
            OffsetVect.y*=-1;

        GmLineTo(PrePos.x,PrePos.y);
        
        break;
    default:
        break;
    }
    return 0;
}

#ifdef WIN32
void SnockMain(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif
{
    SysMsg  msg;

    if (APP_RUN_NORMAL==argc) {
        SetContainerEntry(100,Snock_MsgHandler);
        SetActiveContainer(100);
        while (GetMsg(&msg)) 
            if (!SysProcess(&msg))
                AppProcess(&msg);
    }

    NOUSE(argv);
}

运行该程序,我们可以看到有趣的输出(若改变初始位置和初始速度,可以得到各种图案
)
在上面的例子中,我们用到了GmMoveTo函数,在需要完成类似”一笔画”的功能时,该函
数是非常有用的。当然,其他的函数也是经常用到的。
Note:在上面的程序中,我们并没有显式的调用GmSetPenWidth和GmSetPenMode的API,此
时系统会用默认的值
Remark:上面我们提到了GmSetPenWidth,注意这里的Pen不是用户点在Touch-Panel上的触
摸笔,而是图形系统中的一个逻辑概念,图形系统中的另一个重要的逻辑概念是刷子(Br
ush)。在画线,画椭圆,画矩形框时”调用”的是Pen,填充区域时”调用”的是Brush。

练习:
1.调用GmSetPenWidth函数改变笔的宽度,然后使用本小节中的一些基本的绘图API。
2. 调用GmSetPenWidth函数改变了笔的宽度后,再将笔的宽度设为原来的值。
3.完成一个速记的程序(即用户可以用笔在LCD屏幕上画出各种图形等),不许使用白板
控件。
问题:
1.Windows的图形系统中有没有Pen和Brush的概念,若有,试比较Windows系统中的Pen、
Brush与Penbex OS中的Pen、Brush。还有那些应用中也有Pen和Brush的概念?
2.参考有关图形系统的书籍,了解画线,画椭圆的一些算法。试写一个函数,通过调用G
mDrawDot来实现GmDrawLine的功能,并比较该函数与GmDrawLine的执行速度。(Hint:

通过Clock函数来记时)

4.2	其他绘图操作
   其他绘图操作包括对位图的操作(放大、缩小、抓图等)以及设置绘图的模式。这些A
PI主要是GmDrawBmp、GmMagnifyBmp、GmReduceBmp等。下面我们试图将一个Bmp图形放大一
倍,然后再缩回原状。
下面我们将具体实现该功能。我们的程序名为GraphicDemo。下面是GraphicDemo.c的源文
件:
/*****************************************************************************
\
   FileName: GraphicDemo.c
   Date    : 00\10\19
\*****************************************************************************
/
#include "pbxall.h"
#include "GraphicDemo.h"

GraphicDemoVar   cGraphicDemoVar;
GraphicDemoVar  *pGraphicDemoVar;

WORD Circle[]={
0x626D,0x0400,0x0014,0x0014,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,// 10
0x0000,0x0000,0x0000,0x0000,0xFFC0,0x0000,0x000F,0x003C,0x0000,0x0030,// 20
0x0003,0x0000,0x00C0,0x0000,0xC000,0x0300,0x0000,0x3000,0x0300,0x0000,// 30
0x3000,0x0300,0x0000,0x3000,0x0300,0x0000,0x3000,0x0300,0x0000,0x3000,// 40
0x0300,0x0000,0x3000,0x00C0,0x0000,0xC000,0x0030,0x0003,0x0000,0x000F,// 50
0x003C,0x0000,0x0000,0xFFC0,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,// 60
0x0000,0x0000,0x0000,0x0000,// 64
};

static MsgHandler(SysMsg *pMsg)
{
    BYTE *buf;
    BYTE *LargerBmp;
    BYTE *LittleBmp;

    switch (pMsg->type) {
    case MT_SYS_CONT_BEGIN:
        GmDrawBmp(0,0,(LBmp*)Circle);
        buf=MmNewP(GmCheckBmpSize(((LBmp*)Circle)->w,((LBmp*)Circle)->h));
        GmCaptureBmp(0,0,((LBmp *)Circle)->w,((LBmp *)Circle)->h,(LBmp *)buf);

        GmDrawBmp(20,20,(LBmp *)buf);
        LargerBmp=MmNewP(GmCheckBmpSize(40,40));
        GmCaptureBmp(0,0,40,40,(LBmp *)LargerBmp);        
        GmMagnifyBmp((LBmp *)LargerBmp,(LBmp *)buf);
        GmDrawBmp(40,40,(LBmp *)LargerBmp);
        LittleBmp=MmNewP(GmCheckBmpSize(20,20));
        GmCaptureBmp(0,0,20,20,(LBmp *)LittleBmp);
        GmMagnifyBmp((LBmp *)LittleBmp,(LBmp *)LargerBmp);
        GmDrawBmp(80,80,(LBmp *)LittleBmp);
        break;
    default:
        break;
    }
    return  0;
}

#ifdef WIN32
void GraphicDemoMain(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif
{
    SysMsg  msg;

    if (APP_RUN_NORMAL==argc) {
        SetContainerEntry(100,MsgHandler);
        SetActiveContainer(100);
        while (GetMsg(&msg))
            if (!SysProcess(&msg))
                AppProcess(&msg);
    }
}
上面的程序中我们用到了一个位图结构,即Circle数组所表示的数据。在我们的程序中,
Circle数组非常大,它是我们的一个工具程序所产生的(该工具程序负责将Windows系统下
标准的Bmp文件转换称为我们Penbex OS上的位图结构)。首先介绍一下如何通过该工具程
序生成上面的Circle数组。
   在Windows中用任何画图工具产生一个位图文件,大小为20X20像素,命名为Circle.bm
p,然后调用我们的工具程序Bmp2arr.exe,如下图所示:
    
上面有一个Format的选项,默认的是Gray,即按四阶灰度进行转化,若选择B/W,则按照黑
白两色进行转化。在Bitmap File栏中填入位图的路径及位图名,在Arry File栏中填入转
化后的Arr文件路径以及文件名,然后点击Start Convert按钮即可。下面我们看看circle
.txt中的内容:
C:\My Documents\My Pictures\Circle_Tmp.bmp
Colors : Gray
Width  : 20
Height : 20
WORD 
0x626D,0x0400,0x0014,0x0014,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,// 10
0x0000,0x0000,0x0000,0x0000,0xFFC0,0x0000,0x000F,0x003C,0x0000,0x0030,// 20
0x0003,0x0000,0x00C0,0x0000,0xC000,0x0300,0x0000,0x3000,0x0300,0x0000,// 30
0x3000,0x0300,0x0000,0x3000,0x0300,0x0000,0x3000,0x0300,0x0000,0x3000,// 40
0x0300,0x0000,0x3000,0x00C0,0x0000,0xC000,0x0030,0x0003,0x0000,0x000F,// 50
0x003C,0x0000,0x0000,0xFFC0,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,// 60
0x0000,0x0000,0x0000,0x0000,// 64
只需要将相应的内容Copy至代码中Circle数组的定义部分即可。
运行上面的程序,可以看到以下的结果:(一共有四个圆圈,按照由上至下的顺序,第一
个是从Circle数组直接绘制的,第二个是将LCD屏幕上从0,0至20,20的图像复制到一块缓存
区域中,然后绘制该缓存区域的内容,第三个是将缓存区域中的位图放大两倍并保存在另
一块较大的缓存区中,然后绘制该缓存区中的内容,第四个是将较大的位图缩小为20X20,
然后再进行绘制。)
 
在上面的程序中有几点需要注意的地方:
1.我们进行了位图的放大和缩小操作,但是仅仅调用了GmMagnifyBmp函数,这是因为GmM
agnifyBmp和GmReduceBmp函数事实上是一样的,所实现的功能都是将第二个参数(一个指
向Bmp结构的指针)所指向的位图数据缩小或者放大到第一个参数(也是指向Bmp结构的指
针)所指向的数据区域,至于究竟是实现放大还是缩小是有两个参数决定的。
例如,假如第二个参数为BmpA,其中((LBmp *)BmpA)->w=20,((LBmp* ))BmpA->h=20,第
一个参数为BmpB,并且((LBmp *)BmpB)->w=40,((LBmp *)BmpB)->h=40,这GmMagnifyBmp
和GmReduceBmp两个函数实现的都是放大的功能,反之则两个函数事实上实现的都是缩小的
功能。
  2.在我们调用GmMagnifyBmp((LBmp *)LargerBmp,(LBmp *)buf)语句之前首先调用了Gm
CaptureBmp(0,0,40,40,(LBmp *)LargerBmp)语句,请读者结合上面关于GmMagnifyBmp功能
的介绍想一下为什么要这样处理,有无其他的方法。

练习:
1.修改上一节的绘制台球轨迹的程序,结合本节介绍的绘图操作,实现绘制一个台球在球
台上不断运动、反弹的效果。(注意动画效果的实现)
问题:
1.将本节中的例子略作修改,先将一个位图缩小到原大小的一半,再通过对缩小后的位图
放大生成原样大小的位图,绘制出放大后得到的位图,与原图有何差别,为什么?在Wind
ows的画图程序中进行类似的操作,结果如何,为什么?什么格式的图形文件在上述的变换
下不会失真?
2.参考附录中关于LBmp格式的定义,编写一个用透明方式绘制的函数。(例如,该API为
DrawBmpInTransMode(int x,int y,LBmp *pData),调用DrawBmpInTransMode后,会在x,y
开始处以透明方式绘制一个位图,该位图数据是由pData指针指定的)


lyman (懒人,懒得去想) 于Sun Nov 18 10:16:07 2001)
提到:

Penbex OS系统内建立了文件系统。该系统中的文件名符合DOS中的8.3 规范,整个文件系
统的操作流程与标准的文件操作相同(也是遵循打开文件流,对文件流进行读写和关闭文
件流的过程)。关于Penbex OS上的文件系统,我们必须注意它对文件名是区分大小写的。
下面我们演示一下最基本的文件操作。
5.1	  基本文件操作
   我们试着在A盘建立一个Tst目录,然后在该目录下创建一个Tst.dat文件,向该文件中
写入”Hello,My First File”,然后读出内容并显示。
  首先在SDK的工程文件中加入Tst.c和Tst.h文件,然后在Tst.c的消息处理函数(这里是
Tst_Handler)中键入以下内容:
   char sHello[]=“Hello,My first file”;

   static BOOL Tst_Handler(SysMsg *pMsg)
{  static FHANDLE fd;
   static char buf[20];

   if (MT_SYS_CONT_BEGIN==pMsg->type) {
      FileNewDir(“A:\\Tst”);
      FileSetCurrentDir(“A:\\Tst”);
      fd=FileOpen(“Tst.dat”,\
FILE_FLAG_READWRITE|FILE_FLAG_CREAT,\
FILE_MODE_READ_WRITE);
      FileWrite(fd,sHello,sizeof(sHello));
      FileClose(fd);
      fd=FileOpen(“Tst.dat”,\
          FILE_FLAG_READONLY, FILE_MODE_READ);
      FileRead(fd,buf,sizeof(sHello)-1);
      FileClose(fd);
      GmTextOut(10,10,buf);
   }
   return 0;
}
   该程序首先在当前盘符下建立了Tst目录,然后在该目录下创建了Tst.dat文件并将字符
串szHello的内容写到了Tst.dat文件中,接着打开Tst.dat文件并读出了该文件中的内容,
同时打印到屏幕上。这里应该注意两个FileOpen函数所带的参数不同,创建Tst.dat时,参
数FILE_FLAG_READWRITE表明所创建的文件允许读和写的操作,FILE_FLAG_CREAT表明若Ts
t.dat文件不存在则创建它。
读出Tst.dat时,参数FILE_FLAG_READONLY表明对该文件只拥有读的权利。
   运行该程序,我们可以看到如下的输出:
  在上面的例子中,我们演示了基本的文件操作。事实上,上面源码中的一些处理是不大
妥当的。例如,在对文件流进行了操作后,并没有检查和处理返回值,而在用C语言设计程
序时,检查返回值是一种很重要的设计规范(尤其是在大的应用程序中)。因此,我们对
上面的代码作如下修改。
static BOOL Tst_Handler(SysMsg *pMsg)
{  static FHANDLE fd;
   static char buf[20];

   if (MT_SYS_CONT_BEGIN==pMsg->type) {
      FileNewDir(“A:\\Tst”);
      FileSetCurrentDir(“A:\\Tst”);
      fd=FileOpen(“Tst.dat”,\
FILE_FLAG_READWRITE|FILE_FLAG_CREAT,\
FILE_MODE_READ_WRITE);

          if ( fd>=0) {

      FileWrite(fd,sHello,sizeof(sHello));
        FileClose(fd);
        fd=FileOpen(“Tst.dat”,\
          FILE_FLAG_READONLY, FILE_MODE_READ);

        if ( fd>=0) {

        FileRead(fd,buf,sizeof(sHello)-1);
          FileClose(fd);
          GmTextOut(10,10,buf);
        }
        else 
          FloatBox(“Error reading file”,DELAY300ms,2);
      }
      else {
        FloatBox(“Error creating file”,DELAY300ms,GM_FONT_MIDDLE);
      }
   }
  return 0;
}

修改之后的代码中对文件流的操作更具有鲁棒性(robust)。
  为了在文件的任意位置进行随存取,Penbex OS提供了以下对文件指针的操作:FileSee
k,FileTell,FileRewind。其中FileSeek移动文件指针,FileRewind将文件指针重置到文
件开始位置,FileTell返回文件指针位置。
  在有的应用程序中,用户可能需要读出一个目录下的所有文件,这时我们需要用到以下
的文件API,FileGetFirst,FileGetNext和FileGetDone。例如,我们需要读出A:盘根目录
下所有后缀名为.log的文件,可以用如下的代码来实现:
DSINFO dsInfo;
        FileSetCurrentDir("\\");
        if (FileGetFirst(&dsInfo,"*.log")) //存在后缀为ini的文件
        {
            //print info of the first .log file
            PrintFileInfo(...);  // pseudo code
            while (FileGetNext(&dsInfo)) 
            {
                //print info of .log file
                PrintFileInfo(...);  //pseudo code
            }
        }
        FileGetDone(&dsInfo);
练习:
1.	定义一种关系数据库的文件格式,并通过文件操作将一些信息按照自定义的格式写到文
件中。
2.	从练习4.1.1生成的文件中读出数据。
问题:
1. 在Penbex OS上,要获得一个文件的长度信息,有几种方法,那一种最好,为什么?(
Hint:可以通过Clock()函数来进行耗时评估)
5.2  对Penbex OS中文件的一些说明
   Penbex OS中的文件在硬件上不是对应着硬盘,而是与RAM,FlashROM以及ROM相对应,由
于底层硬件的不同,文件的底层操作与基于硬盘(或软盘)的文件操作有一定的不同,但
是在开发者看来,所有对于文件的操作与通用的基于文件流的操作模式是一致的。
   模拟器中的文件操作:
   在Windows平台的模拟器上,所有与Penbex OS上的文件都是在内存中模拟的,当模拟器
关闭之后,所有在内存中进行模拟的文件都随着内存的释放而被释放(unretrievable)。
Penbex OS SDK专门为模拟器提供了两个API,GenWinFile和GenEmuFile,其中GenWinFile可
以产生Windows系统下的本机文件以供调试,而GenEmuFile可以将Windows系统下的文件写
入到模拟器的文件系统中。


lyman (懒人,懒得去想) 于Sun Nov 18 10:19:45 2001)
提到:

数据库是操作大量数据不可缺少的工具,目前数据库技术正在不断发展,面向对象的数据
库以及多媒体数据库都是当前正在研究的领域,但是目前应用最广泛的还是关系数据库,
因此Penbex OS上实现的数据库仍然是关系数据库。
6.1  在Penbex OS系统上创建一个简单的数据库
在Penbex OS上创建数据库与普通的创建数据库的操作没有什么本质上的区别,用户首先创
建一个数据库的结构(包含数据库的域的信息、内容等等,但并不包括具体的记录),然
后向该数据库中添加记录。同时,用户可以对数据库建立索引,并按照索引进行查找,也
可以对数据库进行添加,删除等常用的数据库操作。下面,我们试着建立一个小型的数据
库,然后对其进行常用的数据库操作。
 首先,在SDK的工程中加入Database.c和Database.h文件,然后向Database.c文件的消息
处理函数中键入以下的内容:
  DbField FieldStruct[]={
    {“ID”,’C’,6,0},
    {“Name”,’C’,12,0},
  };

static BOOL Database_Handler(SysMsg *pMsg)
{
  int iDBFld,size;
  DWORD dwDBFld;
char buf[20];

  if ( MT_SYS_CONT_BEGIN==pMsg->type) {
    DbInitial();
    DbCreateDb (“FirstDb”, FieldStruct,2); 
    iDBFld=DbOpenDb (“FirstDb”);
    DbAppend(iDBFld);
    DbWrite (iDBFld, 0, “000001”, 7);
    DbWrite (iDBFld,1,”Jesus”,Strlen(“Jesus”));
    DbAppend(iDBFld);
    DbWrite(iDBFld,0,”000002”,7);
    DbWrite(iDBFld,1,”Israle”,Strlen(“Israle”));
    DbCloseDb(iDBFld);
    
    dwDBFld=DbOpen(“FirstDb”);   
    DbRead (dwDBFld,1,buf,&size);
    DbCloseDb(dwDBFld);
    buf[size]=0;
    GmTextOut(10,10,buf);
   }
   return 0;
}
  在上面的程序中,我们创建了一个数据库,命名为FirstDb,指明该数据库的结构(有两
个域,第一个域为ID域,最大长度为六个字节,第二个域为Name域,最大长度为12个字节
),然后向该数据库中加入了两个记录,分别为{000001,”Jusus”}和{000002,”I
srale”},接着关闭该数据库。为了验证数据库中确实有上述两项记录,我们试着读出第
一条记录的第二个域并将其输出到LCD屏上。
运行该程序,我们可以看到如下的输出:  
(图5.1.1)
练习:
1.  调查一下使用ODBC的PC上,ODBC一般占用多少内存?
6.2  以表格的形式显示数据库
  在上一节中,为了检验数据库中确实写入了记录,我们读出了一条记录的一个域并打印
出来,事实上,Penbex OS中提供了专门用来显示数据库的控件Fld。
本节中我们将用Fld来实现数据库的显示。Fld控件提供了对于数据库的平面存储结构的平
面显示。
   对Database_Handler函数作如下修改:
char Width[]={80,80};
char Name[]={“ID”,”Name”};
FldDBDes DbDes={“FirstDb”,2,Width,Name};
FldDes
fdDes={0,&DbDes,GM_FONT_MIDDLE,3,2,0,FLD_STYLE_LINEMODE|FLD_STYLE_FRAME|FLD_ST
YLE_TITLE|FLC_SYTLE_RESERVE};

static BOOL Database_Handler(SysMsg *pMsg)
{
  int iDBFld,size; 
  DWORD dwDBFld;
char buf[20];

  if ( MT_SYS_CONT_BEGIN==pMsg->type) {
    DbInitial();
    DbCreateDb (“FirstDb”, FieldStruct,2); 
    iDBFld=DbOpenDb (“FirstDb”);
    DbAppend(iDBFld);
    DbWrite (iDBFld, 0, “000001”, 7);
    DbWrite (iDBFld,1,”Jesus”,Strlen(“Jesus”));
    DbAppend(iDBFld);
    DbWrite(iDBFld,0,”000002”,7);
    DbWrite(iDBFld,1,”Israle”,Strlen(“Israle”));
    DbCloseDb(iDBFld);
     
    ContAddObj(fldNew (11, &fdDes, 0, 30, 160, 120)); 
    ContRedraw();
   }
 return 0;
}
运行该程序,输出如下图所示:
 
(5.2.1)
在Fld控件中,程序员可以轻松的做出一个关系型数据库的平面视图。并且可以通过Fld这
个可视化控件来与用户进行交互。下面我们对上面的程序进行修正,使得用户每点击到一
条记录时,该记录都会被删除。为此,对上面的代码作如下修改:
static BOOL Database_Handler(SysMsg *pMsg)
{
  int iDBFld,size; 
  DWORD dwDBFld;
char buf[20];

  if ( MT_SYS_CONT_BEGIN==pMsg->type) {
    DbInitial();
    DbCreateDb (“FirstDb”, FieldStruct,2); 
    iDBFld=DbOpenDb (“FirstDb”);
    DbAppend(iDBFld);
    DbWrite (iDBFld, 0, “000001”, 7);
    DbWrite (iDBFld,1,”Jesus”,Strlen(“Jesus”));
    DbAppend(iDBFld);
    DbWrite(iDBFld,0,”000002”,7);
    DbWrite(iDBFld,1,”Israle”,Strlen(“Israle”));
    DbCloseDb(iDBFld);
     
    ContAddObj(fldNew (11, &fdDes, 0, 30, 160, 120)); 
    ContRedraw();
   }
  else if ( MT_FIELD_COMMAND==pMsg->type) {
    iDBFld=DbOpenDb(“FirstDb”);
    DbAppend(iDBFld);
    DbSeek(iDBFld,pMsg->data.w,DBSEEK_SET);
    DbDelete(iDBFld);
    DbCloseDb(iDBFld);
}
return 0;
}
练习:
1.	改写本节的程序,使得用户点中一条记录时,Fld控件能够立即更新显示。
(Hint:考虑ChangeActiveContainer函数)


lyman (懒人,懒得去想) 于Sun Nov 18 10:20:05 2001)
提到:

Penbex OS上提供了对网络的支持。一般PDA上网有两种方案,一是通过外置式Modem(有线
或无线),另一种是通过无线设备上网(内置或者外置)。Penbex OS SDK自带的模拟器提供
了对于串口的模拟,因此若有外置式Modem,则可以在SDK模拟环境下编写PDA上的网络应用
程序。在NetApi.h文件中列出了与网络有关的API。目前底层封装了PPP协议、SLIP协议、
TCP/IP协议以及在此之上的SMTP和POP3协议。通过外置式Modem上网的过程是这样的:首先
通过Modem拨号,与ISP建立PPP连接,连接建立后通过Socket与Internet交互。
    下面我们分析一个简单的网络拨号程序。
   #include “pbxall.h”
   #include “DialTst.h”

  static BOOL DialTst_Handler (SysMsg *pMsg)
{ 
  NETDIALINFO DialInfo;
  if (MT_SYS_CONT_BEGIN==pMsg->type) {
    DialInfo.lBaudRate=19200l;
    DialInfo.speaker=3;
    DialInfo.flowCtl=0;
    DialInfo.fTone=1;
    DialInfo. CInitStrA=“ATx1”;
    DialInfo. CAccountA=“169”;
    DialInfo. CPasswordA=“169”;
    DialInfo. CPhoneNoA=“9,169”;
    
    NetDial (&DialInfo,0);    
}
return 0;
}
在上面的程序中,我们首先填充了拨号信息DialInfo的各个域,其中lBaudRate代表波特率
,speaker代表Modem上喇叭的声音,fTone代表Modem的工作模式,CinitStrA代表Modem的
初始化字符串,CAccountA代表用户的账号,CpasswordA代表密码,CPhoneNoA代表所拨的
电话号码。执行该程序,模拟器通过操作系统的串口而控制Modem拨号至169。若NetDial成
功,则成功的通过ISP与Internet建立了连接,可以通过Socket编程来与Internet交互。

为了运行该程序,需要有一个外置式Modem连接到PC机的串口,并且在模拟器中有相应的设
置,具体过程如下:
运行模拟器,在模拟器上单击鼠标右键,在弹出菜单中选择Config,弹出如下对话框:

 
若外置式Modem接在Com1上,则将Set COM Port设为COM1,Set Baud设为57600,然后点击
OK,在模拟器上就可以运行上面的程序了。

上面的这个程序仅仅在链路层建立的链接,下面我们看一个稍复杂点的程序。程序名称为
NetTest。下面是NetTest.c文件的内容:
/*************************************************************************\
    FileName:  NetTest.c
    Date    :  00/10/19
\*************************************************************************/
#include "pbxall.h"
#include "NetTest.h"

NetTestVar   cNetTestVar;
NetTestVar  *pNetTestVar;

void DrawMsg(char netmsg)
{
	char DispMsg[50];
}

static BOOL MsgHandler(SysMsg *pMsg)
{
	int  iRet=0;
      if (iRet>=0) {	   
           //获取IP地址	   
           iRet=NetGetIpAddr (&ioctl, sizeof (NETIOCTL));	   
           iRet=NetGetHostByName("www.yahoo.com",&netHost);			
       }
       else 
           FloatBox("Dialing failed",DELAY1000ms,GM_FONT_MIDDLE); 
	}
}

#ifdef WIN32
void NetTest(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif
{
    SysMsg   msg;

    if (APP_RUN_NORMAL==argc) {
        SetContainerEntry(100,MsgHandler);
        SetActiveContainer(100);
        while (GetMsg(&msg))
            if (!SysProcess(&msg))
                AppProcess(&msg);
    }
}
在上面的例子中,我们并没有填充一个DialInfo数据结构,有关拨号的信息我们是直接从
系统的配置文件中获得的,下面演示一下如何配置系统的拨号信息:
在模拟器运行时,第一个程序是系统给定的,其程序名为Console(控制台),通过控制台
程序我们可以进行一些配置工作。首先点击模拟器桌面上的Console应用程序进入控制台,
然后切换到Network选项卡,可以看到如下的内容:
 
 上图中Speed为Modem的波特率,Speaker为Modem扬声器的音量,FlowControl为流控制方
式,DialType为拨号类型(一般为音频模式),Initial String为Modem的初始化字符串,
Account为ISP的账号,Password为ISP的密码,Phone为电话。在执行上面的程序以前,我
们必须配置上面的各项参数,将Initial字符串设为ATX1,Account设为169,Password设为
169,电话为9,169(逗号用来进行延时)。
上面程序中调用NetDial时第二个参数设为一个函数指针,该函数为回调函数,当拨号进行
到某个阶段时,NetDial函数会调用该回调函数,并传给一个char型参数用来表明当前拨号
所处的状态。
这个程序在调用了NetDial后会检测该函数的返回值,若返回值大于等于0,则获取ISP动态
分配的IP地址并调用域名解析API来解析www.yahoo.com的IP地址,若返回值小于0,说明拨
号失败,就给出出错信息。
若读者希望编写网络应用程序,我们的OS提供了标准的Socket函数库,使用这些Socket函
数库可以实现各种网络功能。
练习:
1.	通过调用NetDial函数拨号到附近的ISP,并通过检测返回值来判断拨号是否成功。

2.	通过标准的Socket函数,向远端服务器发送Socket数据包以获取时间。(Hint:通过
Time
端口实现,并且发送数据包之前调用GetIpAddress以获得ISP动态分配的IP地址并将该地址
封装到数据包的Head字段中)


lyman (懒人,懒得去想) 于Sun Nov 18 10:20:25 2001)
提到:

通讯是PDA应用中非常重要的一个方面。PDA上的通讯主要包括普通的串口通讯和红外通讯
两部分,这里给大家介绍一下串口通讯。
串口通讯的两种模式:
Penbex OS支持两种串口通讯的方式,立即方式和中断方式(事实上,这两种方式也是决大
多数串口通讯程序使用的方式)。并且Penbex OS的通讯部分对XModem和YModem协议进行了
封装。
下面我们将介绍两个通过Xmodem协议进行上传和下传的程序。首先我们实现程序Download
,下面是Download.c的内容:
   /*************************************************************************\

FileName:  Download.c
Date   : 00/10/10
   \*************************************************************************/

#include "pbxall.h"
#include "TstUART.h"

TstUARTVar  cTstUARTVar={0,};
TstUARTVar *pTstUARTVar;
UIOBJ  *_pmed;

static BOOL MsgHandler (SysMsg *pMsg) 
{
    static VHANDLE shData;    
    static UIOBJ *pLab,*_pmed;
    register BOOL retValue=1;
    register BYTE *pb;
    register DWORD CurrentSize;
    static DWORD _dwSize;

    switch(pMsg->type) {
    case MT_SYS_CONT_BEGIN:
        ContSetTitle ("Download Application:");
        ContAddObj (btnNewEx(333,5,138,70,16,"Back Home",BTNSTYLE_STRING,BTNST
RPOS_MIDDLE,2,BTN_KIND_BUTTON));
        ContAddObj (btnNewEx(334,80,138,70,16,"Download AP",BTNSTYLE_STRING,BT
NSTRPOS_MIDDLE,2,BTN_KIND_BUTTON));
        _pmed=medNew (31,3,30,140,8,EM_STYLE_NO_BORDER|EM_STYLE_NO_SHOWENTER,2
);
        ContAddObj (_pmed);
        ContRedraw ();

        medSetEditText (_pmed,">Download File\n");
        UrtSet (UART_MODE_NORMAL,UART_WAY_ON_BOTH,57600,8,1);
        _dwSize = 0;
        break;

    case MT_SYS_CONT_END:  
        break;
    case MT_BUTTON_COMMAND:
        switch (pMsg->id) {
        case 333:    
            AppReturnHome();
            break;
        case 334:
//  使用XModem协议来进行通讯(中断模式)
            UrtXmodemGet (1); 
            break;
        }
        break;
        case MT_GETXM_BEGIN:
            shData = MmNewH (_dwSize=0);
            medAppendStr (_pmed,"MT_GETXM_BEGIN\n"); 
uiRedraw(_pmed);
            break;
        case MT_GETXM_INIT_TIMEOUT:
            medAppendStr (_pmed,"MT_GETXM_INIT_TIMEOUT\n"); uiRedraw(_pmed);

            break;
        case MT_GETXM_FRAME_TIMEOUT:
            medAppendStr (_pmed,"MT_GETXM_FRAME_TIMEOUT\n"); uiRedraw(_pmed);

            break; 
        case MT_GETXM_CHKSUM_ERR:
            medAppendStr (_pmed,"MT_GETXM_CHKSUM_ERR\n"); uiRedraw(_pmed);
            break;
        case MT_GETXM_CANCEL:
            medAppendStr (_pmed,"MT_GETXM_CANCEL\n"); 
uiRedraw(_pmed);
            break;
        case MT_GETXM_RETRY_OVER:
            medAppendStr (_pmed,"MT_GETXM_RETRY_OVER\n");
uiRedraw(_pmed);
            break;
        case MT_GETXM_CONTINUE_TMOUT:
            medAppendStr (_pmed,"MT_GETXM_CONTINUE_TMOUT\n"); uiRedraw(_pmed);

            break;
        case MT_GETXM_DATA_COME:
            CurrentSize =_dwSize;
            _dwSize += pMsg->id;
            MmResizeH (shData,_dwSize);
            pb = (BYTE *)*shData+CurrentSize;
            Memcpy (pb,pMsg->data.p,pMsg->id);
            UrtXGetContinue ();
            break;
        case MT_GETXM_END:            
            medAppendStr (_pmed,"MT_GETXM_END\n\n"); uiRedraw (_pmed);
            pb = (BYTE *)*shData;            

            if (0<_dwSize) {
//   若下载了数据文件,可以用文件操作将数据写到文件中,这里是写到A:\\Demo1.txt

FHANDLE fd;
            	fd=FileOpen("A:\\Demo1.txt",FILE_FLAG_READWRITE|\
FILE_FLAG_CREAT,FILE_MODE_READ_WRITE);
            	FileWrite(fd,(BYTE *)pb,(short)Strlen((char *)pb));
            	FileClose(fd);
            }
            shData?(MmDelH(shData),shData=0):0;
            break;
        default: retValue=0; break;
    }
    return retValue;
}

#ifdef WIN32
void TstUARTMain(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif
{
	SysMsg  msg;
    NOUSE(argv);
}
将PDA连接到PC的COM口(若读者没有PDA,也可以在模拟器上进行调试,具体方法是将PC的
COM1和COM2通过串口线相连)运行该程序,在PC端我们通过超级终端来进行数据的发送,
在超级终端中串口通讯的参数设置如下:
 
问题:
1.	通过上面的程序来下载pbx文件,看看下载后的Demo1.txt的文件大小,与PC上的Pbx文
件大小一样吗?为什么?如何解决?
2.	通过串口通讯来下载pbx文件有没有上面的问题,为什么?(Hint:参考SDK中的exdesk
.c文件中相应的源代码和typedef.h中关于pbx文件格式的定义)


lyman (懒人,懒得去想) 于Sun Nov 18 10:20:50 2001)
提到:

9.1  通过MmNewP、MmDelP实现内存的分配、删除
   Penbex OS中的内存操作事实上与标准C的内存操作是非常类似的(事实上,我们可以直
接使用Malloc,Calloc等API),在Penbex OS上分配并使用内存的过程与通用的习惯是完
全吻合的。下面我们通过一个例子来演示Penbex OS上的内存操作,这里我们的例子程序的
源文件是DemoMem.c和DemoMem.h。下面我们看一下DemoMem.c文件中的内容:
/******************************************************************\
FileName:  DemoMem.c
Author  :  Weyl
\*****************************************************************/
#include “pbxall.h”
#include “DemoMem.h”

static BOOL MsgHandler(SysMsg *pMsg);

#ifdef WIN32
void DemoMemMain(DWORD argc,void *argv)
#else
void PenbexMain(DWORD argc,void *argv)
#endif
{   SysMsg  msg;

    if(APP_RUN_NORMAL==argc) {
        SetContainerEntry(100,conProcess);
        SetActiveContainer(100);
        while(GetMsg(&msg)) {
            if(!SysProcess(&msg))
                AppProcess(&msg);
        }
    }
}

static BOOL MsgHandler(SysMsg *pMsg)
{
   static char *prt;

   if ( MT_SYS_CONT_BEGIN==pMsg->type ) {
   //分配10K内存
     prt=MmNewP(10000);     
}
else if ( MT_SYS_CONT_END==pMsg->type) {
 //释放内存
   MmDelP(10000);
}
}

Note:关于内存分配,在SDK中,一个应用程序最多能够使用的内存大约是500K。
Remark:编写基于PDA的程序时,必须注意不能够有内存泄漏(因为PDA上内存一般比较小
)!!
问题:
1. 本节程序中MsgHandler函数中static char *prt语句里的static有何作用?
2. 若一个OS能够对无用内存块进行搜集(例如说Java),底层应如何实现?这个OS上调用
内存分配函数时系统应做些什么?Penbex OS是不是一个这样的操作系统?为什么?
3. 当我们创建控件时,系统底层事实上进行了动态内存分配,为什么退出程序时我们不必
将这部分内存释放?验证你的猜测。
4. 一般而言,OS管理内存(内存分配、内存删除)有那几种常用的实现方案?Penbex OS
使用的是那种方案?验证你的想法。
5. Penbex OS的文件系统是在物理内存(FlashROM或者RAM)中实现的,若Penbex OS上新
建立的一个100K的文件,那么OS可分配的内存空间是否会少100K?通过一段源代码证明之
。这样实现有什么好处?有什么坏处?
9.2  MmNewH 另一种分配内存的方法
前面我们介绍了如何通过MmNewP和MmDelP来进行内存分配和删除的操作,本节我们将介绍
如何用MmNewH来分配内存。首先,我们看一个在内存管理中普遍存在的问题,内存碎片(
Memory Fragments)。
下图是一段用户可以访问的内存:
 
在程序执行中,首先要求系统分配20K内存,分配之后,系统内存占用如下所示:(假设系
统分配内存时采用的算法为最先匹配算法)
 
 此时A区域的内存为程序占用,然后接着程序连续两次要求系统分配20K的内存,分配之后
系统内存占用如下所示:
 
此时A、B、C区域的内存均为程序占用,D区域的内存为空闲内存块,大小为10K
接着程序将B区域的内存释放,此时内存占用如下所示:
 
此时的内存空闲块为B、D两块,共有30K,但是若再要求系统分配一块介于20K和30K之间的
内存,则系统会发现没有一个单独的空闲块满足需求,这就是内存碎片问题。有的OS在遇
到这种问题时,简单的将内存分配函数返回0代表分配失败。但这样处理是不是最优的,我
们完全可以将内存块C的位置进行移动,使得A和C两块内存在物理地址上是连续的,然后就
可以分配一块20K以上、30K以下的内存了。
上面的方法听起来很容易,但事实上有这样一个问题:若内存块C被移动了,程序如何知道
这块内存已经被移动了?请读者思考一下有什么实现方法。
结论:1.为了减少内存碎片,对内存块的移动是不可少的(在内存分配、释放算法固定的
前提下)
2.如果一个内存块被移动了,必须确保程序以后仍然能够正确的访问这块内存,若这一点
无法做到,那么这块内存就不能够被系统移动
   
   我们看看在下面这种情况下如何使用内存:
   我们想做一个串口通讯程序来下载用户从PC端传来的文件并将该文件的内容存到PDA的
文件系统中,该文件的大小是未知的,并且该文件的大小并未写在文件头信息中(这就意
味着除非该文件传输完毕,否则我们是无法知道该文件大小的,从而也无法知道因该分配
多大的一块内存用来存储该文件的信息),因该如何实现?在前面讲解串口通讯的程序中
,我们了解到可以通过Xmodem协议用中断的方式来进行接收,在串口接收到一定量数据后
,会发给CPU一个中断,系统会trap到这个中断并且以消息的形式提供给程序开发者,这个
消息会告诉我们串口收到了多少个字节的数据。这里主要问题是如何分配大小适当的内存
用来存放文件的内容,以下两种方案都是可行的:
1.	每当收到MT_GETXM_DATA_COME消息时,通过消息的相关字段知道有多大的数据传了进来
,通过对内存块的Resize来获取足够大的内存
2.	分配大小固定的一块内存(例如5K),用这块内存作为Buffer,每当有数据进来时,若
Buffer未满并且Buffer的剩余空间足够容纳新接收的数据,则将数据放在Buffer中,否则
将Buffer中的数据写到Penbex OS的文件中(追加模式)清空Buffer,然后将新的数据放在
Buffer中
若我们选用第一种方案,那么我们必须能够对内存块进行Resize。一般而言,假设pData是
指向一块系统分配的内存块的指针,该内存块大小为m个字节,那么Resize(pData,n)
应该:(这里假设Resize是对内存块大小进行改变的函数,并且n>=m)
若pData+m至pData+n区域的内存完全未被其他程序占用,则占用该内存
若pData+m至pData+n区域的内存有任何部分被其他程序占用,则在系统中查找大小不小于
n个字节的空闲块,占用该空闲块并且释放以前pData所指向的空闲块,同时(非常重要的
),以某种形式返回新分配的内存块指针。
  下面说明一下Resize的情况:
  假设某个时刻内存占用情况如下图所示:
 
 A内存块大小为10K,C内存块大小为10K,A、C之间的空闲块大小为2K,B空闲块大小为50K,
现在欲将A内存块的大小变为20K,请读者想一下有几种方案。
   下面我们随便列出几种可能的方案
1.	将C内存块移动到其他位置,以便A内存块可以再扩充10K
2.	将C内存块向后移动8K,使得A内存块可以再扩充10K(事实上是方案一的特殊情况)

3.	在B空闲块中分配20K内存,将A内存块的内容Copy至B内存块的头部,将A内存块释放,
将新分配内存块的指针以某种形式返回
熟悉标准C库函数的读者可能知道第三种方案正是标准C的Realloc函数所使用的。

事实上,Penbex OS目前Resize内存的方法正是第三种方案,因为这种方案是最直观、最简
单的。
为了使用Penbex OS中的这种对内存进行Resize的功能,必须使用内存句柄(Handle)来进
行内存分配,相应的API为MmNewH。当我们调用了MmNewH后,系统会分配一块内存区,并且
返回该内存区的句柄(Handle),需要注意的是Handle并不是内存区的头指针,事实上,
Handle是一个指向内存区头指针的指针。例如我们调用了hHandle=MmNewH(1000)后,系统
的内存模块会分配1K的内存块,例如内存块A,而MmNewH返回的Handle是指向内存块A的指
针,如下图所示:
   我们应该如何访问内存块A呢?当然最直观的方法是将*hHandle作为指向内存块A首地址
的指针来使用,但我们要求使用MmLockH(hHandle)这个API,该API的返回值是指向内存块
A首地址的指针,然后对内存块A中的数据进行读写,最后调用MmUnlockH(hHandle)。
  问题:为什么要使用MmLockH和MmUnlockH的机制来访问内存块A呢?
为了回答这个问题,我们考虑一下前面得到的结论1:为了减少内存碎片,对内存块的移动
是不可少的(在内存分配、释放算法固定的前提下)。对于我们通过MmNewP分配的内存块
,Penbex OS系统是不会对其移动的(即使移动这些内存块可以消除内存碎片),大家可以
想想为什么,而对于通过MmNewH分配的内存块,系统为了减少内存碎片,是会对其进行移
动的!(移动后,系统会改变handle,使得内存句柄指向移动后内存块的首地址)
   这种做法的好处是系统的内存模块可以通过内存块移动来减少内存碎片,但是一个问题
是不一致性(inconsistency)。例如若用户通过*hHandle来访问内存块A,并且试图向内
存块A中写入一些数据,若正在数据写入的过程中,系统底层接收到了一个MT_XMODEM_DAT
A_COME消息,此时,系统需要分配一块缓存区以存取串口收到的数据,若最大的空闲内存
块长度不够,则系统需要进行内存移动的工作以获取较大的空闲内存块,这个时候,可能
内存块A会被移动!!!(因为通过MmNewH创建的内存块是可以移动的),读者可以想像这
样会发生什么情况。(即程序员试图对一块内存进行操作,而该块内存已经被移动了!!
!)。这也正是我们在操作通过MmNewH分配的内存时要遵循MmLockH,…,
MmUnlockH流程的原因。当我们调用了MmLockH时,系统会将该句柄所指定的内存块属性变
为不可移动(unremovable),然后我们可以对该块内存进行操作,在操作完成后,我们可以
调用MmUnlockH来将该内存块设为可以移动(Removable)(Guess Why?)。
   结论:
1.	为了创建一块可以改变大小的内存,我们必须调用MmNewH来通知系统分配一块内存
2.	由MmNewH来分配的内存都被认为是可以移动的(movable),因此,在访问这块内存时,
必须先调用MmLockH,然后对该内存块进行访问,最后调用MmUnlockH进行解锁
练习:
1.创建一个程序,首先分配10K的内存,然后根据Rand的返回值而对该内存块的大小进行
改变。(分别使用MmNewP、MmResizeP和MmNewH、MmResizeH两种方法,比较它们的差异)

2.当调用MmDelH和MmLockH时,系统底层会进行什么操作?
问题:
1.	什么时候用户会用到对内存块大小进行改变的操作?
2.	学习exdesk.c中关于内存句柄的使用方法,该程序中有什么问题?验证你的想法。

--

彻夜游荡,在这城市流浪,
无聊地只想把无聊解放。
思念不断籍着寂寞传染,
刹那间侵蚀了我的心房。
※ 来源:.饮水思源WWW bbs.sjtu.edu.cn. [FROM: 211.80.40.100] 

[返回上一页] [本讨论区]