上一篇: MiniGUI源码分析-- 开始篇
接下来,通过剖析MiniGUI的最简单的例程,来详细说明MiniGUI程序是如何创建和运行的。
这个例程,可以从很多地方得到,凡是接触过MiniGUI的朋友,首先接触的便是这个例子,为了方便大家阅读,贴在下面
/*
** $Id: helloworld.c,v 1.38 2007-10-25 07:56:45 weiym Exp $
**
** Listing 2.1
**
** helloworld.c: Sample program for MiniGUI Programming Guide
** The first MiniGUI application.
**
** Copyright (C) 2004 ~ 2007 Feynman Software.
**
** License: GPL
*/
#include <stdio.h>
#include <string.h>
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
static char welcome_text [512];
static char msg_text [256];
static RECT welcome_rc = {10, 100, 600, 400};
static RECT msg_rc = {10, 100, 600, 400};
static const char* syskey = "";
static int last_key = -1;
static int last_key_count = 0;
static void make_welcome_text (void)
{
const char* sys_charset = GetSysCharset (TRUE);
const char* format;
if (sys_charset == NULL)
sys_charset = GetSysCharset (FALSE);
SetRect (&welcome_rc, 10, 10, g_rcScr.right - 10, g_rcScr.bottom / 2 - 10);
SetRect (&msg_rc, 10, welcome_rc.bottom + 10, g_rcScr.right - 10, g_rcScr.bottom - 20);
if (strcmp (sys_charset, FONT_CHARSET_GB2312_0) == 0
|| strcmp (sys_charset, FONT_CHARSET_GBK) == 0) {
format = "欢迎来到 MiniGUI 的世界! 如果您能看到该文本, 则说明 MiniGUI Version %d.%d.%d 可在该硬件上运行!";
}
else if (strcmp (sys_charset, FONT_CHARSET_BIG5) == 0) {
format = "欢迎来到 MiniGUI 的世界! 如果您能看到该文本, 则说明 MiniGUI Version %d.%d.%d 可在该硬件上运行!";
}
else {
format = "Welcome to the world of MiniGUI. \nIf you can see this text, MiniGUI Version %d.%d.%d can run on this hardware board.";
}
sprintf (welcome_text, format, MINIGUI_MAJOR_VERSION, MINIGUI_MINOR_VERSION, MINIGUI_MICRO_VERSION);
strcpy (msg_text, "No message so far.");
}
static int HelloWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
syskey = "";
switch (message) {
case MSG_CREATE:
make_welcome_text ();
SetTimer (hWnd, 100, 200);
break;
case MSG_TIMER:
sprintf (msg_text, "Timer expired, current tick count: %ul.",
GetTickCount ());
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_LBUTTONDOWN:
strcpy (msg_text, "The left button pressed.");
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_LBUTTONUP:
strcpy (msg_text, "The left button released.");
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_RBUTTONDOWN:
strcpy (msg_text, "The right button pressed.");
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_RBUTTONUP:
strcpy (msg_text, "The right button released.");
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_PAINT:
hdc = BeginPaint (hWnd);
DrawText (hdc, welcome_text, -1, &welcome_rc, DT_LEFT | DT_WORDBREAK);
DrawText (hdc, msg_text, -1, &msg_rc, DT_LEFT | DT_WORDBREAK);
EndPaint (hWnd, hdc);
return 0;
case MSG_SYSKEYDOWN:
syskey = "sys";
case MSG_KEYDOWN:
if(last_key == wParam)
last_key_count++;
else
{
last_key = wParam;
last_key_count = 1;
}
sprintf (msg_text, "The %d %skey pressed %d times",
wParam, syskey, last_key_count);
InvalidateRect (hWnd, &msg_rc, TRUE);
return 0;
case MSG_KEYLONGPRESS:
sprintf (msg_text, "=======The %d key pressed over a long term", wParam);
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_KEYALWAYSPRESS:
sprintf (msg_text, "=======The %d key pressed always", wParam);
InvalidateRect (hWnd, &msg_rc, TRUE);
break;
case MSG_KEYUP:
sprintf (msg_text, "The %d key released", wParam);
InvalidateRect (hWnd, &msg_rc, TRUE);
return 0;
case MSG_CLOSE:
KillTimer (hWnd, 100);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
#ifdef _MGRM_PROCESSES
JoinLayer(NAME_DEF_LAYER , "helloworld" , 0 , 0);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Hello, world!";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = HelloWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = g_rcScr.right;
CreateInfo.by = g_rcScr.bottom;
CreateInfo.iBkColor = COLOR_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow(hMainWnd, SW_SHOWNORMAL);
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
MainWindowThreadCleanup (hMainWnd);
return 0;
}
简要说明下:程序从 MiniGUIMain 函数开始执行,首先调用CreateMainWindow创建一个新的窗口,然后通过GetMessage, TranslateMessage和 DispatchMessage三个函数来获取、分发消息;最后通过MainWindowThreadCleanup 函数来删除主窗口。
在MiniGUI中,主窗口与普通窗口(控件窗口)是有很大区别的。主窗口可以拥有有自己的消息循环,并被MiniGUI系统单独管理。
该文件的编译也很简单,可以通过
gcc -o helloworld helloworld.c -lmiigui_ths -lpthread
但是我们知道,一般C语言的入口都是main,那么MiniGUIMain函数怎么成为入口呢?
在MiniGUI源码include/minigui.h中,定义了宏
#define MiniGUIMain \
MiniGUIAppMain (int args, const char* argv[]); \
int main_entry (int args, const char* argv[]) \
{ \
int iRet = 0; \
if (InitGUI (args, argv) != 0) { \
return 1; \
} \
iRet = MiniGUIAppMain (args, argv); \
TerminateGUI (iRet); \
return iRet; \
} \
int MiniGUIAppMain
其中main_entry,在linux 直接被定义为main, 但是,当_USE_MINIGUIENTRY被定义后,它被定义为minigui_entry:
#ifdef _USE_MINIGUIENTRY
#define main_entry minigui_entry
int minigui_entry (int args, const char* arg[]);
#else
#define main_entry main
#endif
定义minigui_entry可以用在一些RTOS系统上,如vxworks上。因为它们的入口不是从main开始的。
上面我们的程序,就被展开。可以看到,其中最最要的是InitGUI和TerminateGUI两个函数。
InitGUI函数时主要的初始化函数,我们详细分析下该函数。
InitGUI就定义在minigui.h中,它的原型是:
MG_EXPORT int GUIAPI InitGUI (int, const char **);
InitGUI的实现,有多个版本,在线程版中,是在src/kernal/init.c中实现的。我们重点考察这一部分:(删除了一些不重要的部分)
int GUIAPI InitGUI (int args, const char *agr[])
{
int step = 0;
.......
if (!mg_InitFixStr ()) { //初始化字符串管理内存。MiniGUI在内部实现一个字符串的内存管理器,可以加快分配速度,减少内存碎片
fprintf (stderr, "KERNEL>InitGUI: Init Fixed String module failure!\n");
return step;
}
step++;
/* Init miscelleous*/
if (!mg_InitMisc ()) { //初始化杂项,主要是加载MiniGUI.cfg文件,后面的很多操作,都依赖于MiniGUI.cfg中的配置
fprintf (stderr, "KERNEL>InitGUI: Initialization of misc things failure!\n");
return step;
}
step++;
switch (mg_InitGAL ()) { //初始化 GAL,它负责打开图形设备
case ERR_CONFIG_FILE:
fprintf (stderr,
"KERNEL>InitGUI: Reading configuration failure!\n");
return step;
case ERR_NO_ENGINE:
fprintf (stderr,
"KERNEL>InitGUI: No graphics engine defined!\n");
return step;
case ERR_NO_MATCH:
fprintf (stderr,
"KERNEL>InitGUI: Can not get graphics engine information!\n");
return step;
case ERR_GFX_ENGINE:
fprintf (stderr,
"KERNEL>InitGUI: Can not initialize graphics engine!\n");
return step;
}
。。。。。
/*
* Load system resource here.
*/
step++;
if (!mg_InitSystemRes ()) { //初始化内建资源管理器
fprintf (stderr, "KERNEL>InitGUI: Can not initialize system resource!\n");
goto failure1;
}
/* Init GDI. */
step++;
if(!mg_InitGDI()) { //初始化GDI, 主要是字体的初始化
fprintf (stderr, "KERNEL>InitGUI: Initialization of GDI failure!\n");
goto failure1;
}
/* Init Master Screen DC here */
step++;
if (!mg_InitScreenDC (__gal_screen)) {//创建和初始化主屏幕
fprintf (stderr, "KERNEL>InitGUI: Can not initialize screen DC!\n");
goto failure1;
}
g_rcScr.left = 0;
g_rcScr.top = 0;
g_rcScr.right = GetGDCapability (HDC_SCREEN_SYS, GDCAP_MAXX) + 1;
g_rcScr.bottom = GetGDCapability (HDC_SCREEN_SYS, GDCAP_MAXY) + 1;
/* Init mouse cursor. */
step++;
if( !mg_InitCursor() ) {//初始化光标
fprintf (stderr, "KERNEL>InitGUI: Count not init mouse cursor!\n");
goto failure1;
}
/* Init low level event */
step++;
if(!mg_InitLWEvent()) {//初始化IAL
fprintf(stderr, "KERNEL>InitGUI: Low level event initialization failure!\n");
goto failure1;
}
/** Init LF Manager */
step++;
if (!mg_InitLFManager ()) {//初始化Look And Feel Render管理器。这一部分是3.0独有的。
fprintf (stderr, "KERNEL>InitGUI: Initialization of LF Manager failure!\n");
goto failure;
}
#ifdef _MGHAVE_MENU
/* Init menu */
step++;
if (!mg_InitMenu ()) {//初始化菜单系统
fprintf (stderr, "KERNEL>InitGUI: Init Menu module failure!\n");
goto failure;
}
#endif
/* Init control class */
step++;
if(!mg_InitControlClass()) {//初始化控件类,这样可以使得预定义的控件都可以使用
fprintf(stderr, "KERNEL>InitGUI: Init Control Class failure!\n");
goto failure;
}
。。。。
step++;
if (!mg_InitDesktop ()) { //创建和初始化一个桌面窗口。桌面窗口是非常重要的托管窗口,它是所有主窗口的父窗口,完成众多的消息分发和主窗口管理工作。它在一个单独线程中运行
fprintf (stderr, "KERNEL>InitGUI: Init Desktop failure!\n");
goto failure;
}
/* Init accelerator */
step++;
if(!mg_InitAccel()) {
fprintf(stderr, "KERNEL>InitGUI: Init Accelerator failure!\n");
goto failure;
}
step++;
if (!mg_InitDesktop ()) {
fprintf (stderr, "KERNEL>InitGUI: Init Desktop failure!\n");
goto failure;
}
step++;
if (!mg_InitFreeQMSGList ()) {
fprintf (stderr, "KERNEL>InitGUI: Init free QMSG list failure!\n");
goto failure;
}
step++;
if (!createThreadInfoKey ()) {
fprintf (stderr, "KERNEL>InitGUI: Init thread hash table failure!\n");
goto failure;
}
step++;
if (!SystemThreads()) { //创建desktop线程
fprintf (stderr, "KERNEL>InitGUI: Init system threads failure!\n");
goto failure;
}
SetKeyboardLayout ("default");
SetCursor (GetSystemCursor (IDC_ARROW));
SetCursorPos (g_rcScr.right >> 1, g_rcScr.bottom >> 1);
#ifdef _MG_EVALUATION
mg_InitEvaluation ();
#endif
return 0;
failure:
mg_TerminateLWEvent ();
failure1:
mg_TerminateGAL ();
fprintf (stderr, "KERNEL>InitGUI: Init failure, please check your MiniGUI configuration or resource.\n");
return step;
}
TerminateGUI是一个相反的过程。有兴趣的朋友可以自己研究下,这里不再详细说明。
在线程版,当InitGUI被启动后,实际上又额外创建了3个线程,分别是Desktop线程,timer主线程和IAL线程。这是可以通过gdb来观察。使用gdb ./helloworld,在主线程调用完InitGUI函数后暂停,通过 info threads命令,我们可以看到:
(gdb) info threads其中,线程1就是主线程,另外几个线程都是由InitGUI创建的。可以通过thread N 和bt命令,得到堆栈情况:
4 Thread 0xb6eafb70 (LWP 23218) 0x0012d422 in __kernel_vsyscall ()
3 Thread 0xb76b0b70 (LWP 23217) 0x0012d422 in __kernel_vsyscall ()
2 Thread 0xb7eb1b70 (LWP 23216) 0x0012d422 in __kernel_vsyscall ()
* 1 Thread 0xb7fec6c0 (LWP 23212) MiniGUIAppMain (argc=1, argv=0xbffff434)
at helloworld.c:171
(gdb) thread 2
[Switching to thread 2 (Thread 0xb7eb1b70 (LWP 23216))]#0 0x0012d422 in __kernel_vsyscall ()
(gdb) bt
#0 0x0012d422 in __kernel_vsyscall ()
#1 0x0013a2e0 in sem_wait@GLIBC_2.0 () from /lib/tls/i686/cmov/libpthread.so.0
#2 0x001e67f1 in PeekMessageEx (pMsg=0xb7eb1368, hWnd=3133696, iMsgFilterMin=0,
iMsgFilterMax=0, bWait=1, uRemoveMsg=1) at message.c:672
#3 0x001e3f9e in GetMessage (data=0xbffff32c) at ../../include/window.h:2250
#4 DesktopMain (data=0xbffff32c) at desktop-ths.c:123
#5 0x0013396e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#6 0x003f5a4e in clone () from /lib/tls/i686/cmov/libc.so.6
(gdb) thread 3
[Switching to thread 3 (Thread 0xb76b0b70 (LWP 23217))]#0 0x0012d422 in __kernel_vsyscall ()
(gdb) bt
#0 0x0012d422 in __kernel_vsyscall ()
#1 0x003ee971 in select () from /lib/tls/i686/cmov/libc.so.6
#2 0x00170f90 in __mg_os_time_delay (ms=10) at nposix.c:384
#3 0x001db484 in _os_timer_loop (data=0xbffff25c) at timer.c:101
#4 TimerEntry (data=0xbffff25c) at timer.c:117
#5 0x0013396e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#6 0x003f5a4e in clone () from /lib/tls/i686/cmov/libc.so.6
(gdb) thread 4
[Switching to thread 4 (Thread 0xb6eafb70 (LWP 23218))]#0 0x0012d422 in __kernel_vsyscall ()
(gdb) bt
#0 0x0012d422 in __kernel_vsyscall ()
#1 0x0013a4e7 in sem_post@GLIBC_2.0 () from /lib/tls/i686/cmov/libpthread.so.0
#2 0x001db8b0 in EventLoop (data=0xbffff32c) at init.c:141
#3 0x0013396e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#4 0x003f5a4e in clone () from /lib/tls/i686/cmov/libc.so.6
(gdb)
线程2,其入口是DesktopMain。该函数是在src/kernel/desktop-ths.c(线程版)文件中定义的。这个函数是在InitGUI中调用SystemThreads中创建的线程并启动其运行的。
线程3,其入口是TimerEntry,该函数时MiniGUI中定时器的实现。在线程版中,它通过线程和sleep等方法获得等距时间。它的实现在src/kernel/timer.c中。它的创建是在SystemThreads中调用__mg_timer_init函数中创建的。
线程4,其入口是EventLoop,该函数时IAL的主要监视线程。也是在SystemThreads中创建的。
连同主线程在内的这4个线程,实际上大部时间都处于休眠状态,仅仅会在有事件发生时才唤醒。所以,对CPU的影响很小。
有一些开发板在移植后,会出席CPU占用过高的情况,其中一种原因是由于IAL的实现不合理造成的。EventLoop线程依靠IAL的具体实现,由它来阻塞线程,直到收到按键或者鼠标消息。有些开发板需要通过定时器去轮询,而不是通过select方法来等待。当轮询时间过短时,会造成cpu占用率升高。
下一节中,我将从窗口的创建、消息循环系统的建立,讲述一个窗口是如何创建和运行的。