上一篇: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消息时怎么产生的。