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. 
If 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の3つの関数を通じてメッセージを取得、配布します.最後にメインウィンドウをメインウィンドウから削除します.
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は、vxworksなどのRTOSシステムで使用することができる.それらの入り口はmainから始まっていないからです.
上のプログラムは、展開されます.最も重要なのはInitGUIとTerminateGUIの2つの関数であることがわかります.
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!
"); return step; } step++; /* Init miscelleous*/ if (!mg_InitMisc ()) { // , MiniGUI.cfg , , MiniGUI.cfg fprintf (stderr, "KERNEL>InitGUI: Initialization of misc things failure!
"); return step; } step++; switch (mg_InitGAL ()) { // GAL, case ERR_CONFIG_FILE: fprintf (stderr, "KERNEL>InitGUI: Reading configuration failure!
"); return step; case ERR_NO_ENGINE: fprintf (stderr, "KERNEL>InitGUI: No graphics engine defined!
"); return step; case ERR_NO_MATCH: fprintf (stderr, "KERNEL>InitGUI: Can not get graphics engine information!
"); return step; case ERR_GFX_ENGINE: fprintf (stderr, "KERNEL>InitGUI: Can not initialize graphics engine!
"); return step; } 。。。。。 /* * Load system resource here. */ step++; if (!mg_InitSystemRes ()) { // fprintf (stderr, "KERNEL>InitGUI: Can not initialize system resource!
"); goto failure1; } /* Init GDI. */ step++; if(!mg_InitGDI()) { // GDI, fprintf (stderr, "KERNEL>InitGUI: Initialization of GDI failure!
"); goto failure1; } /* Init Master Screen DC here */ step++; if (!mg_InitScreenDC (__gal_screen)) {// fprintf (stderr, "KERNEL>InitGUI: Can not initialize screen DC!
"); 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!
"); goto failure1; } /* Init low level event */ step++; if(!mg_InitLWEvent()) {// IAL fprintf(stderr, "KERNEL>InitGUI: Low level event initialization failure!
"); goto failure1; } /** Init LF Manager */ step++; if (!mg_InitLFManager ()) {// Look And Feel Render 。 3.0 。 fprintf (stderr, "KERNEL>InitGUI: Initialization of LF Manager failure!
"); goto failure; } #ifdef _MGHAVE_MENU /* Init menu */ step++; if (!mg_InitMenu ()) {// fprintf (stderr, "KERNEL>InitGUI: Init Menu module failure!
"); goto failure; } #endif /* Init control class */ step++; if(!mg_InitControlClass()) {// , fprintf(stderr, "KERNEL>InitGUI: Init Control Class failure!
"); goto failure; } 。。。。 step++; if (!mg_InitDesktop ()) { // 。 , , 。 fprintf (stderr, "KERNEL>InitGUI: Init Desktop failure!
"); goto failure; }     /* Init accelerator */     step++;     if(!mg_InitAccel()) {         fprintf(stderr, "KERNEL>InitGUI: Init Accelerator failure!
");         goto failure;     }     step++;     if (!mg_InitDesktop ()) {         fprintf (stderr, "KERNEL>InitGUI: Init Desktop failure!
");         goto failure;     }         step++;     if (!mg_InitFreeQMSGList ()) {         fprintf (stderr, "KERNEL>InitGUI: Init free QMSG list failure!
");         goto failure;     }     step++;     if (!createThreadInfoKey ()) {         fprintf (stderr, "KERNEL>InitGUI: Init thread hash table failure!
");         goto failure;     }     step++;     if (!SystemThreads()) { // desktop         fprintf (stderr, "KERNEL>InitGUI: Init system threads failure!
");         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.
"); return step; }

 
TerminateGUIは逆のプロセスです.興味のある人は自分で研究してもいいですが、ここでは詳しく説明しません.
オンライン版では、InitGUIが起動されると、実際にはDesktopスレッド、timerプライマリスレッド、IALスレッドの3つのスレッドが追加されます.これは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占有率が上昇します.
次のセクションでは、ウィンドウの作成、メッセージループシステムの構築から、ウィンドウがどのように作成され、実行されるかについて説明します.