作者:魏永明
MiniGUI 提供的非 GUI/GDI 接口
本文讲述了 MiniGUI 为应用程序提供的非 GUI/GDI 接口,这些接口能够帮助应用程序更好地和操作系统交互,扩展应用程序功能,并提高应用程序的可移植性。内容主要涉及到如下几个方面:MiniGUI-Lite 和 select 系统调用;基于 UNIX Domain Socket 的进程间通讯;编写可移植性代码等。
1 引言
一般而言,GUI 系统的应用程序编程接口主要集中于窗口、消息队列、图形设备等相关方面。但因为 GUI 系统在处理系统事件时通常会提供自己的机制,而这些机制往往会和操作系统本身提供的机制不相兼容。比如,MiniGUI 提供了消息循环机制,而应用程序的结构一般是消息驱动的;也就是说,应用程序通过被动接收消息来工作。但很多情况下,应用程序需要主动监视某个系统事件,比如在 UNIX 操作系统中,可以通过 select 系统调用监听某个文件描述符上是否有可读数据。这样,如何将 MiniGUI 的消息队列机制和现有操作系统的其他机制融合在一起,就成了一个较为困难的问题。本文将讲述几种解决这一问题的方法。
我们知道,MiniGUI-Lite 采用 UNIX Domain Socket 实现客户程序和服务器程序之间的交互。应用程序也可以利用这一机制,完成自己的通讯任务――客户向服务器提交请求,而服务器完成对客户的请求处理并应答。一方面,在 MiniGUI-Lite 的服务器程序中,你可以扩展这一机制,注册自己的请求处理函数,完成定制的请求/响应通讯任务。另一方面,MiniGUI-Lite 当中也提供了若干用来创建和操作 UNIX Domain Socket 的函数,任何 MiniGUI-Lite 的应用程序都可以建立 UNIX Domain Socket,并完成和其他 MiniGUI-Lite 应用程序之间的数据交换。本文将举例讲述如何利用 MiniGUI-Lite 提供的函数完成此类通讯任务。
嵌入式 Linux 系统现在能够在许多不同架构的硬件平台上运行,MiniGUI 也能够在这些硬件平台上运行。但由于许多硬件平台具有和其他硬件平台不同的特性,比如说,常见的 CPU 是 Little Endian 的,而某些 CPU 则是 Big Endian 的。这要求我们在编写代码,尤其是文件 I/O 相关代码时,必须编写可移植代码,以便适合具有不同架构的平台。本文将描述 MiniGUI 为应用程序提供的可移植性函数及其用法。
除了与上述内容相关的函数之外,MiniGUI 还提供了其他一些函数,本文最后部分将描述这些函数的用途和用法,包括配置文件读写以及定点数运算。
2 MiniGUI-Lite和 select 系统调用
我们知道,在 MiniGUI-Lite 之上运行的应用程序只有一个消息队列。应用程序在初始化之后,会建立一个消息循环,然后不停地从这个消息队列当中获得消息并处理,直到接收到 MSG_QUIT 消息为止。应用程序的窗口过程在处理消息时,要在处理完消息之后立即返回,以便有机会获得其他的消息并处理。现在,假如应用程序在处理某个消息时监听某个文件描述符而调用 select 系统调用,就有可能会出现问题――因为 select 系统调用可能会长时间阻塞,而由 MiniGUI-Lite 服务器发送给客户的事件得不到及时处理。这样,消息驱动的方式和 select 系统调用就难于很好地融合。在 MiniGUI-Threads 中,因为每个线程都有自己相应的消息队列,而系统消息队列是由单独运行的 desktop 线程治理的,所以任何一个应用程序建立的线程都可以长时间阻塞,从而可以调用类似 select 的系统调用。但在 MiniGUI-Lite 当中,假如要监听某个应用程序自己的文件描述符事件,必须进行恰当的处理,以避免长时间阻塞。
在 MiniGUI-Lite 当中,有几种解决这一问题的办法:
在调用 select 系统调用时,传递超时值,保证 select 系统调用不会长时间阻塞。
设置定时器,定时器到期时,利用 select 系统调用查看被监听的文件描述符。假如没有相应的事件发生,则立即返回,否则进行读写操作。
利用 MiniGUI-Lite 提供的 RegisterListenFD 函数在系统中注册监听文件描述符,并在被监听的文件描述符上发生指定的事件时,向某个窗口发送 MSG_FDEVENT 消息。
由于前两种解决方法比较简单,这里我们重点讲述的第三种解决办法。MiniGUI-Lite 为应用程序提供了如下两个函数及一个宏:
#define MAX_NR_LISTEN_FD 5
/* Return TRUE if all OK, and FALSE on error. */
BOOL GUIAPI RegisterListenFD (int fd, int type, HWND hwnd, void* context);
/* Return TRUE if all OK, and FALSE on error. */
BOOL GUIAPI UnregisterListenFD (int fd);
MAX_NR_LISTEN_FD 宏定义了系统能够监听的最多文件描述符数,默认定义为 5。
RegisterListenFD 函数在系统当中注册一个需要监听的文件描述符,并指定监听的事件类型(type 参数,可取 POLLIN、POLLOUT 或者 POLLERR),接收 MSG_FDEVENT 消息的窗口句柄以及一个上下文信息。
UnregisterListenFD 函数注销一个被注册的监听文件描述符。
在应用程序使用RegisterListenFD 函数注册了被监听的文件描述符之后,当指定的事件发生在该文件描述符上时,系统会将 MSG_FDEVENT 消息发送到指定的窗口,应用程序可在窗口过程中接收该消息并处理。MiniGUI 中的 libvcongui 就利用了上述函数监听来自主控伪终端上的可读事件,如下面的程序段所示(vcongui/vcongui.c):
...
/* 注册主控伪终端伪监听文件描述符 */
RegisterListenFD (pConInfo-masterPty, POLLIN, hMainWnd, 0);
/* 进入消息循环 */
while (!pConInfo-terminate && GetMessage (&Msg, hMainWnd)) {
DispatchMessage (&Msg);
}
/* 注销监听文件描述符 */
UnregisterListenFD (pConInfo-masterPty);
...
/* 虚拟控制台的窗口过程 */
static int VCOnGUIMainWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
PCONINFO pConInfo;
pConInfo = (PCONINFO)GetWindowAdditionalData (hWnd);
switch (message) {
...
/* 接收到 MSG_FDEVENT 消息,则处理主控伪终端上的输入数据 */
case MSG_FDEVENT:
ReadMasterPty (pConInfo);
break;
...
}
/* 调用默认窗口过程 */
if (pConInfo-DefWinProc)
return (*pConInfo-DefWinProc)(hWnd, message, wParam, lParam);
else
return DefaultMainWinProc (hWnd, message, wParam, lParam);
}
在 3.2 节当中,我们还可以看到RegisterListenFD 函数的使用。显然,通过这种简单的注册监听文件描述符的接口,MiniGUI-Lite 程序能够方便地利用底层的消息机制完成对异步事件的处理。
3 MiniGUI-Lite 与进程间通讯
3.1 简单请求/应答处理
我们知道,MiniGUI-Lite 利用了 UNIX Domain Socket 实现服务器和客户程序之间的通讯。为了实现客户和服务器之间的简单方便的通讯,MiniGUI-Lite 中定义了一种简单的请求/响应结构。客户程序通过指定的结构将请求发送到服务器,服务器处理请求并应答。在客户端,一个请求定义如下(include/gdi.h):
typedef strUCt tagREQUEST {
int id;
const void* data;
size_t len_data;
} REQUEST;
typedef REQUEST* PREQUEST;
其中,id 是用来标识请求类型的整型数,data 是发送给该请求的关联数据,len_data 则是数据的长度。客户在初始化 REQUEST 结构之后,就可以调用 cli_request 向服务器发送请求,并等待服务器的应答。该函数的原型如下。
/* send a request to server and wait reply */
int cli_request (PREQUEST request, void* result, int len_rslt);
服务器程序(即 mginit)会在自己的消息循环当中获得来自客户的请求,并进行处理,最终会将处理结果发送给客户。
在上述这种简单的客户/服务器通讯中,客户和服务器必须就每个请求类型达成一致,也就是说,客户和服务器必须了解每种类型请求的数据含义并进行恰当的处理。
MiniGUI-Lite 利用上述这种简单的通讯方法,实现了若干系统级的通讯任务:
鼠标光标的治理。鼠标光标是一个全局资源,当客户需要创建或者销毁鼠标光标,改变鼠标光标的外形、位置,显示或者隐藏鼠标时,就发送请求到服务器,服务器程序完成相应任务并将结果发送给客户。
层及活动客户治理。当客户查询层的信息,新建层,加入某个已有层,或者设置层中的活动客户时,通过该接口发送请求到服务器。
其他一些系统级的任务。比如在新的 GDI 接口中,服务器程序统一治理显示卡中可能用来建立内存 DC 的显示内存,当客户要申请建立在显示内存中的内存 DC 时,就会发送请求到服务器。
为了让应用程序也能够通过这种简单的方式实现客户和服务器之间的通讯,服务器程序可以注册一些定制的请求处理函数,然后客户就可以向服务器发送这些请求。
为此,MiniGUI-Lite 提供了如下接口:
#define MAX_SYS_REQID 0x0010
#define MAX_REQID 0x0018
/*
* Register user defined request handlers for server
* Note that user defined request id should larger