开源GUI-Microwindows之程序入口分析

时间:2021-02-07 18:11:38

***************************************************************************************************************************

作者:EasyWave                                                               时间:2014.10.05

类别:开源GUI系统-Microwindows之程序入口分析      声明:转载,请保留链接

注意:如有错误,欢迎指正。这些是我学习的日志文章......

***************************************************************************************************************************

一:MicroWindows程序入口

     
 这里仅仅分析基于WIN32 Message方式部分,对于Nano不是这里的分析重点,相信熟悉Linux内核的,应该都知道在Linux下,无论是设备,驱动,还是进程,都是採用链表的方式将各个宿主数据结构链接起来,而在Microwindows中也採用内似的方式,我们先来复习下Linux的双向链表吧,在Linux内核中,有大量的数据结构须要用到双循环链表,比如进程、文件、模块、页面等。若採用双循环链表的传统实现方式,须要为这些数据结构维护各自的链表,而且为每一个链表都要设计插入、删除等操作函数。由于用来维持链表的next和prev指针指向相应类型的对象,因此一种数据结构的链表操作函数不能用于操作其他数据结构的链表。

在Linux源码树的include/linux/list.h文件里,採用了一种类型无关的双循环链表实现方式。其思想是将指针prev和next从详细的数据结构中提取出来构成一种通用的"双链表"数据结构list_head。假设须要构造某类对象的特定链表,则在其结构(被称为宿主数据结构)中定义一个类型为list_head类型的成员,通过这个成员将这类对象连接起来,形成所需链表,并通过通用链表函数对其进行操作。其长处是仅仅需编写通用链表函数,就可以构造和操作不同对象的链表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。例如以下所看到的的定义:

struct list_head {

         struct list_head *next, *prev; 

};  

在Linux内核中的双循环链表实现方式例如以下:

  • list_head类型的变量作为一个成员嵌入到宿主数据结构内;
  • 能够将链表结构放在宿主结构内的不论什么地方;
  • 能够为链表结构取不论什么名字;
  • 宿主结构能够有多个链表结构;
  • 用list_head中的成员和相相应的处理函数来对链表进行遍历;
  • 假设想得到宿主结构的指针,使用list_entry能够算出来。
      Linux中是怎样通过list_head获取宿主数据结构的呢?这里尝试分析一下:  
      假设须要有某种数据结构的队列,就在这样的数据结构定义内部放上一个list_head数据结构。比如,建立数据结构foo链表的方式是,在foo的定义中,嵌入了一个list_head成员list。这里就是所指的"宿主"。 

typedef struct dtest {     

     …      

     struct list_head list;

     … 

}; 


可是,怎样通过list_head成员訪问到宿主结构项呢?毕竟list_head只是是个连接件,而我们须要的是一个"特定"的数据结构链表。 

先介绍几个基本宏:offsetof、typeof、containerof 


#define __compiler_offsetof(a,b)  __builtin_offsetof(a,b) 

而__builtin_offsetof()宏就是在编译器中已经设计好了的函数,直接调用就可以。 

#undef offsetof      //取消先前的不论什么定义,能够保证以下的定义生效 

#ifdef __compiler_offsetof  

#define offsetof(TYPE,MEMBER)  __compiler_offsetof(TYPE,MEMBER) 

#else  

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 

#endif    

上面的算法一共分以下几步:
  • ( (TYPE *)0 ) 0地址强制 "转换" 为 TYPE结构的指针;
  • ((TYPE *)0)->MEMBER   訪问结构中的数据成员;
  • &( ( (TYPE *)0 )->MEMBER)取出数据成员的地址
  • (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型.

巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址; 这里使用的是一个利用编译器技术的小技巧(编译器自己主动算出成员的偏移量),即先求得结构成员变量在结构体中的相对于结构体的首地址的偏移地址,然后依据结构体的首地址为0,从而得出该偏移地址就是该结构体变量在该结构体中的偏移,即:该结构体成员变量距离结构体首的距离。在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。

ontainer_of - cast a member of a structure out to the containing structure。   

ptr:    the pointer to the member.   

type:   the type of the container struct this is embedded in.

member: the name of the member within the struct.    

#define container_of(ptr, type, member) ({                \

        const typeof( ((type *)0)->member ) *__mptr = (ptr);       \

(type *)( (char *)__mptr - offsetof(type,member) );})


       list_entry()宏,获取当前list_head链表节点所在的宿主结构项。第一个參数为当前list_head节点的指针,即指向宿主结构项的list_head成员。第二个參数是宿主数据结构的定义类型。第三个參数为宿主结构类型定义中list_head成员名。 

