MiniGUIソース分析——MiniGUIMainの奥義
次に、MiniGUIの最も簡単なルーチンを分析することによって、MiniGUIプログラムがどのように作成され、実行されるかを詳細に説明する.
このルーチンは、多くの場所から入手できます.MiniGUIに触れたことがある人は、まずこの例に触れます.皆さんが読みやすいように、以下に貼ってください.
簡単な説明:プログラムはMiniGUIMain関数から実行され、まずCreateMainWindowを呼び出して新しいウィンドウを作成し、GetMessage、TranslateMessage、DispatchMessageの3つの関数を通じてメッセージを取得、配布します.最後にメインウィンドウをメインウィンドウから削除します.
MiniGUIでは、メインウィンドウは通常のウィンドウ(コントロールウィンドウ)と大きく異なります.メインウィンドウは独自のメッセージループを持ち、MiniGUIシステムによって個別に管理されます.
このファイルのコンパイルも簡単で、
しかし、一般的なC言語の入り口はmainであることを知っていますが、MiniGUIMain関数はどのように入り口になりますか?
MiniGUIのソースコードinclude/minigui.hでは、マクロが定義されている
ここでmain_entryは、linuxで直接mainとして定義されていますが、_USE_MINIGUIENTRYが定義されるとminigui_として定義されるentry:
miniguiの定義entryは、vxworksなどのRTOSシステムで使用することができる.それらの入り口はmainから始まっていないからです.
上のプログラムは、展開されます.最も重要なのはInitGUIとTerminateGUIの2つの関数であることがわかります.
InitGUI関数の場合の主な初期化関数を詳細に解析した.
InitGUIはminiguiに定義されています.hでは、その原型は:
InitGUIの実現は、複数のバージョンがあり、オンライン版では、src/kernal/initである.cで実現した.私たちはこの部分に重点を置いて考察します:(重要でない部分を削除しました)
TerminateGUIは逆のプロセスです.興味のある人は自分で研究してもいいですが、ここでは詳しく説明しません.
オンライン版では、InitGUIが起動されると、実際にはDesktopスレッド、timerプライマリスレッド、IALスレッドの3つのスレッドが追加されます.これはgdbで観察できる.gdb./の使用helloworldは、メインスレッドがInitGUI関数を呼び出した後に一時停止し、info threadsコマンドで次のことを見ることができます.
ここで、スレッド1はメインスレッドであり、他のいくつかのスレッドはInitGUIによって作成される.スタックの状況は、thread Nコマンドとbtコマンドで取得できます.
スレッド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占有率が上昇します.
次のセクションでは、ウィンドウの作成、メッセージループシステムの構築から、ウィンドウがどのように作成され、実行されるかについて説明します.
このルーチンは、多くの場所から入手できます.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占有率が上昇します.
次のセクションでは、ウィンドウの作成、メッセージループシステムの構築から、ウィンドウがどのように作成され、実行されるかについて説明します.