WinAPI??【消息及相关结构体】

作者在 2009-07-02 22:02:46 发布以下内容
MSG结构

typedef struct tagMSG {

HWND           hwnd;     //这个消息所在的窗口句柄

UINT         message;     //消息标识符,如WM_SIZE、WM_COMMAND、WM_QUIT等等

WPARAM     wParam;     //32位消息的特定附加信息

LPARAM        lParam;     // 32位消息的特定附加信息

DWORD            time;    // /消息创建时的时间

POINT                   pt;   //消息创建时的鼠标位置

} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

 

GetMessage()

 

BOOL GetMessage

//从消息队列中“摘取”一个消息信息放到lpMsg所指的变量里;如果所取窗口的消息队列中没有消息,则程序会暂停在GetMessage 函数里,不会返回。

LPMSG lpMsg,  //传出参数、函数执行成功,从消息队列中“摘”取的一个消息信息会放入lpMsg所指的MSG结构变量中。

HWND hWnd ,  //传入参数。你要获取你程序中哪个窗口的消息,那就把相应的窗口句柄代入其中。

UINT wMsgFilterMin,//

UINT wMsgFilterMax);

//返回值:如果取的是WM_QUIT消息,则返回值为0,如果取的是其它消息,返回值为非0值。

//WM_QUIT消息是退出程序的消息。当我们想让程序退出时,我们就可以发一个WM_QUIT消息,让GetMessage返回0值。


 

TranslateMessage()

 

BOOL TranslateMessage( CONST MSG *lpMsg);

//对GetMessage取得的MSG消息结构中的信息进行必要的预处理。

//GetMessage函数取得的消息,要经过TranslateMessage处理一下,才可以传给DispatchMessage函数 。

//因此,TranslateMessage必放在GetMessage与DispatchMessage之间。


DispatchMessage()

 

LONG DispatchMessage( CONST MSG *lpMsg);

//用来完成调用WinPro回调函数并把由GetMessage取得的消息结构MSG变量中的信息传递给WinPro回调函数。

 

 

鼠标消息

随着 Windows 操作系统的流行,鼠标因为其精确定位和操作方便的优点而成为计算机不可缺少的输入设备。

鼠标的基础知识

 1 .鼠标操作和鼠标消息用户在使用鼠标操作的过程中,经常会使用的主要方式有五种 ,如表所示。

操作名称 描述
单击(Click) 按下并迅速释放鼠标按钮
双击(Double Click) 连续快速完成两次单击操作
移动(Move) 鼠标光标移动
拖动(Drag) 按下鼠标一键不放,同时执行鼠标移动操作
与键盘的特殊键组合 在按下Ctrl键或Shift键的同时执行鼠标单击操作

 

其中,前三种操作是最为基本的操作,可以产生Windows内部定义的消息,并通过这些消息来判断用户具体执行了哪种操作。


Windows定义的鼠标消息共有20条,其中非编辑区的鼠标消息一般交由系统处理,程序只处理编辑区内的鼠标消息。编辑区内的鼠标消息共有10条,如表所示。

消息常量 操作描述 消息常量 操作描述 消息常量 操作描述
WM_MOUSEMOVE 移动鼠标
WM_LVBUTTONDOWN 按下鼠标左键 WM_RVBUTTONDBLCLK 按下鼠标右键 WM_MVBUTTONDOWM 按下鼠标中键
WM_LBUTTONUP 释放鼠标左键 WM_RBUTTONUP 释放鼠标右键 WM_MBUTTONUP 释放鼠标中键
WM_LBUTTONDBLCLK 双击鼠标左键 WM_RBUTTONDBLCLK 双击鼠标右键 WM_MBUTTONDBLCLK 双击鼠标中键

 

对于前表所列的鼠标操作中的最后两种,不能直接使用Windows定义的消息来判断,只能通过编程,将多种消息和数据组合之后判断。

例如,判断用户是否按下鼠标左键之后进行拖动操作可以通过case语句来实现:

case WM_MOUSEMOVE:

if (wParam&MK_LBUTTON) //只处理鼠标拖动的消息
{ …… // 处理程序
}

