上一篇: 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; }
在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
#ifdef _USE_MINIGUIENTRY #define main_entry minigui_entry int minigui_entry (int args, const char* arg[]); #else #define main_entry main #endif
上面我们的程序,就被展开。可以看到,其中最最要的是InitGUI和TerminateGUI两个函数。
InitGUI函数时主要的初始化函数,我们详细分析下该函数。
InitGUI就定义在minigui.h中,它的原型是:
MG_EXPORT int GUIAPI InitGUI (int, const char **);
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 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其中,线程1就是主线程,另外几个线程都是由InitGUI创建的。可以通过thread N 和bt命令,得到堆栈情况:
(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)
线程3,其入口是TimerEntry,该函数时MiniGUI中定时器的实现。在线程版中,它通过线程和sleep等方法获得等距时间。它的实现在src/kernel/timer.c中。它的创建是在SystemThreads中调用__mg_timer_init函数中创建的。
线程4,其入口是EventLoop,该函数时IAL的主要监视线程。也是在SystemThreads中创建的。
连同主线程在内的这4个线程,实际上大部时间都处于休眠状态,仅仅会在有事件发生时才唤醒。所以,对CPU的影响很小。
有一些开发板在移植后,会出席CPU占用过高的情况,其中一种原因是由于IAL的实现不合理造成的。EventLoop线程依靠IAL的具体实现,由它来阻塞线程,直到收到按键或者鼠标消息。有些开发板需要通过定时器去轮询,而不是通过select方法来等待。当轮询时间过短时,会造成cpu占用率升高。
下一节中,我将从窗口的创建、消息循环系统的建立,讲述一个窗口是如何创建和运行的。