上一篇:MiniGUI源码分析--hellowworld(1) :MiniGUIMain中有什么奥秘
上一篇讲到MiniGUI程序的启动过程。当MiniGUI完成了初始化之后,就可以创建一个主窗口。(主窗口是唯一可以作为根窗口的窗口对象。这可能是MiniGUI在当初设计时为了方便而设立的。但是个人认为,这实在是一个蹩脚的设计。应该将主窗口与控件的接口完全统一了,就像windows API那样。)
创建主窗口函数,是CreateMainWindow ,这是一个内联函数:
static inline HWND GUIAPI CreateMainWindow (PMAINWINCREATE pCreateInfo) { return CreateMainWindowEx (pCreateInfo, NULL, NULL, NULL, NULL); }而 CreateMainWindowEx的定义如下:
MG_EXPORT HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo, const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs, const char* window_name, const char* layer_name);该函数后面的参数都是和LF渲染器有关的,这一部分会在以后的章节专门叙述。最后一个参数可以忽略。
最重要的参数就是PMAINWINCREATE,它描述了一个窗口的主要信息,它的定义是:
/** * Structure defines a main window. */ typedef struct _MAINWINCREATE { /** The style of the main window */ DWORD dwStyle; /** The extended style of the main window */ DWORD dwExStyle; /** The caption of the main window */ const char* spCaption; /** The handle to the menu of the main window */ HMENU hMenu; /** The handle to the cursor of the main window */ HCURSOR hCursor; /** The handle to the icon of the main window */ HICON hIcon; /** The hosting main window */ HWND hHosting; /** The window callback procedure */ int (*MainWindowProc)(HWND, int, WPARAM, LPARAM); /** The position of the main window in the screen coordinates */ int lx, ty, rx, by; /** The pixel value of background color of the main window */ int iBkColor; /** The first private data associated with the main window */ DWORD dwAddData; /** Reserved, do not use */ DWORD dwReserved; }MAINWINCREATE; typedef MAINWINCREATE* PMAINWINCREATE;其中最重要的就是MainWindowProc了,这是窗口的过程回调。
该函数返回一个窗口句柄。
在MiniGUI中,窗口对象是用句柄来表示的,那么,这个句柄是什么呢?
看它的定义:(common.h)
typedef unsigned int HWND;这只是一个幌子,它的本质,是一个MAINWIN的结构体对象指针,该结构体声明在src/include/internals.h中。
可以通过CreateMainWindowEx函数非常清楚的看到:
HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo, const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs, const char* window_name, const char* layer_name) { // PMAINWIN pWin;//定义了窗口指针 if (pCreateInfo == NULL) { return HWND_INVALID; } if (!(pWin = calloc(1, sizeof(MAINWIN)))) { //注意这里,分配了MAINWIN大小的空间 return HWND_INVALID; } #ifdef _MGRM_THREADS ..... return (HWND)pWin; err: ...... return HWND_INVALID; }我将大部分代码删除后,可以看到,HWND不过简单的把地址值转换为整数。
这个结构体的定义,如下:(内容很多,请注意注释部分)
typedef struct _MAINWIN { /* * These fields are similiar with CONTROL struct. */ unsigned char DataType; // 数据类型,表示是否是一个窗口(主窗口或者控件窗口),对于该结构和CONTROL结构,都必须是TYPE_HWND unsigned char WinType; // 判断是否是主窗口,对于该结构,必须是TYPE_MAINWIN unsigned short Flags; // special runtime flags, such EraseBkGnd flags int left, top; // the position and size of main window. int right, bottom; int cl, ct; // the position and size of client area. int cr, cb; DWORD dwStyle; // the styles of main window. DWORD dwExStyle; // the extended styles of main window. int iBkColor; // the background color. HMENU hMenu; // handle of menu. HACCEL hAccel; // handle of accelerator table. HCURSOR hCursor; // handle of cursor. HICON hIcon; // handle of icon. HMENU hSysMenu; // handle of system menu. PLOGFONT pLogFont; // pointer to logical font. char* spCaption; // the caption of main window. int id; // the identifier of main window. LFSCROLLBARINFO vscroll; // the vertical scroll bar information. LFSCROLLBARINFO hscroll; // the horizital scroll bar information. /** the window renderer */ WINDOW_ELEMENT_RENDERER* we_rdr; HDC privCDC; // the private client DC. INVRGN InvRgn; // 无效区域,在处理MSG_PAINT消息时很重要 PGCRINFO pGCRInfo; // pointer to global clip region info struct. // the Z order node. int idx_znode; PCARETINFO pCaretInfo; // pointer to system caret info struct. DWORD dwAddData; // the additional data. DWORD dwAddData2; // the second addtional data. int (*MainWindowProc)(HWND, int, WPARAM, LPARAM); // 这是主窗口的主要函数 struct _MAINWIN* pMainWin; // the main window that contains this window. // for main window, always be itself. HWND hParent; // the parent of this window. // for main window, always be HWND_DESKTOP. /* * Child windows. */ HWND hFirstChild; // the handle of first child window. HWND hActiveChild; // the currently active child window. HWND hOldUnderPointer; // the old child window under pointer. HWND hPrimitive; // the premitive child of mouse event. NOTIFPROC NotifProc; // the notification callback procedure. /* * window element data. */ struct _wnd_element_data* wed; /* * Main Window hosting. * The following members are only implemented for main window. */ struct _MAINWIN* pHosting; // the hosting main window. struct _MAINWIN* pFirstHosted; // the first hosted main window. struct _MAINWIN* pNextHosted; // the next hosted main window. PMSGQUEUE pMessages; // the message queue. GCRINFO GCRInfo; // the global clip region info struct. // put here to avoid invoking malloc function. #ifdef _MGRM_THREADS pthread_t th; // the thread which creates this main window. #endif //the controls as main HWND hFirstChildAsMainWin; HDC secondaryDC; ON_UPDATE_SECONDARYDC update_secdc; // the callback of secondary window dc RECT update_rc; } MAINWIN;主窗口包括的内容非常多,但是可以区分成几部分来看,
其中,TYPE_HWND和TYPE_WINTODEL是两个可选值。当调用了DestroyMainWindow后,DataType就会被设置为TYPE_WINTODEL。这个时候,MAINWIN对象会和其他窗口脱离关系,但是内存并不删除。因为这个时候,该指针仍然可能被消息循环所使用,为了避免出现野指针,它只是被废弃,却没有被删除。只有调用了MainWindowThreadCleanup后,该内存才会被删除。
我们可以通过IsWindow函数,实际上是通过宏MG_IS_WINDOW来判断的:
#define MG_IS_WINDOW(hWnd) \ (hWnd && \ hWnd != HWND_INVALID && \ ((PMAINWIN)hWnd)->DataType == TYPE_HWND)
WinType是为了区分主窗口和控件窗口而定义的。该值可以取TYPE_MAINWIN TYPE_CONTROL TYPE_ROOTWIN。主窗口必须是TYPE_MAINWIN 可以通过IsMainWindow来判断,实际上是通过宏MS_IS_MAIN_WINDOW来判断的:
#define MG_IS_MAIN_WINDOW(hWnd) \ (MG_IS_WINDOW(hWnd) && ((PMAINWIN)hWnd)->WinType == TYPE_MAINWIN)
那么,在看CreateMainWindowEx函数的实现,内容很多,但是也很有规律,请看注释:
HWND GUIAPI CreateMainWindowEx (PMAINWINCREATE pCreateInfo, const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs, const char* window_name, const char* layer_name) { // PMAINWIN pWin; if (pCreateInfo == NULL) { return HWND_INVALID; } if (!(pWin = calloc(1, sizeof(MAINWIN)))) {//分配结构体内存 return HWND_INVALID; } #ifdef _MGRM_THREADS //这是重要部分,用于找到消息队列 if (pCreateInfo->hHosting == HWND_DESKTOP || pCreateInfo->hHosting == 0) { /* ** Create thread infomation and message queue for this new main window. */ if ((pWin->pMessages = GetMsgQueueThisThread ()) == NULL) { //试图获取本线程关联的消息队列结构体 if (!(pWin->pMessages = mg_InitMsgQueueThisThread ()) ) { //试图去创建一个向消息队列结构体 free (pWin); return HWND_INVALID; } pWin->pMessages->pRootMainWin = pWin; } else { /* Already have a top level main window, in case of user have set a wrong hosting window */ pWin->pHosting = pWin->pMessages->pRootMainWin; } } else { pWin->pMessages = GetMsgQueueThisThread (); //直接获取,这种情况下,是可以肯定消息队列已经存在 if (pWin->pMessages != kernel_GetMsgQueue (pCreateInfo->hHosting) || //该函数的调用者必须和hosting的消息队列所在线程一致。这很重要 pWin->pMessages == NULL) { free (pWin); return HWND_INVALID; } } if (pWin->pHosting == NULL) pWin->pHosting = gui_GetMainWindowPtrOfControl (pCreateInfo->hHosting); /* leave the pHosting is NULL for the first window of this thread. */ #else pWin->pHosting = gui_GetMainWindowPtrOfControl (pCreateInfo->hHosting); if (pWin->pHosting == NULL) pWin->pHosting = __mg_dsk_win; pWin->pMessages = __mg_dsk_msg_queue; #endif pWin->pMainWin = pWin; //以下部分在初始化结构体成员,可以忽略 pWin->hParent = 0; pWin->pFirstHosted = NULL; pWin->pNextHosted = NULL; pWin->DataType = TYPE_HWND; pWin->WinType = TYPE_MAINWIN; #ifdef _MGRM_THREADS pWin->th = pthread_self(); #endif pWin->hFirstChild = 0; pWin->hActiveChild = 0; pWin->hOldUnderPointer = 0; pWin->hPrimitive = 0; pWin->NotifProc = NULL; pWin->dwStyle = pCreateInfo->dwStyle; pWin->dwExStyle = pCreateInfo->dwExStyle; #ifdef _MGHAVE_MENU pWin->hMenu = pCreateInfo->hMenu; #else pWin->hMenu = 0; #endif pWin->hCursor = pCreateInfo->hCursor; pWin->hIcon = pCreateInfo->hIcon; #ifdef _MGHAVE_MENU if ((pWin->dwStyle & WS_CAPTION) && (pWin->dwStyle & WS_SYSMENU)) pWin->hSysMenu= CreateSystemMenu ((HWND)pWin, pWin->dwStyle); else #endif pWin->hSysMenu = 0; pWin->spCaption = FixStrAlloc (strlen (pCreateInfo->spCaption)); if (pCreateInfo->spCaption [0]) strcpy (pWin->spCaption, pCreateInfo->spCaption); pWin->MainWindowProc = pCreateInfo->MainWindowProc; pWin->iBkColor = pCreateInfo->iBkColor; pWin->pCaretInfo = NULL; pWin->dwAddData = pCreateInfo->dwAddData; pWin->dwAddData2 = 0; pWin->secondaryDC = 0; /* Scroll bar */ //下面是初始化滚动条相关的内容 if (pWin->dwStyle & WS_VSCROLL) { pWin->vscroll.minPos = 0; pWin->vscroll.maxPos = 100; pWin->vscroll.curPos = 0; pWin->vscroll.pageStep = 101; pWin->vscroll.barStart = 0; pWin->vscroll.barLen = 10; pWin->vscroll.status = SBS_NORMAL; } else pWin->vscroll.status = SBS_HIDE | SBS_DISABLED; if (pWin->dwStyle & WS_HSCROLL) { pWin->hscroll.minPos = 0; pWin->hscroll.maxPos = 100; pWin->hscroll.curPos = 0; pWin->hscroll.pageStep = 101; pWin->hscroll.barStart = 0; pWin->hscroll.barLen = 10; pWin->hscroll.status = SBS_NORMAL; } else pWin->hscroll.status = SBS_HIDE | SBS_DISABLED; /** perfer to use parent renderer */ //初始化渲染器相关的内容,这时可以忽略这一部分 if (pWin->dwExStyle & WS_EX_USEPARENTRDR) { if (((PMAINWIN)pCreateInfo->hHosting)->we_rdr) { pWin->we_rdr = ((PMAINWIN)pCreateInfo->hHosting)->we_rdr; ++pWin->we_rdr->refcount; } else { return HWND_INVALID; } } else { /** set window renderer */ set_window_renderer (pWin, werdr_name); } /** set window element data */ while (we_attrs && we_attrs->we_attr_id != -1) { // append_window_element_data (pWin, // we_attrs->we_attr_id, we_attrs->we_attr); DWORD _old; set_window_element_data ((HWND)pWin, we_attrs->we_attr_id, we_attrs->we_attr, &_old); ++we_attrs; } /** prefer to parent font */ if (pWin->dwExStyle & WS_EX_USEPARENTFONT) pWin->pLogFont = __mg_dsk_win->pLogFont; else { pWin->pLogFont = GetSystemFont (SYSLOGFONT_WCHAR_DEF); } if (SendMessage ((HWND)pWin, MSG_NCCREATE, 0, (LPARAM)pCreateInfo)) goto err; /** reset menu size */ ResetMenuSize ((HWND)pWin); #ifdef __TARGET_FMSOFT__ pCreateInfo->lx += __mg_mainwin_offset_x; pCreateInfo->rx += __mg_mainwin_offset_x; pCreateInfo->ty += __mg_mainwin_offset_y; pCreateInfo->by += __mg_mainwin_offset_y; #endif SendMessage ((HWND)pWin, MSG_SIZECHANGING, //开始发生一些消息,让窗口进行一些工作 (WPARAM)&pCreateInfo->lx, (LPARAM)&pWin->left); SendMessage ((HWND)pWin, MSG_CHANGESIZE, (WPARAM)&pWin->left, 0); pWin->pGCRInfo = &pWin->GCRInfo; if (SendMessage (HWND_DESKTOP, MSG_ADDNEWMAINWIN, (WPARAM) pWin, 0) < 0)//这个很重要:把主窗口发送给Desktop窗口托管,进行管理。 goto err; /* * We should add the new main window in system and then * SendMessage MSG_CREATE for application to create * child windows. */ if (SendMessage ((HWND)pWin, MSG_CREATE, 0, (LPARAM)pCreateInfo)) {//发送MSG_CREATE消息 SendMessage(HWND_DESKTOP, MSG_REMOVEMAINWIN, (WPARAM)pWin, 0); goto err; } return (HWND)pWin; err: #ifdef _MGRM_THREADS if (pWin->pMessages && pWin->pHosting == NULL) { mg_FreeMsgQueueThisThread (); } #endif if (pWin->secondaryDC) DeleteSecondaryDC ((HWND)pWin); free (pWin); return HWND_INVALID; }
Desktop对主窗口的管理,是相当复杂的,但是目前来说,不需要了解那么深。只要知道Desktop为窗口提供什么功能就可以了。
接下来,我们将剖析消息循环的奥秘,并详细讲解MSG_PAINT消息时怎么产生的。