wParam参数中保存了在消息产生时其他操作进行的状态;用户可以通过位屏蔽操作来判断在该消息产生的同时,其余操作是否正在进行。这正是在程序中判断复杂鼠标操作的基本方法。例如,上面判断拖动操作的程序段就用了位操作 wParam& MK_LBUTTON, 判断在鼠标移动(WM_MOUSEMOVE)的同时鼠标左键是否同时被接下。如果,鼠标左键同时按下,则位操作的结果为TRUE,说明当前操作为拖动操作,程序可以继续进行下一步处理。又如需要判断单击鼠标左键时是否同时按下了Ctrl键或Shift键,可以用以下程序段来处理:

case WM_ LBUTTONDOWN:
if(wParam& MK_CTROL)
{//Ctrl键同时按下
  if (wParam&MK_ SHIFT)
  {// Ctrl 键和Shift键都同时按下
    …… // 处理程序
  }
  else { // Ctrl健同时按下,但 Shift键没有被按下
    ……. // 处理程序
  }
}
else if(wParam&MK_ SHIFT)
{ // Shift键同时按下,但 Ctrl键没有被接下
  …… // 处理程序
}
else
{// Shift 键和Ctrl键都未按下
  …… // 处理程序
}

 

lParam参数保存了消息产生时鼠标所在点的坐标,其中低16位为X坐标,高16位为Y坐标。

在处理鼠标消息的时候,如果需要处理鼠标双击消息,则在注册窗口类时,窗口的风格必须包括CS_DBCLCKS。否则即使执行了双击操作,窗口也只能收到两条WM_ BUTTONUP和 WM_BUTTONDOWN消息。区分双击操作和两次单击操作是以两次击键的时间间隔为标准的。当两次击键的时间间隔小于 500毫秒时, Windows将其视为双击操作:如果两次击键的时间间隔大于500毫秒,Windows将其视为两次单击操作。500毫秒为默认的时间间隔,用户可以通过调用SetDoubleClickTime函数来修改这一时间间隔。

