MiniGUI源码分析——MiniGUIMain的奥秘

接下来,通过剖析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 
#include 

#include 
#include 
#include 
#include 

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


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


 

你可能感兴趣的:(Linux,C/C++,MiniGUI)