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

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

作者: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中,如下所示:

在上图中的0064行的MwOpen(ac, av)函数是这里重点分析的代码,而mwCreateInstance()函数是建立应用程序的句柄,但是它是相对于rootwp而言的,也就是Desktop,就是大家常说的桌面。而WinMain()就是我们需要实现的应用程序GUI,这个就是我们需要发挥的地方,目前这里先分析MwOpen函吧,如下所示:

在MwOpen函数中,通过调用MwInitialize函数,而MwInitialize函数的详情如下:

这里主要分析这行代码:
wp->pClass = (PWNDCLASS)mwClassHead.head;,而mwClassHead的定义如下所示:

也就是说,在MicroWindows中也采用Linux中内似的链表来实现管理的,而mwClassHead有谁在使用呢,答案是RegisterClass(CONST WNDCLASS
*lpWndClass)函数,如下所示:

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的成员变量,如下所示:

从上图可以看到在tagWNDCLASSA结构体中有MWLIST link。

二:总结

Microwindows也采用了一种类型无关的双循环链表实现方式。其思想是将指针head和tail从具体的数据结构中提取出来构成一种通用的"双链表"数据结构MWLISTHEAD。如果需要构造某类对象的特定链表,则在其结构(被称为宿主数据结构)中定义一个类型为MWLISTHEAD类型的成员,通过这个成员将这类对象连接起来,形成所需链表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的链表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。

时间: 2024-12-24 09:01:43

开源GUI-Microwindows之程序入口分析的相关文章

cocos2d-x打飞机实例总结(一):程序入口分析和HelloWorld简单了解

首先,是个敲代码的,基本上都知道程序的入口是main函数,显然,就算在cocos2d-x框架中也一样 我们看看main函数做了什么 #include "main.h" #include "AppDelegate.h" #include "cocos2d.h" USING_NS_CC; int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdL

Hadoop源码分析—— Job任务的程序入口

这篇文章大致介绍Hadoop Job的程序是如何启动的. 通常用Java编写的Hadoop MapReduce程序是通过一个main方法作为程序的整个入口,如下: public static void main(String[] args) throws Exception { int res = ToolRunner.run(new Configuration(), new CalculateSumJob(),args); System.exit(res);} 可以看到这个Job任务的MapR

Swift程序入口深度分析

1.swift为什么不需要main 在c/c++及其它语言中都有一个main函数,程序从main作为起点,开始执行程序,如下: int main(int argc, const char * argv[]) { printf("Hello, World!\n"); return 0; } main函数实际上是一个特殊的函数,为了能找到程序入口,大多楼语言都约定main()函数作为入口.那么为什么在Swift中没有这样的一个函数呢?先看一下官方的解释 Code written at gl

理解计算机的工作方式——通过汇编一个简单的C程序并分析汇编代码

Author: 翁超平 Notice:原创作品转载请注明出处 See also:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  本文通过汇编一个简单的C程序,并分析汇编代码,来理解计算机是如何工作的.整个过程都在实验楼上完成,感兴趣的读者可以通过上面给出的课程链接自行动手学习.以下是实验过程和结果. 一.操作步骤 1.首先在通过vim程序建立main.c文件.代码如下: 图1 2.使用如下命令将main.c编

Linux安装程序Anaconda分析

1.概述 Anaconda是RedHat.CentOS.Fedora等Linux的安装管理程序.它能够提供文本.图形等安装管理方式,并支持Kickstart等脚本提供自己主动安装的功能.此外,其还支持很多启动參数,熟悉这些參数可为安装带来非常多方便.该程序的功能是把位于光盘或其它源上的数据包,依据设置安装到主机上.为实现该定制安装,它提供一个定制界面,能够实现交互式界面供用户选择配置(如选择语言,键盘,时区等信息).Anaconda的大部分模块用Python编写,有少许的加载模块用C编写. An

微信小程序再添能力:搜一搜增加小程序入口

随着移动互联网的快速发展,流量正悄然的变成大数据,借助"入口+流量"模式,互联网激活了大量的个性化入口,积累了巨量流量,并形成高企的资本市场估值.小程序作为当前最新的应用模式,在微信不断开放入口之后,或将迎来流量的爆发期. 今天,笔者发现,微信又为小程序开放了一个超级入口:微信搜一搜增添小程序入口.用户在微信发现中,通过搜一搜 输入关键词,比如水果,搜索页面便会出现相关小程序推荐,点击更多,可直接进入小程序"水果"的搜索页面,相关小程序都会出现在页面中. 点击微信搜

Quartz(GUI)图形界面程序----Quartz Web

下载.设置和运行Quartz(GUI)图形界面程序----Quartz Web 一.获取Quartz Web程序(Quartz GUI).早期的 Quartz 框架开发者意识到一个 GUI 对于某类用户群体是必需的.几年前,一个 Web 应用被创立,它可用于管理 Quartz 框架.虽说是历经了几年有相当投入的开发,但不得不说的,总是时断时续的.近来出现有更多的要求对这个应用的更新与支持,因而又重新吸引了新的开发者自愿的工作并使之保持不断更新.这个应用就是知名的 Quartz Web 程序.Qu

VS2010/MFC编程入门之四(MFC应用程序框架分析)

VS2010/MFC编程入门之四(MFC应用程序框架分析)-软件开发-鸡啄米 http://www.jizhuomi.com/software/145.html   上一讲鸡啄米讲的是VS2010应用程序工程中文件的组成结构,可能大家对工程的运行原理还是很模糊,理不出头绪,毕竟跟C++编程入门系列中的例程差别太大.这一节鸡啄米就为大家分析下MFC应用程序框架的运行流程. 一.SDK应用程序与MFC应用程序运行过程的对比        程序运行都要有入口函数,在之前的C++教程中都是main函数,

03-第一个C语言程序的分析

一.代码分析 二.开发和运行C程序的步骤 三.总结 说明:这个C语言专题,是学习iOS开发的前奏.也为了让有面向对象语言开发经验的程序员,能够快速上手C语言.如果你还没有编程经验,或者对C语言.iOS开发不感兴趣,请忽略 在上一篇中我们已经创建了一个C程序,接下来分析一下里面的代码. 项目结构如下: 回到顶部 一.代码分析 打开项目中的main.c文件(C程序的源文件拓展名为.c),可以发现它是第一个C程序中的唯一一个源文件,代码如下: 1 #include <stdio.h> 2 3 int