MiniGUI源码分析--hellowworld(1) :MiniGUIMain中有什么奥秘

来源:互联网 发布:poe诗歌软件 编辑:程序博客网 时间:2024/04/29 13:30

上一篇: 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  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) 

线程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占用率升高。


下一节中,我将从窗口的创建、消息循环系统的建立,讲述一个窗口是如何创建和运行的。


原创粉丝点击