SetDoubleClickTime函数的原型定义如下:
BOOL SetDoubleClickTime(UINT uInterval // 新的击键时间间隔)

2.鼠标捕捉

在通常情况下,只有当鼠标位于窗体内时,窗体才能接收到鼠标的消息。如果需要接收所有的鼠标消息而不论鼠标是否在窗口内,这时可以调用SetCapture函数来实现。
SetCapture函数的原型定义如下:

HWND SetCapture (
  HWND hwnd // 窗口句柄
);

调用SetCapture函数后,所有鼠标操作所产生的消息都直接发送到指定窗口。因为此时鼠标可能位于窗口之外,所以鼠标的坐标可能为负值。由于调用该函数会使其他窗口不能接收到键盘和鼠标的消息,因此在完成操作后应及时调用ReleaseCapture 函数释放鼠标捕获。

ReleaseCapture函数的原型定义如下:

BOOL ReleaseCapture(VOID);

 

 

键盘基础

Windows程序获得键盘输入的方式:键盘输入以消息的形式传递给程序的窗口过程。实际上,第一次学习消息时,键盘就是一个明显的例子:消息应该传递给应用程序的信息类型。
Windows用8种不同的消息来传递不同的键盘事件。程序可以忽略其中至少一半的消息而不会有任何问题。在大多数情况下,这些消息中包含的键盘信息会多于程序所需要的。处理键盘的部分工作就是识别出哪些消息是重要的,哪些是不重要的。

键盘基础知识:

用键盘当作输入设备,每当用户按下或释放某一个键时,会产生一个中断,该中断激活键盘驱动程序KEYBOARD.DRV来对键盘中断进行处理。 KEYBOARD.DRV程序会根据用户的不同操作进行编码,然后调用Windows用户模块USER.EXE生成键盘消息,并将该消息发送到消息队列中等候处理。

1.扫描码和虚拟码

扫描码对应着键盘上的不同键,每一个键被按下或释放时,都会产生一个唯一的扫描码作为本身的标识。扫描码依赖于具体的硬件设备,即当相同的键被按下或释放时,在不同的机器上可能产生不同的扫描码。在程序中通常使用由Windows系统定义的与具体设备无关的虚拟码。在击键产生扫描码的同时,键盘驱动程序KEYBOARD.DRV截取键的扫描码,然后将其翻译成对应的虚拟码,再将扫描码和虚拟码一齐编码形成键盘消息。所以,最后发送到消息队列的键盘消息中,既包含了扫描码又包含了虚拟码。

2.输入焦点

同一时刻,Windows中可能有多个不同的程序在运行,也就是说有多个窗口同时存在。这时,键盘由多个窗口共享,但只有一个窗口能够接收到键盘消息,这个能够接收键盘消息的窗口被称为拥有输入焦点的窗口。拥有输入焦点的窗口应该是当前的活动窗口,或者是活动窗口的子窗口,其标题和边框会以高亮度显示,以区别于其他窗口。拥有输入焦点的也可以是图标而不是窗口,此时,Windows也将消息发送给图标,只是消息的格式略有不同。

窗口过程可以通过发送WM_SETFOCUS和 WM_KILLFOCUS消息使窗体获得或失去输入焦点。程序也可以通过捕获WM_SETFOCUS和WM_KILLFOCUS消息来判断窗体何时获得或失去输入焦点。其中WM_SETFOCUS消息表示窗口正获得输入焦点,WM_ KILLFOCUS消息表示窗口正失去输入焦点。

3.键盘消息

键盘消息分为系统键消息和非系统键消息。系统键消息是指由Aft键和其他键组合而产生的按键消息。当系统键被按下时产生WM_ SYSKEYDOWN消息,当系统键被释放时产生WM_SYSKEYUP消息。 Aft键与其他键形成的组合键通常用于对程序菜单和系统菜单进行选择,或用于在不同的程序之间进行切换。因此,系统键消息应该交由Windows进行处理,用户所编制的程序一般不处理系统键消息,而是将这些消息交由DefWindowProc函数进行处理。如果用户想对系统键消息进行处理,应该在处理完这些消息后,再将其发送给DefWindowProc函数,使得Windows系统能够正常工作。

某些击键消息可以被转换成字符消息,例如字母键、数字键等。而有些键只能产生按键消息而没有字符消息,例如 Shift键、Insert键等。消息循环中的 TranslateMessage函数可以实现从击键消息向字符消息的转化。当GetMessage函数捕获一个WM_SYSKEYDOWN消息或 WM_KEYDOWN消息后,TranslateMessage函数判断产生该消息的键是否能够被转换成字符消息,如果能,就将该消息转换成字符消息,再通过DispatchMessape函数将转换后的字符消息发送到消息队列中去。字符消息共有以下四种,如表所示。

 

字符 系统字符 非系统字符
普通字符 WM_SYSCHAR WM_CHAR
死字符 WM_SYSDEADCHAR WM_DEADCHAR

 

其中死字符是由某些特殊键盘上的按键所造成的,Windows一般忽略死字符所产生的消息。

Windows的消息一般是通过一个MSG结构体变量传送给消息处理函数的。对于键盘消息, MSG结构体变量的各个域中较重要的是lParam域和 wParam域。

wParam域:对于非字符消息,wParam域保存按键的虚拟健代码;对于字符消息, wParam域保存字符的ASCII码。

lParam域:32位的lParam变量被分为六部分,记录了以下相关信息:重复次数、OEM扫描码、扩展键标志、关联键标志、前一击键状态和转换状态。

lParam域各位的含义如表所示。

 

位数 含义
0-15 击键重复次数累加
16-23 OEM扫描码
24 是否为扩展键
25-28 未定义
29 是否便用关联键,及Alt键是否同时按下
30 前一次击键状态,0表示该键前一次状态为抬起,1表示前一次状态为按下
31 转换状态

 

按键的次序不同,产生的消息也不相同。例如,按下并释放1键,读过程依次产生如表所示三条消息。按下1键所产生的消息和wParam的取值

消息??????? wParam变量取值
WM_KEYDOWN??虚拟码1
WM_CHAR????ASCII码“1”
WM_KEYUP ????虚拟码1

如果按下Shift键后再按下1键并释放,则依次产生如表所示的消息。按下 Shift键后按 1健所产生的消息和 wParam的取值

消息??????? wParam变量取值
WM_KEYDOWN ??虚拟码 VK_SHIFT
WM_KEYDOWN ??虚拟码 VK_1
WM_CHAR ASCII??码 “1”
WM_KEYUP ????虚拟码 VK_1
WM_KEYUP???? 虚拟码 VK_SHIFT

 


WM_SIZE消息

wParam域:保存了窗体新尺寸的左上角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。

lParam域:保存了窗体新尺寸的右下角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。

在编程过程中,通常通过LOWORD宏定义来获得32位变量的低16位数值,通过HIWORD宏定义来获得32位变量的高历位数值。

基础知识 | 阅读 4591 次
文章评论,共5条
vfdff(作者)
2009-07-02 22:03
1
消息结构体MSG操作系统通过消息机制将感知到事件传递给应用程序的,操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序.MSG结构定义如下:<br />
typedef struct tagMSG{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HWND hwnd;&nbsp;&nbsp;窗口句柄(索引内存当中的资源)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UINT(无符号整型)&nbsp;&nbsp;message;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WPARAM(整型) wParam; //关于消息的附加参数,例如:按下一个键,具体是哪个按键<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPARAM(整型) LParam; //关于消息的附加参数<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DWORD(32位整数) time;//消息被传递时候的时间<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POINT(坐标结构体) pt; //消息被传递时候,光标在屏幕上的位置 <br />
} MSG;<br />
<br />
句柄(HANDLE),资源的标识.按资源的类型,将句柄细分成:图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)<br />
宏定义: typedef int WIDTH<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WIDTH x;
vfdff(作者)
2009-07-02 22:13
2
MSG包含了消息的信息。<br />
<br />
那些程序或操作系统发送的信息都被MSG结构体所描述。<br />
<br />
结构体原型<br />
typedef struct{<br />
HWND hwnd;<br />
UINT message;<br />
WPARAM wParam;<br />
LPARAM lParam;<br />
DWORD time;<br />
POINT pt;<br />
} MSG, *PMSG;<br />
<br />
一条消息包含了6个信息<br />
<br />
第一个 HWND hwnd<br />
Handle to the window whose window procedure receives the message. hwnd is NULL when the message is a thread message.<br />
接收消息的程序的句柄。 <br />
<br />
<br />
<br />
第二个 UINT message<br />
Specifies the message identifier. Applications can only use the low word, the high word is reserved by the system.<br />
用来标注消息号,应用程序只处理低位的,系统处理高位的。<br />
<br />
<br />
<br />
第三个 WPARAM wParam<br />
Specifies additional information about the message. The exact meaning depends on the value of the message member.<br />
消息的参数,不同的消息具有不同的含义。<br />
<br />
<br />
第四个 LPARAM lParam<br />
同上<br />
<br />
<br />
<br />
第五个 DWORD time<br />
Specifies the time at which the message was posted.<br />
消息发送的时间。<br />
<br />
<br />
<br />
第六个&nbsp;&nbsp;POINT pt<br />
Specifies the cursor position, in screen coordinates, when the message was posted.<br />
消息发送时,鼠标的位置。
vfdff(作者)
2009-07-02 22:19
3
消息队列中没有消息GetMessage是不会返回的。和GetMessage不同,PeekMessage是不阻塞,立即返回。<br />
<br />
GetMessage函数在没有消息时的确是阻塞的,不消耗CPU,这是书上明确说明的。
vfdff(作者)
2009-07-02 23:16
4
 int readpos, writepos;   // 邮寄消息缓冲区的当前读取和写入位置<br />
  <br />
    /*<br />
     * One thread can only support eight timers.<br />
     * And number of all timers in a MiniGUI applicatoin is 16.<br />
     */<br />
    HWND TimerOwner[8];     // 定时器所有者<br />
    int TimerID[8];      // 定时器标识符<br />
    BYTE TimerMask;       // 已使用的定时器掩码<br />
  } MSGQUEUE;<br />
  typedef MSGQUEUE* PMSGQUEUE;<br />
  <br />
  可以看出,在 MiniGUI 的消息队列定义中,只有邮寄消息的定义类似清单 2 中的线性循环队列。上面提到,通知消息类似邮寄消息,但该消息是不允许丢失的,因此,该消息通过链表形式实现。PMSG 结构的定义也很简单:<br />
  <br />
  typedef struct _QMSG<br />
  {<br />
    MSG         Msg;<br />
    struct _QMSG*    next;<br />
    BOOL        fromheap;<br />
  }QMSG;<br />
  typedef QMSG* PQMSG;<br />
  <br />
  用于同步消息传递的数据结构为 SYNCMSG,该结构在消息队列中也形成了一个链表,但该结构本身稍微复杂一些:<br />
  <br />
  typedef struct _SYNCMSG<br />
  {<br />
    MSG       Msg;<br />
    int       retval;<br />
    sem_t      sem_handle;<br />
    struct _SYNCMSG* pNext;<br />
  }SYNCMSG;<br />
  typedef SYNCMSG* PSYNCMSG;<br />
  <br />
  可以看到,该结构中有一个信号量,该信号量就是用来通知同步消息的发送线程的。当接收并处理同步消息的线程处理该消息之后,将在 retval 成员中存放处理结果,然后通过 sem_handle 信号量唤醒同步消息的发送线程。<br />
  <br />
  在上述消息队列结构的定义中,还有两个分别用来实现互斥访问和同步的成员,即互斥锁 lock 和信号量 wait。互斥锁 lock 用来实现不同线程对消息队列的互斥访问,比如在获取邮寄消息时的操作如下:<br />
  <br />
      pthread_mutex_lock (&amp;pMsgQueue-&gt;lock);<br />
      if (pMsgQueue-&gt;readpos != pMsgQueue-&gt;writepos) {<br />
  <br />
        pMsgQueue-&gt;readpos++;<br />
        if (pMsgQueue-&gt;readpos &gt;= pMsgQueue-&gt;len) pMsgQueue-&gt;readpos = 0;<br />
  <br />
        pthread_mutex_unlock (&amp;pMsgQueue-&gt;lock);<br />
        return 1;<br />
      }<br />
      else<br />
        pMsgQueue-&gt;dwState &amp;= ~QS_POSTMSG;<br />
  <br />
      pthread_mutex_unlock (&amp;pMsgQueue-&gt;lock);<br />
  <br />
  <br />
  <br />
  信号量 wait 用来同步消息循环。一般来说,一个线程在建立窗口之后,要进入消息循环持续地从消息队列中获取消息(通过 GetMessage() 函数)。当消息队列中没有任何消息时,该线程将进入休眠状态,而当其他线程将消息邮寄或发送到该消息队列之后,将通过信号量 wait 唤醒该线程:<br />
  <br />
    sem_getvalue (&amp;pMsgQueue-&gt;wait, &amp;sem_value);<br />
    if (sem_value == 0)<br />
      sem_post(&amp;pMsgQueue-&gt;wait);<br />
  <br />
  在 MiniGUI 的消息队列结构中,第一个成员是消息队列的状态字。该状态字通过标志位表示如下状态:<br />
  <br />
  消息队列中是否有邮寄消息;<br />
  消息队列中是否有通知消息;<br />
  消息队列中是否有同步消息;<br />
  消息队列中是否有退出消息;<br />
  消息队列中是否有重绘消息;<br />
  消息队列中是否有定时器消息。<br />
  通过这些标志,GetMessage() 可判断是否需要检查邮寄消息队列、通知消息链表和同步消息链表等等。同时,利用这些标志还可以处理上面提到的一些特殊消息。这里以定时器为例进行说明。<br />
  <br />
  在 MiniGUI 中,一个创建了窗口的线程一般拥有一个消息队列,使用该消息队列所有窗口,包括子窗口在内,一共可以建立 8 个定时器。这些定时器是否到期,体现在消息队列的状态字上――状态字的最低 8 位分别用来表示这 8 个定时器是否到期。消息队列中同时还有三个成员:<br />
  <br />
    HWND TimerOwner[8];     // 定时器所有者<br />
    int TimerID[8];      // 定时器标识符<br />
    BYTE TimerMask;       // 已使用的定时器掩码<br />
  <br />
  其中 TimerMask 表示当前有效的定时器,每位表示一个定时器;TimerID 表示这 8 个定时器的标识符(整数);而 TimerOwner 则表示定时器的所有者(窗口句柄)。这种定时器的实现方法类似 Linux 内核中的信号实现。定时器是否有效以及是否到期均由二进制字节的一个位来表示。当 GetMessage 检查这些标志时发现有某个定时器到期才会获得一个定时器消息。也就是说,定时器消息是不排队的。这样就解决了排队时可能塞满消息队列的问题。
