MiniGUI的窗口剪切机制在众多嵌入式GUI中还是比较有特点的。 它基于这样一种理论,每个GDI原子操作都支持剪切,那么基于这些操作来完成的一次绘制也是支持剪切的。而很多GUI实际上都是为每个窗口开辟了一块buffer,GDI原子操作本身不需要支持剪切,先将图形绘制到buffer上,然后再将buffer局部输出到前台(如果你将这个过程也定义为GDI操作,那可能准确一点的说法是除Blit之外的操作都是不支持剪切的)。不能说MiniGUI这种做法就一定好,凡事都没有绝对,只能说它比较有特点,在某些情况下效率的确比buffer级剪切要高,而且资源占用少。
MiniGUI的比较完整的开源版本是1.3.3,虽然1.6.2-str也是一个开源版本,但比较关键的newgdi和newgal模块不包含在其中,所以本文选择了1.3.3做为分析对象。1.3.3分为lite和thread两种运行模式,但lite模式下不能完整的支持多窗口剪切,所以分析范围再次细小到thread模式。
thread模式,即线程模式,每个应用程序都以线程的方式运行,共享进程空间。这种模式是典型的嵌入式设计,很多小巧的嵌入式OS有且只有线程这个概念,特别是那些为没有MMU的处理器设计的OS。
要分析窗口剪切,首先要搞明白MiniGUI的窗口是如何存储的。其实我们分析任何一段代码都无非是从数据结构和算法两个角度来观察的,数据结构是静的,算法是动的,也就是所谓的存储设计和运行设计。
MiniGUI单个窗口在内部存储结构大致如下,截取自internal.h,
typedef struct _MAINWIN
{
...
int left, top; // the position and size of main window.
int right, bottom;
int cl, ct; // the position and size of client area.
int cr, cb;
...
HDC privCDC; // the private client DC.
INVRGN InvRgn; // the invalid region of this main window.
PGCRINFO pGCRInfo; // pointer to global clip region info struct.
PZORDERNODE pZOrderNode;
// the Z order node.
...
int (*MainWindowProc)(HWND, int, WPARAM, LPARAM);
// the address of main window procedure.
...
HWND hParent; // the parent of this window.
// for main window, always be HWND_DESKTOP.
/*
* Child windows.
*/
HWND hFirstChild; // the handle of first child window.
...
PMSGQUEUE pMessages;
// the message queue.
GCRINFO GCRInfo;
// the global clip region info struct.
// put here to avoid invoking malloc function.
...
}MAINWIN;
MiniGUI的窗口分客户区和非客户区,这点和windows是一样的,(left, top, right, bottom)为主窗口大小,(cl,ct,cr,cb)为客户区大小。
一般来说屏幕上会出现多个窗口,而且相互之间会有层叠关系。多个窗口在内存中被组织成窗口栈,MiniGUI中有两个窗口栈,一个是普通窗口栈(desktop.c中使用静态全局变量sg_MainWinZOrder保存),另一个是TopMost窗口栈(desktop.c中使用静态全局变量sg_TopMostWinZOrder保存)。其实在我看来完全没有必要分解成两个窗口栈,使用同一个窗口栈保留两个指针就可以了,这样就不至于在无效区域重绘时要判断两个变量,当然这只是设计上的一个小问题。
以一个例子来说明MiniGUI是如何完成窗口剪切计算的。在这个例子中我们假设屏幕上有两个窗口,关系如下图所示,
当窗口A消失的时候,B窗口原来被