MiniGUI源码分析--hellowworld(2):主窗口诞生的秘密

上一篇: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;
主窗口包括的内容非常多,但是可以区分成几部分来看,

  • 头部分, DataType是所有句柄共有的,表示它具体是哪个对象,在MiniGUI里面,支持的句柄有TYPE_HWND TYPE_HMENU TYPE_HACCEL TYPE_HCURSOR TYPE_HICON TYPE_HDC TYPE_WINTODEL。

其中,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)


  • 位置信息,包括left, top, right, bottom, 是窗口相对于屏幕的位置。cl,ct,cr,cb就是client left, client top, client right和client bottom,表示客户区的范围。这个范围,是相对于(left,top)
  • 从dwStyle到MainWindowProc部分的结构都是属于窗口的特殊的属性的。dwAddData和dwAddData2是关联的附加数据,可以通过SetWindowAddtional/GetWindowAdditional以及SetWindowAdditonal2/GetWindowAdditional2来访问的。一般情况下,控件窗口会使用dwAddData2以保持控件的私有数据,只保留dwAddData给外部程序使用。不过,对于主窗口,就没有这个限制了。
  • pMainWin指向主窗口指针。这是相对于控件窗口来说的。它与hParent不同之处在于,hParent可以是一个主窗口也可以使控件窗口,总之,hParent是直接的,但是pMainWin必须是窗口树的根。 可以通过GetMainWindowHandle来获取
  • hosting窗口实际上是主窗口内部的一个树形关系,使用GetFirstHosted和GetNextHosted可以遍历所有主窗口。所有在一个hosting树上得主窗口,必须是通过一个线程内部的(只对线程版是这样),因为这些窗口,都共用一个消息队列。
  • 有关sendaryDC的结构体。这些结构体是使用特效时用的。它可以把所有窗口内容绘制到sendaryDC中,而不是屏幕上。


那么,在看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;
}


其实该函数的核心,做了以下几件事:

  1. 创建结构体
  2. 获取并填充消息队列
  3. 初始化其他信息
  4. 把自己交给Desktop主窗口托管,让其管理主窗口之际的关系
  5. 发送一个MSG_CREATE消息,告知应用程序窗口已经创建成功


Desktop对主窗口的管理,是相当复杂的,但是目前来说,不需要了解那么深。只要知道Desktop为窗口提供什么功能就可以了。


接下来,我们将剖析消息循环的奥秘,并详细讲解MSG_PAINT消息时怎么产生的。










你可能感兴趣的:(UI,MiniGUI,嵌入式)