vfdff(作者)
2009-07-02 23:31
5
在linux上模拟uCOS-II实时操作系统的实现 <br />
 <br />
2009-1-16 13:20:00 张森  供稿 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;摘要:uCOS-II是一个短小而功能强大的实时嵌入式操作系统。在Jean J.Labrosse先生所著.由邵贝贝教授翻译的Micro/uCOS-II THE REAL-TIME KERNEL(Second Edition)一书中对这一操作系统作出了精彩的讲解,该书是一部关于uCOS-II操作系统的经典教材,同时在书中提供了关于uCOS-II在windows环境下的移植的4个范例。本文对其第一个范例作一个在redhat linux9.0上的移植版本。移植的工作主要集中在三个方面,下文将分为三章,结合代码详细介绍。文章的最后将介绍我的试验平台并演示我的试验结果。<br />
<br />
  1 引言<br />
<br />
  uCOS-II是一个短小而功能强大的实时嵌入式操作系统。在Jean J.Labrosse先生所著.由邵贝贝教授翻译的Micro/uCOS-II THE REAL-TIME KERNEL(Second Edition)一书中对这一操作系统作出了精彩的讲解,该书是一部关于uCOS-II操作系统的经典教材,同时在书中提供了关于uCOS-II在windows环境下的移植的4个范例。本文对其第一个范例作一个在redhat linux9.0上的移植版本。移植的工作主要集中在三个方面,下文将分为三章,结合代码详细介绍。文章的最后将介绍我的试验平台并演示我的试验结果。<br />