#define list_entry(ptr, type, member) \        

        container_of(ptr, type, member) 


扩展替换即为:  

#define list_entry(ptr, type, member) \      

        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))  


       比如,我们要訪问dtest链表(链表头为head)中首个元素,则如此调用:

list_entry(head->next, struct dtest, list);  

        经过C预处理的文字替换,这一行的内容就成为:

 ((struct dtest *)((char *)(head->next) - (unsigned long)(&((struct dtest *)0)->list))) 

        考虑list_head类型成员member相对于宿主结构(类型为type)起始地址的偏移量。对于全部该类型的宿主对象,这个偏移量是固定的。而且能够在如果宿主对象地址值为0,通过返回member成员的地址获得,即等于(unsigned long)(&((type *)0)->member)。这样,将当前宿主对象的"连接件"地址(ptr)减去这个偏移量,得到宿主对象地址,再将它转换为宿主数据结构类型的指针。 须要重申的是,链表头必须被嵌入到宿主对象中。

       而在MicroWindows用也採用了内似的方法将各个WNDCLASS链接起来,而且通过MwFindClassByName(lpWndClass->lpszClassName);函数查找相应的已经注冊过的WNDCLASS,如今来分析下MircoWindows的初始化程序吧,源代码是在Winmain.c中,例如以下所看到的:
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
       在上图中的0064行的MwOpen(ac, av)函数是这里重点分析的代码,而mwCreateInstance()函数是建立应用程序的句柄,可是它是相对于rootwp而言的,也就是Desktop,就是大家常说的桌面。而WinMain()就是我们须要实现的应用程序GUI,这个就是我们须要发挥的地方,眼下这里先分析MwOpen函吧,例如以下所看到的:
开源GUI-Microwindows之程序入口分析
       在MwOpen函数中,通过调用MwInitialize函数,而MwInitialize函数的详情例如以下:
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
这里主要分析这行代码:
wp->pClass = (PWNDCLASS)mwClassHead.head;,而mwClassHead的定义例如以下所看到的:
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
也就是说,在MicroWindows中也採用Linux中内似的链表来实现管理的,而mwClassHead有谁在使用呢,答案是RegisterClass(CONST WNDCLASS
*lpWndClass)函数,例如以下所看到的:
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
开源GUI-Microwindows之程序入口分析
      RegisterClass(CONST WNDCLASS *lpWndClass)函数中会去调用MwFindClassByName(LPCSTR lpClassName),而在MwFindClassByName(LPCSTR
lpClassName)函数中会去调用GdItemAddr,是否有似曾相识的感觉呢,只是,我们能够通过解析例如以下:
((WNDCLASS*)((long)mwClassHead.head - MWTEM_OFFSET(WNDCLASS, link)))
进一步的解析例如以下:
((WNDCLASS*)(  (long)mwClassHead.head -  ((long)&(((WNDCLASS *)0)->link))  )
      通过这种方式就能够查找到之前是否已经同样的控件注冊到WNDCLASS 系统中去啦。跟Linux内核的链表有异曲同工之妙哦。而WNDCLASS的定义在以下,通过定义能够看到在WNDCLASS中有一个link的成员变量,例如以下所看到的:
开源GUI-Microwindows之程序入口分析
从上图能够看到在tagWNDCLASSA结构体中有MWLIST link。

二:总结
       Microwindows也採用了一种类型无关的双循环链表实现方式。其思想是将指针head和tail从详细的数据结构中提取出来构成一种通用的"双链表"数据结构MWLISTHEAD。假设须要构造某类对象的特定链表,则在其结构(被称为宿主数据结构)中定义一个类型为MWLISTHEAD类型的成员,通过这个成员将这类对象连接起来,形成所需链表,并通过通用链表函数对其进行操作。其长处是仅仅需编写通用链表函数,就可以构造和操作不同对象的链表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。