<br />
  2 字符串的显示<br />
<br />
  字符串显示函数PC-DispStr在文件pc.c中,这个文件本身不是uCOS-II的一部分。它的主要工作是建立一系列的功能函数来发挥PC机的强大功能,并被测试代码所调用。<br />
<br />
  2.1 设置前景色和后景色<br />
<br />
  我们使用类似于prinf(&quot;33[30m&quot;)的语句来设置颜色.转义序列就是一个让shell执行一个特殊步骤的控制指令。转义序列通常都是以ESC开头(这也是它的命名原因)。在sh <br />
ell里表示为︿[。这种表示法需要一点时间去适应, 也可以用33完成相同的工怍(ESC的ASCII码用十进制表示就是27,=用八进制表示的33)。33声明了转义序列的开始,然后是[开始定义颜色。下面我们要选择前景色(这里是32,代表绿色)。背景色的40表示黑色。要是不想让提示符后面的文字也变成绿色,我们用33[0m关闭转义序列,33[0m是shell的默认颜色。前景色和背景色都有8种可用的选择。可选颜色:红色、绿色、黄色、蓝色、洋红、青色和白色。他们对应的颜色代码是:30(黑色)、31(红色)、32(绿色)、33(黄色)、34(蓝色)、35(洋红)、36(青色)、37(白色)。用同样色方法设置背景色,不过要把第一个数字&quot;3&quot;替换成&quot;4&quot;,例如40、41、42、43、44、45、46、47。虽然在这里可以按照上面介绍的对应关系定义修改在pc.h中定义的前景色和后景色的宏,使对应关系更加明确。(注意:他的后面一位表示前景色,前面一位表示后景色),但是我们在这里的设计思路是尽量不对原书中的代码作改动,所以在函数的实现中直接使用switch语句,对相应的前景色和后景色 。(linux的shell只支持以上几种颜色)<br />
<br />
  switch (color&amp;0xF0) /*查看前景色*/<br />
<br />
  { case DISP_FGND_BLACK: printf(&quot;33[30m&quot;);break;<br />
<br />
  ……<br />
<br />
  }<br />
<br />
  switch(color&amp;0x0F) /*查看后景色*/<br />
<br />
  { case DISP_BGND_BLACK: prinf (&quot;33[40m&quot;);<br />
<br />
  break;<br />
<br />
  ……<br />
<br />
  }<br />
<br />
  2.2跟踪光标的位置<br />
<br />
  我使用printf(&quot;33[%u;%uH&quot;,y+1,x+1)来跟踪光标的位置。33是声明了转义序列的开始,上文已经介绍,不再累叙,[y;xH是设置光标位置的格式。x和y分别表示横轴和纵轴。<br />
<br />
  3 键盘输入<br />
<br />
  键盘输入函数PC_GetKey在windows环境下,由于有库函数kbhit返回最近所敲的按键.就很容易实现。而在linux环境下我们需要构造自己的kbhit,在参考文献2中John.Wiley.Sons先生提供了一种现成的实现方法(这个方法会阻塞read函数.在本文中并不适用),这里我们使用了另外的一种实现方法,下面介绍给出其实现代码.<br />
<br />
  int kbhit(void){<br />
<br />
  struct timeval tv;<br />
<br />
  fd_set readFd;<br />
<br />
  struct termios newKbdMode;<br />
<br />
  if(!inited){<br />
<br />
  newKbdMode.c_lflag&amp;=~(ICANON | ECHO);<br />
<br />
  newKbdMode.c_cc[VTIME]=0;<br />
<br />
  newKbdMode.c_cc[VMIN]=1;<br />
<br />
  tcsetattr(0,TCSANOW,&amp;newKbdMode);<br />
<br />
  atexit(rekbd);<br />
<br />
  inited=1;<br />
<br />
  }<br />
<br />
  tv.tv_sec=0;<br />
<br />
  tv.tv_usec=0;<br />
<br />
  FD_ZERO(&amp;readFd);<br />
<br />
  FD_SET(STDIN_FILENO,&amp;readFd);<br />
<br />
  select(1,&amp;readFd,NULL,NULL,&amp;tv);<br />
<br />
  if(FD-ISSET(STDIN-FILENO,&amp;readFd))<br />
<br />
  return 1;<br />
<br />
  else<br />
<br />
  return 0; <br />
<br />
  }<br />
<br />
  3.1 控制台的初始化<br />
<br />
  首先,这里使用了全局变量inited,它是一个初始化与否的标记.因为函数kbhit将被多次调用,而初始化只需要做一次.这样.当发现inited置1以后,就不会去做重复性的初始化工作了。如果inited为0,就需要对控制台(键盘)做初始化工作,这里定义了内核结构体termios类型的变量newKbdMode,我们需要对这个结构体的两个成员c_lflag和c_cc进行初始化,代码中对c_lflag的设置表示终端为不回显的非标准模式。c_cc[VTIME]=0,c_cc[VMIN]=1表示读函数会等待.直到出现1个键盘输入为止。(关于这个结构体的详细分析,可参阅参考文献2的第5章)。然后再调用tcsetattr把设置的值写入。最后,函数atexit将在3.3节详叙。<br />
<br />
  3.2 检测键盘的输入<br />
<br />
  在这里我们使用宏FD_ZERO把内核的结构体readFd清0.用宏FD_SET把标准输入的文件描述符STDIN_FILENO和readFd关联,然后用select函数来监测输入.他只关注一个描述符,所以第一个参数为1,第二个参数为上面的readFd,后面的两个参数表示是否关注标准输出和出错的文件描述符,我们不要,所以置0.最后一个参数表示超时时间,我们不需要,所以置0。经过以上的处理后,如果有输入时.宏FD_ISSET就会返回非0值。我们就知道键盘上有输入。<br />
<br />
  3.3 系统退出<br />
<br />
  在windows环境下使用了成对的函数PC_DOSSaveReturn()和PC_DOSReturn。前一个保存DOS的状态,后一个在退出时前调用.恢复保存的DOS状态。而在linux下,表面看来我仅使用函数exit()直接退出,而没有进行类似的保存一恢复处理.但实际上在linux下我们调用了函数atexit(function)来设置程序正常结束前调用的函数,当程序通过调用exit()返回时,参数function所指定的函数会先被调用.然后才真正由exit()结束程序。function将指定函数rekbd(函数的实现见下面的代码),这个函数就是清屏和清处所有前文的属性设置,33声明了转义序列的开始,然后是[2J,表示清屏。[0m表示关闭所有属性。<br />
<br />
  void rekbd(void){<br />
<br />
  prinf(&quot;33[0m&quot;);<br />
<br />
  prinf(&quot;33[2J&quot;);<br />
<br />
  }<br />
<br />
  4 MAKEFILE 文件的编写<br />
<br />
  在Jean J.Labrosse先生的原书中是使用boland c的编译器.而我们在linux下使用GCC的编译器,由于编译器的改变.所以makefile就需要重写。为了简化makefile的编写,我提供一种最简单的方法,那就是把所有uCOS-II 的源码(SOFTWAREuCOS-IISOURCE). 以及配置头文件和测试函数(SOFTWAREuCOS-IIEX1_x86LBC45SOURCE).还有按上文编写的pc.c和pc.h文件,全部放在linux的根目录下.假设为/test78,则makefile可简写为如下方式:<br />
<br />
  UCOS_SRC=/test78<br />
<br />
  UCOS_PORT=/test78<br />
<br />
  UCOS_PC=/test78<br />
<br />
  all:<br />
<br />
  gcc-I$(UCOS_SRC) -I$(UCOS_PORT) -I$(UCOS_PC) test.c $(UCOS_SRC)/uCOS_II.C $(UCOS_PC)/<br />
<br />
  pc.c $(UCOS_PORT)/os_cpu_c.c -o test<br />
<br />
  all是一个伪目标,&quot;伪目标&quot; 并不是一个文件,只是一个标签,它的特性是,总是被执行的。这样的目的是让编译器每次都产生新的目标。-o test指定输出文件为test.‘-I‘选项指定搜索的目录.<br />
<br />
  注意:把所有源文件都放在一个目录下也许并不是一个好方法,它使得整个工程杂乱无章,特别是在工程比较大时.是不能这样处理的。但这里仅仅是为了简化makefile的编写,提供一个可行的方法。所以在这个makefile的前面,我定义了几个宏,如果需要编译的几个文件在路径下,就只需要指定路径就可以了。<br />
<br />
  5 结束语<br />
<br />
  本文的创新点主要体现在<br />
<br />
  1.自建的键盘输入函数。由于(Beginning.Linux.Programming)中实现会阻塞read函数,所以本文采用了改进的方法实现键盘输入,详见第3节。<br />
<br />
  2.MAKEFILE文件。由于编译器的改变,我们需要改写makefile文件,本文提供了一种非常简单的编写方法,详见第4节。<br />
<br />
  我的试验平台如下:在Virtual PC 2004上安装red hat linux 9.0,并且在linux下进行编译和调试。 <br />
<br />
  参考文献:<br />
<br />
  [1]Jean J.Labrosse(邵贝贝译) 《嵌入式实时操作系统UC/OS-II(第2版)》<br />
<br />
  [2]John.Wiley.Sons.Beginning.linux.Programming,Third.Edition<br />
<br />
  [3]Werner Zimmermann uCOS-II-Port for the LINUX Operating System<br />
<br />
  [4]石绍应,张圮,罗诗途.基于嵌入式操作系统VxWorks的战车虚拟仪表显示技术研究[J].微计算机信息,2005,4:136-137<br />
<br />
  [5]钱晨,徐荣华,王钦若.基于Linux操作系统的设备驱动程序开发,微计算机信息.2004,9
游客请输入验证码
浏览1942774次