精华内容
下载资源
问答
  • 事件驱动 基本概念 窗口/组件 事件 消息(队列) 事件响应(服务处理程序) 调度算法 进程/线程 非阻塞I/O 程序的执行可以看成对CPU,内存,IO资源一次占用 现代操作系统支持...

    事件驱动

    基本概念

    • 窗口/组件

    • 事件

    • 消息(队列)

    • 事件响应(服务处理程序)

    • 调度算法

    • 进程/线程

    • 非阻塞I/O

    • 程序的执行可以看成对CPU,内存,IO资源一次占用

    • 现代操作系统支持多任务,可以分时复用上述资源.

    1. 为什么采用事件驱动模型?

    事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:

    • 首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方);

    • 当目标发送改变(发布),观察者(订阅者)就可以接收到改变;

    • 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。

    2. 代码执行流程

    在传统的或“过程化”的应用程序中,应用程序自身控制了执行哪一部分代码和按何种顺序执行代码。从第一行代码执行程序并按应用程序中预定的路径执行,必要时调用过程。
    在事件驱动的应用程序中,代码不是按照预定的路径执行-而是在响应不同的事件时执行不同的代码片段。事件可以由用户操作触发、也可以由来自操作系统或其它应用程序调度算法的消息触发、甚至由应用程序本身的消息触发。这些事件的顺序决定了代码执行的顺序,因此应用程序每次运行时所经过的代码的路径都是不同的。

    3. 事件驱动模型

    在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢?

    方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:

    1. CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?
    2. 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;
    3. 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;所以,该方式是非常不好的。

    方式二:就是事件驱动模型目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

    1. 有一个事件(消息)队列;

    2. 鼠标按下时,往这个队列中增加一个点击事件(消息);

    3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;

    4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;如图:

     

    4. 事件驱动处理库

    • select

    • poll

    • epoll

    • libev

    5.效率比较 

    让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。

     

    在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。

    在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

    在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

    当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

    程序中有许多任务,而且…
    任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…
    在等待事件到来时,某些任务会阻塞。
    当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

    网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

    事件驱动机制跟消息驱动机制相比

    消息驱动和事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们的区别是消息是谁产生的

    消息驱动:鼠标管自己点击不需要和系统有过多的交互,消息由系统(第三方)循环检测,来捕获并放入消息队列。消息对于点击事件来说是被动产生的,高内聚。

    事件驱动:鼠标点击产生点击事件后要向系统发送消息“我点击了”的消息,消息是主动产生的。再发送到消息队列中。

     

    事件:按下鼠标,按下键盘,按下游戏手柄,将U盘插入USB接口,都将产生事件。比如说按下鼠标左键,将产生鼠标左键被按下的事件。

    消息:当鼠标被按下,产生了鼠标按下事件,windows侦测到这一事件的发生,随即发出鼠标被按下的消息到消息队列中,这消息附带了一系列相关的事件信息,比如鼠标哪个键被按了,在哪个窗口被按的,按下点的坐标是多少?如此等等。

    注意:

    1. 要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu.
    2. 再说什么是事件驱动的程序。一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。
    3. 事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。
    4. 事件驱动的程序的行为,完全受外部输入的事件控制,所以,事件驱动的系统中,存在大量这种程序,并以事件作为主要的通信方式。
    5. 事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。
    6. 目前windows,linux,nucleus,vxworks都是事件驱动的,只有一些单片机可能是非事件驱动的。


    事件模式耦合高,同模块内好用;消息模式耦合低,跨模块好用。事件模式集成其它语言比较繁琐,消息模式集成其他语言比较轻松。事件是侵入式设计,霸占你的主循环;消息是非侵入式设计,将主循环该怎样设计的自由留给用户。如果你在设计一个东西举棋不定,那么你可以参考win32的GetMessage,本身就是一个藕合度极低的接口,又足够自由,接口任何语言都很方便,具体应用场景再在其基础上封装成事件并不是难事,接口耦合较低,即便哪天事件框架调整,修改外层即可,不会伤经动骨。而如果直接实现成事件,那就完全反过来了。

     

    什么是数据驱动编程

    正题

    作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。

    核心

    数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。

    真的是这样吗?让我们来看一个示例。假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理: 

    void msg_proc(const char *msg_type, const char *msg_buf) 
    { 
        if (0 == strcmp(msg_type, "inivite")) 
        { 
            inivite_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "tring_100")) 
        { 
            tring_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "ring_180")) 
        { 
            ring_180_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "ring_181")) 
        { 
            ring_181_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "ring_182")) 
        { 
            ring_182_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "ring_183")) 
        { 
            ring_183_fun(msg_buf); 
        } 
        else if (0 == strcmp(msg_type, "ok_200")) 
        { 
            ok_200_fun(msg_buf); 
        }
    
        。。。。。。 
        else if (0 == strcmp(msg_type, "fail_486")) 
        { 
            fail_486_fun(msg_buf); 
        } 
        else 
        { 
            log("未识别的消息类型%s\n", msg_type); 
        } 
    } 


    上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,没增加一个消息,就要增加一个流程分支。

    按照数据驱动编程的思路,可能会这样设计: 

    typedef void (*SIP_MSG_FUN)(const char *);
    
    typedef struct __msg_fun_st 
    { 
        const char *msg_type;//消息类型 
        SIP_MSG_FUN fun_ptr;//函数指针 
    }msg_fun_st;
    
    msg_fun_st msg_flow[] = 
    { 
            {"inivite", inivite_fun}, 
            {"tring_100", tring_fun}, 
            {"ring_180", ring_180_fun}, 
            {"ring_181", ring_181_fun}, 
            {"ring_182", ring_182_fun}, 
            {"ring_183", ring_183_fun}, 
            {"ok_200", ok_200_fun},
    
            。。。。。。 
            {"fail_486", fail_486_fun} 
    };
    
    void msg_proc(const char *msg_type, const char *msg_buf) 
    { 
        int type_num = sizeof(msg_flow) / sizeof(msg_fun_st); 
        int i = 0;
    
        for (i = 0; i < type_num; i++) 
        { 
            if (0 == strcmp(msg_flow[i].msg_type, msg_type)) 
            { 
                msg_flow[i].fun_ptr(msg_buf); 
                return ; 
            } 
        } 
        log("未识别的消息类型%s\n", msg_type); 
    } 

     

    数据驱动优势

    1、可读性更强,消息处理流程一目了然。

    2、更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。

    3、重用,第一种方案的很多的else if其实只是消息类型和处理函数不同,但是逻辑是一样的。下面的这种方案就是将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。

    隐含在背后的思想

    很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:

    1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。

    2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。

    3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。

    数据驱动可以用来做什么:

    如上例所示,它可以应用在函数级的设计中。

    同时,它也可以应用在程序级的设计中,典型的比如用表驱动法实现一个状态机(后面写篇文章专门介绍)。

    也可以用在系统级的设计中,比如DSL(这方面我经验有些欠缺,目前不是非常确定)。

    它不是什么:

    1、 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;

    2、它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”

    书中的值得思考的话:

    数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike

    程序员束手无策。。。。。只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks

    数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。

     

    转载自:学时网 » 事件驱动编程、消息驱动编程、数据驱动编程

    展开全文
  • 12.队列的应用-事件驱动编程

    千次阅读 2014-05-05 17:27:52
    事件驱动编程简而言之就是应用程序维护一个或多个事件队列,完全以事件队列为主线来设计和编写整个程序。这里利用队列实现了一个完整的事件驱动编程过程,模拟了银行排队这一离散事件的发生。

    1.事件驱动编程

    前面我们提到队列在操作系统中用的非常多,其中一大应用就是基于事件驱动的消息队列机制,熟悉Windows SDK程序设计的对于Windows的消息产生、分发、处理应该不会陌生。

    事件驱动编程简而言之就是应用程序维护一个或多个事件队列,完全以事件队列为主线来设计和编写整个程序。如Windows就是以消息队列为中心来对每个消息进行处理从而实现事件驱动编程(如鼠标、键盘等事件的处理)。


    2.离散事件模拟

    现在为了演示事件驱动编程,我可能并没有能力去构建一个windows消息系统,这里以类似的应用:[银行排队模拟]来演示整个程序。

    银行排队模拟描述如下:

    假设一个银行有4个窗口对外接待客户。由于每个窗口在某一时刻只能接待一个客户,在客户众多的时候需要排队,对于刚进入银行的客户,如果某个窗口正空闲,则可上前办理业务,如果所有窗口都不空闲则排在人数最少的窗口。现在要求模拟银行的某一时间段内的4个窗口的客户排队情况。这里客户到达的时刻和办理业务的时间都是随机的。


    可以看到不同的时刻4个窗口的客户排队情况是不一样的,这个客户排队情况完全是以时间推移来驱动的,那么我们就考虑建立一个基于时间的事件队列,按照事件发生的先后排列事件。这种类似的应用统称为离散事件模拟。


    3.实现方式

    事件队列元素定义如下

    typedef struct
    {
    	int		nOccurTime;	//事件发生时间
    	int		nType;		//事件类型,0-客户到达事件,1、2、3、4-对应窗口客户离开事件
    }JWArrayElem_event;

    窗口队列元素定义如下

    typedef struct
    {
    	int nArriveTime;		//客户到达事件
    	int nDurationTime;		//客户办理业务所需时间
    }JWArrayElem_window;


    为了实现上述离散事件模拟,这里我们建立一个事件队列,4个窗口排队队列。

    初始化上述队列,在事件队列中插入第一个客户到达事件以启动事件驱动。

    开启计时,检测是否有事件要发生。

    如果是客户到达事件,则设置客户办理业务时间,插入到窗口队列中,同时要插入下一个客户到达事件。如果是当前窗口的第一个客户,还要插入一个客户离开事件以驱动当前窗口的客户离开事件。

    如果是客户离开事件,则从对应窗口队列中取出客户并设置下一个客户离开事件,在同一窗口下前一个客户离开事件没有发生之前是不可能发生第二个客户离开事件的。


    4.实际程序

    #include <stdio.h>
    #include <process.h>
    #include <stdlib.h>
    #include <time.h>
    #include <Windows.h>
    
    #include "JWArray.h"
    
    JWArray(event)		*pEventQueue;					//事件队列
    JWArrayElem(event)	eventElem;						//事件元素
    JWArray(window)		*pWindowsQueue[5];				//窗口队列,为了使用方便,下标从1开始
    JWArrayElem(window)	windowElem;						//窗口元素
    int					nCloseTime = 100;				//银行关闭时间
    
    JWArray_BOOL EventCompare(JWArrayElem(event) elem1, JWArrayElem(event) elem2);
    void ShowState(int nCurTime);
    void Init();
    void Clear();
    int  CalcMinWindow();
    void CustomerArrived(int nArriveTime);
    void CustomerLeave(int nCurTime, int iLeave);
    void EventPump();
    void ShowState(int nCurTime);
    
    
    int main(int argc, char *argv[])
    {
    	//开启事件泵
    	EventPump();
    
    	return 0;
    }
    
    /**
     *功能:	事件队列元素比较函数
     *参数:	elem1,elem2 -- 两个事件队列元素
     *返回:	elem1的发生时间小于elem2则返回JWArray_TRUE,否则返回JWArray_FALSE
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    JWArray_BOOL EventCompare(JWArrayElem(event) elem1, JWArrayElem(event) elem2)
    {
    	return elem1.nOccurTime  < elem2.nOccurTime ? JWARRAY_TRUE : JWARRAY_FALSE;
    }
    
    /**
     *功能:	初始化--分配事件队列和窗口队列,在事件队列插入启动的客户到达事件
     *参数:	无
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void Init()
    {
    	int i;
    
    	//分配队列
    	pEventQueue = JWArrayCreate(event)(20, 10);
    	for (i = 1; i < 5; i++)
    	{
    		pWindowsQueue[i] = JWArrayCreate(window)(20, 10);
    	}
    
    	//在事件队列插入第一个客户到达事件
    	eventElem.nOccurTime	= 0;
    	eventElem.nType			= 0;
    
    	JWArrayEnQueue(event)(pEventQueue, eventElem);
    }
    
    /**
     *功能:	清理工作--清理事件队列和窗口队列
     *参数:	无
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void Clear()
    {
    	int i;
    
    	//清理队列
    	JWArrayDestroy(event)(pEventQueue);
    	for (i = 1; i < 5; i++)
    	{
    		JWArrayDestroy(window)(pWindowsQueue[i]);
    	}
    }
    
    /**
     *功能:	计算当前窗口队列前人数最少的窗口号
     *参数:	无
     *返回:	人数最少的窗口队列号
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    int CalcMinWindow()
    {
    	int nLength ;
    	int i ;
    	int nIndex ;
    	
    	nIndex	 = 1;
    	nLength  = JWArrayGetLength(window)(pWindowsQueue[1]);
    	for (i = 2; i <=4 ; i++)
    	{
    		if (JWArrayGetLength(window)(pWindowsQueue[i]) < nLength)
    		{
    			nIndex = i;
    			nLength  = JWArrayGetLength(window)(pWindowsQueue[i]);
    		}
    	}
    
    	return nIndex;
    }
    
    /**
     *功能:	处理客户到达事件
     *参数:	nArriveTime--客户到达的时间
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void CustomerArrived(int nArriveTime)
    {
    	int i;
    	int nDurationtime;			//当前客户办理业务消耗时间
    	int nNextInterTime;			//距离下一个客户到达时间
    
    	//随机生成nDurationtime和nNextInterTime
    	srand( (unsigned)time( NULL ) );
    	nDurationtime = rand() % 30 +1;
    	nNextInterTime = rand() % 5 +1;
    
    	//设置下一个客户到达事件
    	if (nArriveTime + nNextInterTime < nCloseTime)
    	{
    		eventElem.nOccurTime = nArriveTime + nNextInterTime;
    		eventElem.nType = 0;
    
    		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
    	}
    
    	//设置当前窗口排列队形
    	i = CalcMinWindow();
    	windowElem.nArriveTime = nArriveTime;
    	windowElem.nDurationTime = nDurationtime;
    	JWArrayEnQueue(window)(pWindowsQueue[i], windowElem);
    
    	//如果是第一个到达客户,插入一个离开事件,否则此后的离开事件在前一个离开事件发生后插入
    	if (JWArrayGetLength(window)(pWindowsQueue[i]) == 1)
    	{
    		eventElem.nOccurTime = nArriveTime + nDurationtime;
    		eventElem.nType = i;
    
    		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
    	}
    }
    
    /**
     *功能:	处理客户离开事件
     *参数:	nCurTime--客户离开事件发生时间,iLeave--客户离开事件发生的窗口队列号
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void CustomerLeave(int nCurTime, int iLeave)
    {
    	//window队列出列
    	JWArrayDeQueue(window)(pWindowsQueue[iLeave], NULL);
    
    	//设置下一个客户离开时间
    	if (JWARRAY_FALSE == JWArrayIsEmpty(window)(pWindowsQueue[iLeave]))
    	{
    		JWArrayGetHead(window)(pWindowsQueue[iLeave], &windowElem);
    
    		eventElem.nOccurTime = nCurTime + windowElem.nDurationTime;//注意这里实际离开时间是当前离开事件发生的时间加上下一个客户要办理业务的时间,不能写成下一个客户到达的时间加上下一个客户要办理业务的时间,这是不对的,因为客户到达时不能立即开始办理,要等待!!!
    		eventElem.nType = iLeave;
    
    		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
    	}
    }
    
    /**
     *功能:	开启消息泵,依据时间对事件队列处理从而推动整个事件的发生
     *参数:	无
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void EventPump()
    {
    	int i=0;
    
    	Init();
    
    	do 
    	{
    		if (JWARRAY_FALSE != JWArrayGetHead(event)(pEventQueue, &eventElem))
    		{
    			while (eventElem.nOccurTime == i)//处理当前时刻可能同时存在的多个事件
    			{
    				JWArrayDeQueue(event)(pEventQueue, &eventElem);
    
    				if (eventElem.nType == 0)
    				{
    					CustomerArrived(eventElem.nOccurTime);
    				}
    				else
    				{
    					CustomerLeave(i, eventElem.nType);
    				}
    
    				if (JWARRAY_FALSE == JWArrayGetHead(event)(pEventQueue, &eventElem))
    				{
    					break;
    				}
    			}
    		}
    		else
    		{
    			break;
    		}
    
    		ShowState(i++);
    		Sleep(100);
    	} while (1);
    
    	Clear();
    }
    
    /**
     *功能:	显示当前事件队列和各个窗口队列的情况
     *参数:	无
     *返回:	无
     *其他:	2014/05/05 By Jim Wen Ver1.0
    **/
    void ShowState(int nCurTime)
    {
    	system("cls");
    	printf("当前时间:%d\n\n", nCurTime);
    
    	printf("--------------------------------------------------------------------------------\n");
    	printf("事件队列\t");
    	JWArrayTraverse(event)(pEventQueue, JWArrayPrintfElem(event));
    	printf("\n--------------------------------------------------------------------------------\n\n");
    
    	printf("\n--------------------------------------------------------------------------------\n");
    	printf("窗口1\t");
    	JWArrayTraverse(window)(pWindowsQueue[1], JWArrayPrintfElem(window));
    	printf("\n--------------------------------------------------------------------------------\n");
    	printf("窗口2\t");
    	JWArrayTraverse(window)(pWindowsQueue[2], JWArrayPrintfElem(window));
    	printf("\n--------------------------------------------------------------------------------\n");
    	printf("窗口3\t");
    	JWArrayTraverse(window)(pWindowsQueue[3], JWArrayPrintfElem(window));
    	printf("\n--------------------------------------------------------------------------------\n");
    	printf("窗口4\t");
    	JWArrayTraverse(window)(pWindowsQueue[4], JWArrayPrintfElem(window));
    	printf("\n--------------------------------------------------------------------------------\n");
    }

    程序说明:
    1.事件时间单位为秒
    2.假设客户办理业务不超过30秒,相邻两个客户到达时间间隔不超过5秒
    3.银行在100秒后不再允许客户进入
    4.不考虑客户在排队过程中换队的情况
    5.这里对于event队列构建了一个顺序插入函数JWArrayOrderInsert(event)

    程序运行效果如下



    完整事件驱动编程源代码下载链接
    原创,转载请注明来自http://blog.csdn.net/wenzhou1219
    展开全文
  • 事件驱动编程

    千次阅读 2015-03-11 11:14:52
    查了资料,有点乱,理解这个概念看来不易,先把看过的做下笔记: 要看是哪一层的,  例如这个,貌似是对于单任务的操作系统,适用于一些简单那的单片机,这个其实是在一个顺序结构的程序里写...这就是事件驱动 http

    查了资料,有点乱,理解这个概念看来不易,先把看过的做下笔记:


    要看是哪一层的, 

    1. 例如这个,貌似是对于单任务的操作系统,适用于一些简单那的单片机,这个其实是在一个顺序结构的程序里写了个状态机,然后无限循环里去跑这个状态机,每个状态机对应一个task,有个专门的类实现事件队列,有专门的发送事件的api,每次进入状态机之前都要读取事件,根据事件进去对应的task。这就是事件驱动

    http://wenku.baidu.com/view/44156db465ce05087632136a.html


    2. 那对于多任务的怎么事件驱动呢,多任务的不都是按时间片或者按优先级调度多任务的吗。

    很多资料说windows是事件驱动的。看这个文章,mfc不就是消息驱动(事件驱动)吗。windows就是这种方式,根据用户发来的消息,每个程序都有自己专门的消息队列。当然还有个总的消息队列。系统在队列中去除每一条消息,根据句柄将该消息发送给拥有该窗口的程序(一个程序包括多个窗口)的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。



    =============================================================

    http://www.qudong.com/soft/program/C/rumenjiaocheng/20080317/1362.html       

    Windows系统是一个消息驱动的OS,什么是消息呢?我很难说得清楚,也很难下一个定义(谁在嘘我),我下面从不同的几个方面讲解一下,希望大家看了后有一点了解。

    1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转中之后会有WM_COMMAND消息发送,WPARAM的高字中(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。

    2、谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。

    3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。

    4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。

    5、示例:下面有一段伪代码演示如何在窗口过程中处理消息

    LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
    {
    	switch(uMessageType)
    	{//使用SWITCH语句将各种消息分开
    		case(WM_PAINT):
    			doYourWindow(...);//在窗口需要重新绘制时进行输出
    		break;
    		case(WM_LBUTTONDOWN):
    			doYourWork(...);//在鼠标左键被按下时进行处理
    		break;
    		default:
    			callDefaultWndProc(...);//对于其它情况就让系统自己处理
    		break;
    	}
    }
    

     

    接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。下面的伪代码演示了消息循环的用法:

    while(1)
    {
    	id=getMessage(...);
    	if(id == quit)
    		break;
    	translateMessage(...);
    }
    

    当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 下图为消息投递模式


     

    在16位的系统系统中只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷如死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统

    而32位的系统中每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统


    1、Windows事件驱动机制

      我们当中不少使用VC、Delphi等作为开发语言的程序员是一步步从DOS下的Basic、C++中走过来的,而且大多在刚开始学习编程时也是先从DOS下的编程环境入手的,因此在习惯了DOS下的过程驱动形式的顺序程序设计方法后,往往在向Windows下的开发环境转型的过程中会对Windows所采取的事件驱动方式感到无法适应。因为DOS和Windows这两种操作系统的运行机制是截然不同的,DOS下的任何程序都是使用顺序的、过程驱动的程序设计方法。这种程序都有一个明显的开始、明显的过程以及一个明显的结束,因此通过程序就能直接控制程序事件或过程的全部顺序。即使是在处理异常时,处理过程也仍然是顺序的、过程驱动的结构。而Windows的驱动方式则是事件驱动的,即程序的流程不是由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在编写程序时,并不知道用户会先按下哪个按纽,也就不知道程序先触发哪个消息。因此我们的主要任务就是对正在开发的应用程序要发出的或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。


    2、Windows的消息循环
      Windows操作系统为每一个正在运行的应用程序保持有一个消息队列。当有事件发生后,Windows并不是将这个激发事件直接送给应用程序,而是先将其翻译成一个Windows消息,然后再把这个消息加入到这个应用程序的消息队列中去。应用程序需要通过消息循环来接收这些消息。在MFC中使用了对WinAPI进行了很好封装的类库,虽然可以为编程提供一个面向对象的界面,使Windows程序员能够以面象对象的方式进行编程,把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现,但是由于引入了很好的封装特性,使我们不能直接操纵部分核心代码。对于消息的循环和接收也只是通过类似于下面的消息映射予以很简单的表示:

      BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)

      //{{AFX_MSG_MAP(CTEMMSView)

      ON_WM_LBUTTONDOWN()

      ON_COMMAND(ID_OPENDATA, OnOpenData)

      ON_WM_TIMER()

      ON_WM_PAINT()

      //}}AFX_MSG_MAP

      END_MESSAGE_MAP()

      虽然上述消息映射在编程过程中处理消息非常简练方便,但显然是难于理解消息是如何参与循环和分发的。因此有必要通过SDK(Software Developers Kit,软件开发工具箱)代码深入到被MFC封装的Windows编程的核心中来研究其具体是如何工作的。在SDK编程中,一般是在Windows应用程序的入口点WinMain函数中添加处理消息循环的代码以检索Windows送来的消息,然后WinMain再把这些消息分配给相应的窗口函数并处理它们:

      ……

      MSG msg; //定义消息名

      while (GetMessage (&msg, NULL, 0, 0))

      {

      TranslateMessage (&msg) ; //翻译消息

      DispatchMessage (&msg) ; //撤去消息

      }

      return msg.wParam ;

      上述几句虽然简单但却是所有Windows程序的关键代码,担负着获取、解释和分发消息的任务,下面就重点对其功能和作用进行分析:

      MSG结构在头文件中定义如下:

      typedef struct tagMSG

      {

      HWND hwnd;

      UINT message;

      WPARAM wParam;

      LPARAM lParam;

      DWORD time;

      POINT pt;

      } MSG, *PMSG;

      其数据成员的具体意义如下:

      hwnd:消息将要发送到的那个窗口的句柄,用这个参数可以决定让哪个窗口接收消息。

      message:消息号,它唯一标识了一种消息类型。每种消息类型都在Windows文件进行了预定义。

      wParam:一个32位的消息参数,这个值的确切意义取决于消息本身。

      lParam:同上。

      time:消息放入消息队列中的时间,在这个域中写入的并非当时日期,而是从Windows启动后所测量的时间值。Windows用

      这个域来使用消息保持正确的顺序。

      pt:消息放入消息队列时的鼠标坐标。

      消息循环以GetMessage调用开始,它从消息队列中取出一个消息。该函数的四个参数可以有控制地获取消息,第一个参数指定要接收消息的MSG结构的地址,第二个参数表示窗口句柄,一般将其设置为空,表示要获取该应用程序创建的所有窗口的消息;第三、四参数用于指定消息范围。后面三个参数被设置为默认值,用于接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()返回TRUE;如果GetMessage收到一个WM_QUIT消息,则返回FALSE以退出消息循环,终止程序运行。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。当除WM_QUIT的消息用GetMessage读入后,首先要经过函数TranslateMessage()对其进行解释,但对大多数消息来说并不起什么作用。这里起关键作用的是DispatchMessage()函数,把由GetMessage获取的Windows消息传送给在MSG结构中为窗口所指定的窗口过程。在消息处理函数处理完消息之后,代码又循环到开始去接收另一个消息,这样就完成了一个完整的消息循环。

      由于Windows操作系统是一种非剥夺式多任务操作系统。只有在应用程序主动交出CPU控制权后,Windows才能把控制权交给其他应用程序。在消息循环中,一定要有能交出控制的系统函数才能实现协同式多任务操作。能完成该功能的只有GetMessage、PeekMessage和WaitMessage这三个函数,如果在应用程序中长期不去调用这三个函数之一其他任务则无法执行。GetMessage函数在找不到等待应用程序处理的消息时,会自动交出控制权,由Windows把CPU的控制权交给其他等待获取控制权的应用程序。由于任何Windows应用程序都含有一个消息循环,这种隐式交出控制权的方式可以保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。使用GetMessage函数的消息循环在消息队列中没有消息时将等待,如果需要,可以利用这段时间进行I/O端口操作等耗时操作,不过需要在消息循环中使用PeekMessage函数来代替GetMessage。使用PeekMessage的方法同GetMessage类似,下面是一段使用PeekMessage函数的消息循环的典型例子:

      MSG msg;

      BOOL bDone=FALSE;

      do{

      if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){

      if(msg.message==WM_QUIT)

      bDone=TRUE;

      else{

      TranslateMessage(&msg);

      DispatchMessage(&msg);

      }

      }

      //无消息处理,进行长时间操作

      else{

      ……//长时间操作

      }

      }while(!bDone)

      ……

      无论应用程序消息队列中是否有消息,PeekMessage函数都立即返回,如果希望等待新消息入队,可以利用无返回值的函数WaitMessage配合PeekMessage进行消息循环。

      四、对Windowds消息的处理

      窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句,这在功能上同MFC的消息映射有些类似:

      switch(uMsgId)

      {

      case WM_TIMER:

      //对WM_TIMER定时器消息的处理过程

      return 0;

      case WM_LBUTTONDOWN:

      //对WM_ LBUTTONDOWN鼠标左键单击消息的处理过程

      ruturn 0;

      ……

      default:

      //其他消息由这个默认处理函数来处理

      return DefWindowProc(hwnd,uMsgId,wParam,lParam);

      }

      在处理完消息后必须返回0,这很重要,否则Windows将要不停地重试下去。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。该函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,如更新窗口的正文标题等等。 在MFC下可以用下述部分代码实现与上述SDK代码相同的功能:

      BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)

      //{{AFX_MSG_MAP(CTEMMSView)

      ON_WM_LBUTTONDOWN()

      ON_WM_TIMER()

      //}}AFX_MSG_MAP

      END_MESSAGE_MAP()

      小结:Windows环境提供有非常丰富的系统资源,在这个基础上可以编制出能满足各种各样目标功能的应用系统。要深入Windows编程就必须首先对Windows系统的运行机理有很好的认识,本文仅针对Windows的一种重要运行机制--消息机制作了较深入的剖析和阐述。对培养在Windows下的编程思想有一定的帮助。对某些相关问题的详细论述可以参考MSDN在线帮助的"SDK Reference"部分。

    http://blog.csdn.net/sshhbb/archive/2010/12/14/6076095.aspx



    1.窗口(Windows)和句柄(HANDLE,handle:窗口句柄(HWND)图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH
    2.消息,消息队列,消息循环,消息响应
     .OS将操作包装成Message
     .typedef struct MSG {       
          HWND   hwnd; //窗口句柄,即标示消息所属的窗口     
          UINT   message;//标示消息的类别,是鼠标还是键盘等 如鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR
          WPARAM wParam;//消息的附加信息
          LPARAM lParam;//消息的附加信息
          DWORD  time;//消息投递到消息队列中的时间
          POINT  pt;//鼠标的当前位置
       } MSG;
     .消息队列,每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息
     .进队消息(OS将产生的消息放在应用程序的消息队列中,让应用程序来处理)
      不进队消息(OS直接调用窗口的处理过程)
     .Windows应用程序的消息处理机制
      while(GetMessage(&msg,NULL,0,0)){//接收到WM_QUIT消息时,才返回0
         
    TranslateMessage(&msg);//对消息进行包装处理然后再以消息的形式投放到消息队列
         
    DispatchMessage(&msg);//消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理
     
    }

    (1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
    (2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条条的消息。取出后,以对消息进行一些预处理,如放弃对某些消息的响应,或者调用TranslateMessage产生新的消息
    (3)应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。(4)系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。
     .窗口过程函数
       lresult callback windowproc(

            hwnd hwnd,          // 对应消息的窗口句柄

            uint umsg,           // 消息代码,区别消息的类型

            wparam wparam,      // 消息代码的附加参数1

            lparam lparam       // 消息代码的附加参数2

           );
    【蔡学镛.揭开消息循环的神秘面纱】


    简单归纳如下:
       讯息循环被封装进了 Application 类别的 Run() 静态方法中;Windows Procedure 被封装进了 NativeWindow 与 Control 类别中;
    个别的讯息处理动作被封装进 Control 类别的 OnXyz()(例如 OnPaint())。我们可以覆盖(override)OnXyz(),来提供我们自己的程序。
    也可以利用.NET的事件(event)机制,在 Xyz 事件上,加入我们的事件处理函式(Event Handler)。Control 类别的 OnXyz() 会主动呼叫 Xyz 事件的处理函式。
    请注意,因为 Xyz 的事件处理函式是由 Control 类别的 OnXyz() 方法所呼叫的,所以当你覆写 OnXyz() 方法时,
    不要忘了呼叫 Control 类别的 OnXyz()(除非你有特殊需求),否则 Xyz 事件处理函式将会没有作用。
    只要呼叫 base.OnXyz(),就可以呼叫到 Control 类别的 OnXyz() 方法
    1. 在 Main() 中,利用 Application.Run() 来将 Form1 窗口显示出来,并进入讯息循环。程序的执行过程中,Application.Run() 一直未结束。 
    2. OS 在此 Process 的讯息队列内放进一个 WM_PAINT 讯息,好让窗口被显示出来。 
    3. WM_PAINT 被 Application.Run() 内的讯息循环取出来,发派到 WndProc()。由于多型(Polymorphism)的因素,此次调用(invoke)到的 WndProc() 是属于    Form1 的 WndProc(),也就是上述程序中批注(comment)1 的地方,而不是调用到 Control.WndProc()。 
    4. 在 Form1.WndProc() 的最后,有调用 base.WndProc(),这实际上调用到 Control.WndProc()。 
    5. Control.WndProc() 从 Message 参数中得知此讯息是 WM_PAINT,于是调用 OnPaint()。由于多型的因素,此次调用到的 OnPaint() 是属于 Form1 的 OnPaint(),也就是上述程序中批注 2 的地方,而不是调用到 Control.OnPaint()。 
    6. 在 Form1.OnPaint() 的最后,有调用 base.OnPaint(),这实际上调用到 Control.OnPaint()。 
    7. 我们曾经在 Form1 的建构式(constructor)中将 Form1_Paint() 与 Form1_Paint2() 登记成为 Paint 事件处理函式(Event Handler)。Control.OnPaint() 会去依序去呼叫这两个函式,也就是上述程序中批注 3 与 4 的地方。 

    【.NET Windows Message】
    1.Control--Button,Form……
      protect vitrual WndProcess(ref Message);
        调用private Wm_(ref Message);//具体某类消息
          调用Oprotect virtual On_xx(EventArg e);//触发相关事件
    2.解释事件冒泡:比如键盘消息可先由Form来处理,然后交由相关的Control来处理
    3.解释FormPaint:窗口的移动,最小化,最大话都会引起窗口内容的重新Paint,OS产生一个相关消息发给应用程序的消息队列,应用程序得到并处理消息时先是Form依次经过Wn_Process,Wn_..,On_Paint,Form_Paint,之后Form中的每一个Control也会依次做重绘的工作

    http://www.cnblogs.com/jeemhu/archive/2009/05/09/1453297.html



    =============================================================

    3. inc的code只能说task1是事件驱动的。task2作为输入和处理其它操作。


    4,但是inc里说事件驱动和操作interface时会导致cpu停止工作是有什么联系呢,事件驱动的概念和这个貌似没有关系啊。



    总结:

    1.单任务系统和多任务系统,好像都是一个task(进程)里有个状态机,然后由事件驱动。window是多任务,但是每个任务都有一个自己的消息循环。


    展开全文
  • windows驱动编程入门

    万次阅读 2010-04-09 10:49:00
    刚学windows驱动,经过几天的摸索,有一些经验,先总结如下;必备工具:windows驱动开发包:wdk;调试工具:windbg;驱动加载工具:INSTDRV.exe 至于windows驱动的一些流程,说实话我也没弄太清楚,只不过先弄一个...

    刚学windows驱动,经过几天的摸索,有一些经验,先总结如下;

    必备工具:windows驱动开发包:wdk;调试工具:windbg;驱动加载工具:INSTDRV.exe

     

    至于windows驱动的一些流程,说实话我也没弄太清楚,只不过先弄一个例子手动抄写一些,编译调试一下看看吧。

    例子程序是我在网上找的:

    /***************************************************************
      程序名称:Hello World for WDM
      文件名称:HelloWDM.cpp
      作者:罗聪
      日期:2002-8-16
      ***************************************************************/
      //一定要的头文件,声明了函数模块和变量:
      #include "HelloWDM.h"
      /***************************************************************
      函数名称:DriverEntry()
      功能描述:WDM程序入口
      ***************************************************************/
      //extern "C"是必须的,表示“用C链接”。如果你的文件名是HelloWDM.c的话,这句可以省略。
      extern "C"
      NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,
       IN PUNICODE_STRING RegistryPath)
      {
       //指定“添加设备”消息由函数“HelloWDMAddDevice()”来处理:
       DriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
       //指定“即插即用”消息由函数“HelloWDMPnp()”来处理:
       DriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
       //返回一个NTSTATUS值STATUS_SUCCESS。几乎所有的驱动程序例程都必须返回一个NTSTATUS值,这些值在NTSTATUS.H DDK头文件中有详细的定义。
       return STATUS_SUCCESS;
      }
      /***************************************************************
      函数名称:HelloWDMAddDevice()
      功能描述:处理“添加设备”消息
      ***************************************************************/
      NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
       IN PDEVICE_OBJECT PhysicalDeviceObject)
      {
       //定义一个NTSTATUS类型的返回值:
       NTSTATUS status;
       //定义一个功能设备对象(Functional Device Object):
       PDEVICE_OBJECT fdo;
       //创建我们的功能设备对象,并储存到fdo中:
       status = IoCreateDevice(
       DriverObject, //驱动程序对象
       sizeof(DEVICE_EXTENSION), //要求的设备扩展的大小
       NULL, //设备名称,这里为NULL
       FILE_DEVICE_UNKNOWN, //设备的类型,在标准头文件WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
       0, //各种常量用OR组合在一起,指示可删除介质、只读等。
       FALSE, //如果一次只有一个线程可以访问该设备,为TRUE,否则为FALSE
       &fdo); //返回的设备对象
       //NT_SUCCESS宏用于测试IoCreateDevice内核是否成功完成。不要忘记检查对内核的所有调用是否成功。NT_ERROR宏不等同于!NT_SUCCESS,最好使用!NT_SUCCESS,因为除了错误外,它还截获警告信息。
       if( !NT_SUCCESS(status))
       return status;
       //创建一个设备扩展对象dx,用于存储指向fdo的指针:
       PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
       dx->fdo = fdo;
       //用IoAttachDeviceToDeviceStack函数把HelloWDM设备挂接到设备栈:
       dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
       //设置fdo的flags。有两个“位”是必须改变的,一个是必须清除DO_DEVICE_INITIALIZING标志,如果在DriverEntry例程中调用IoCreateDevice(),就不需要清除这个标志位。还有一个是必须设置DO_BUFFER_IO标志位:
       fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
       fdo->Flags &= ~DO_DEVICE_INITIALIZING;
       //返回值:
       return STATUS_SUCCESS;
      }
      /***************************************************************
      函数名称:HelloWDMPnp()
      功能描述:处理“即插即用”消息
      ***************************************************************/
      NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
       IN PIRP Irp)
      {
       //创建一个设备扩展对象dx,用于存储指向fdo的指针:
       PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
       //首先要通过函数IoGetCurrentIrpStackLocation()得到当前的IRP,并由此得到Minor Function:
       PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
       ULONG MinorFunction = IrpStack->MinorFunction;
       //然后把这个Minor Function传递给下一个设备栈:
       IoSkipCurrentIrpStackLocation(Irp);
       NTSTATUS status = IoCallDriver( dx->NextStackDevice, Irp);
       //处理“即插即用”次功能代码:
       //当Minor Function等于IRP_MN_REMOVE_DEVICE时,说明有设备被拔出或卸下,这时要取消资源分配并删除设备:
       if( MinorFunction==IRP_MN_REMOVE_DEVICE)
       {
       //取消设备接口:
       IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);
       RtlFreeUnicodeString(&dx->ifSymLinkName);
       
       //调用IoDetachDevice()把fdo从设备栈中脱开:
       if (dx->NextStackDevice)
       IoDetachDevice(dx->NextStackDevice);
       //删除fdo:
       IoDeleteDevice(fdo);
       }
       //返回值:
       return status;
      }
      /***************************************************************
      程序名称:Hello World for WDM
      文件名称:HelloWDM.h
      作者:罗聪
      日期:2002-8-16
      ***************************************************************/
      //头文件,只是声明一些函数和变量,比较简单就不多说了,请读者自行研究:
      #ifdef __cplusplus
      extern "C"
      {
      #endif
      #include "ntddk.h"
      #ifdef __cplusplus
      }
      #endif
      typedef struct _DEVICE_EXTENSION
      {
       PDEVICE_OBJECT fdo;
       PDEVICE_OBJECT NextStackDevice;
       UNICODE_STRING ifSymLinkName;
      } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
      NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
       IN PDEVICE_OBJECT PhysicalDeviceObject);
      NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
       IN PIRP Irp);

     

    编写sources文件:

     TARGETNAME=HelloWDM

    TARGETPATH=OBJ

    TARGETTYPE=DRIVER

    SOURCES=HelloWDM.c

    编写makefile文件:

    !INCLUDE $(NTMAKEENV)/makefile.def

     

     

    然后在某个盘符下面建立一个目录,把这几个文件放到该目录下面,注意!整个目录文件的路径不能有空格,否则编译出错!我就在这上面栽了跟斗,刚开始在桌面下面建立文件夹,后来老是报错,原来桌面路径中有一个Documents and Settings中间有空格,所以报错。

    然后在wdk里面选择Windows Driver Kits->WDK 7600.16385.1->Build Environments;然后选择要加载驱动的目标机编译环境,单击进入cmd命令环境,输入build -ceZ进行编译。在这中间我遇到了两个错误,第一个是由于我单词拼写错误,第二个错误比较重要,主要原因是在编写驱动程序时,一定要把变量定义放在函数的刚开始,然后后面是语句,否则会编译出错。

     

    最后就是调试了。在这里我们一般用虚拟机调试。建立调试环境的过程如下:

        1、在主机上的windug的配置:

             是在G盘,建立一个mylocalsymbols文件夹,用来存放系统的符号文件;然后再在建立一个mysyssymbols文件夹,用来存放自己的调试符号文件。

       2、主机环境变量的设置:

             如果你想Windbg启动时自动识别符号路径的设置的话,我们就来建立一个环境变量

    在"我的电脑"上右击,"属性"->"高级"->"环境变量",然后我们给当前用记新建一个名为_NT_SYMBOL_PATH的环境变量;值为:

      G:/work/mylocalsymbols;G:/work/mysyssymbols;SRV*D:/MyLocalSymbols*http://msdl.microsoft.com/download/symbols

     

    这样设置以后,系统的调试符号就在G:/work/mylocalsymbols下,而我们自己驱动的调试符号就在G:/work/mysyssymbols下

    当符号文件不匹配时,Windbg会自动连网从http://msdl.microsoft.com/download/symbols下载符号文件到G:/work/mylocalsymbols,下次再用到时就不用下载了

    设置好保存就可以了,修改环境变量需要重新启动系统才能生效。

     

     

    然后是vmware的设置:

         1.编辑Vmware设置,添加一个串口设备.

          首先我们要添加一个串口设备.打开你的虚拟机,选择“编辑虚拟机设置”;然后"Add...(添加)"->"Serial Port(串口)"->"Output to named pipe(输出到命名管道)"一路Next,名称就用默认的"//./pipe/com_1",这个其实对应于你的串口名称com1,用别的名字当然也可以,不过后面要对应

    第二行选择"This end is the server"

    第三行选择"The other end is an application"

    同时把下面的"Connect at power on(打开电源时连接)";

     

        2.设置串口波特率

    启动虚拟机中的OS,进入系统,打开设备管理器选项,这时就看到刚才添加的串口com1了,双击com1设备进入属性设置,在"端口设置"选项卡中选择"每秒位数"也就是波特率为115200

     

    3.修改boot.ini(如果是Vista以上就不是修改boot.ini了,需要别的修改方式)

    这一步准确讲是添加DEBUG启动模式,对于Win2003及以前的系统,可以通过修改boot.ini来实现,对于Vista,Server2008,Win7等就得用bcdedit来编辑启动设置了,有需要的可以搜索"Windbg内核调试之一 Vista Boot Config设置"来查找那篇文章

    我的GuestOS是WinXP Sp3,所以采用boot.ini的方法,打开C盘,把隐藏文件显示出来,去掉boot.ini的只读属性,双击打开来

    通常你只会看到一个启动选项,我们复制一个,在后面加上 /debug /debugport=com1 /baudrate=115200

    注意修改时间timeout,不要为0或太短,否则你连选择的机会都没有。

    然后你可以往虚拟机中放一些测试驱动常用的工具,比如DebugView,InstDrv等等。

     

    4.建立双机调试快捷方式

    在桌面建立一个Windbg的快捷方式命名为“双机调试”,然后编辑其属性,把“目标”后面加上

    -k com:port=//./pipe/com_1,baud=115200,pipe

    比如我的电脑上,完整的应该是这个样子:

    "C:/Program Files/Debugging Tools for Windows (x86)/windbg.exe" -k com:port=//./pipe/com_1,baud=115200,pipe

     

    重新启动虚拟机中的系统,在启动菜单时选择有“启用调试程序”的那一项,回车确认,然后把虚拟机最小化,回到桌面双击刚才建好的“双机调试”快捷方式,如果没有什么意外的话,稍等一下就会看到:

    Connected to Windows XP 2600 x86 compatible target,ptr64 FALSE

    Kernel Debugger connection established.

    这就表示已经连接成功了,接下来会显示一下符号路径,内核基址等信息。

    此时按下Ctrl+Break,就会中断下来,命令输入窗口变为可用状态,可以输入各种命令了~~

    如果需要进入系统之后才加载驱动的话,可以等系统启动完毕后再中断,输入断点命令.这取决于你驱动的加载时机。

     

    注意!此时虚拟机启动时会出现“假死”的现象,这是因为主机连接上之后,windug中断的原因(ctrl+break),此时你只需要在主机的windug的命令窗口中输入“g”然后回车,虚拟机就能够接着启动并进入系统。
    然后可以进入调试了:
    下面是windbg的一些基本命令:

    1.基本调试控制
    运行程序(Run): 快捷键:F5 命令:g
    单步步入(Step In): 快捷键:F8 命令:p
    单步步过(Step Over): 快捷键:F10 
    运行到光标所在行: 快捷键:F7
    执行到返回:gu
    执行到指定地址:g [Address]
    重新运行调试程序: 快捷键:Ctrl+Shift+F5(这个对驱动一般用不到)

    2.断点
    断点之于调试当然是非常重要的
    常用命令:
    bp [Address]or[Symbol] 在指定地址下断
    可以使用地址或符号,如
        bp 80561259(Windbg默认使用16进制)
        bp MyDriver!GetKernelPath
        bp MyDriver!GetKernelPath+0x12
    bp [Address] /p eprocess 仅当当前进程为eprocess时才中断
    这个很常用,比如你bp nt!NtTerminateProcess,但是只想在某一进程触发此断点时才断下来,那就加上这个参数吧,因为内核中的代码是各个进程共用的,所以此命令很实用
    bp [Address] /t ethread 仅当当前线程为ethread时才中断,用法跟/p参数类似
    bu [Address]or[Symbol] 下一个未解析的断点(就是说这个断点需要延迟解析)
    这个也很常用,比如我们的驱动名为MyDriver.sys,那么在驱动加载之前下断bu MyDriver!DriverEntry,
    然后加载这个驱动时就可以断在驱动入口,并且这个是不需要调试符号支持的
    bl 列出所有断点,L=List
    bc[id] 清除断点,c=Clear,id是bl查看时的断点编号
    bd[id] 禁用断点,d=Disable,id即断点编号
    be[id] 启用断点,e=Enable,id为断点编号

    3.查看和修改数据
    调试中不可避免的要查看和修改数据
    查看内存:
    db/dw/dd/dq [Address]       字节/字/双字/四字方式查看数据
    da/du [Address]           ASCII字符串/Unicode字符串方式查看指定地址
    其它常用的如查看结构
    dt nt!_EPROCESS
    dt nt!_EPROCESS 89330da0 (把0x89330da0作为对象指针)
    修改内存:
    eb/ew/ed/eq/ef/ep Address [Values] 
    字节/字/双字/四字/浮点数/指针/
    ea/eu/eza/ezu Address [Values] 
    ASCII字符串/Unicode字符串/以NULL结尾的ASCII字符串/以NULL结尾的Unicode字符串
    搜索内存:
    s -[b/w/d/q/a/u] Range Target
    搜索字节/字/双字/四字/ASCII字符串/Unicode字符串

    4.寄存器
    在用Windbg调试时可以Alt+4直接调出寄存器窗口,然后拖放到合适的位置就可以。
    要修改呢就直接双击相应的项就可以了。
    把命令的方式也说一下,比较简单:
    r 显示所有寄存器的值
    r eax 显示eax的值
    r eax=1 修改eax的值为1

    5.辅助命令
    !process 显示当前进程信息
    !process 0 0 显示当前所有进程(会有僵尸进程)
    !process 1f4 显示pid为1f4的进程信息,后面也可以跟eprocess的值
    !thread 显示当前线程信息
    !thread 
    !process 1f4 显示tid为768的线程信息,后面也可以跟ethread的值
    栈相关:
    k 显示调用栈
    kb 显示ebp和前3个参数
    kp 以函数调用形式显示栈

     

    然后就可以加载驱动,并进行调试了。

     

     

     

     

     

    ok,下面再把整个调试过程做个简单的总结:

    第一步、设置主机windbg的参数。

    第二步、添加vm的串口,并修改串口参数;然后修改vm目标机的boot.ini文件,增加通过串口调试启动选择。

    第三步、修改主机windbug快捷方式的启动参数,把编译好的符号文件.pdb放入到自己先前设置的符号文件路径中。

    第四步、重新启动虚拟机,以调试方式进入系统。

    第五步、启动主机的windbg。

    第六部、windbg连接成功后,输入“g”命令,让目标机继续运行,并进入系统。

    第七步、在windug中按下“ctrl+pausebreak”键,把要调试的驱动主函数DriverEntry设为断点。

    第八部、输入g命令继续运行。

    第九步、在目标机中用instdrv.exe加载驱动,并运行。

    第十步、在windug中输入F10、或者F11进行调试。

     

     

     

    另外有一个单独加载驱动程序的程序,大家可以参考一下(我也是从网上找到的)

    驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什么区别。

    安装驱动程序流程:
    1,调用OpenSCManager()打开服务控制管理器
    2,调用CreateService()创建一个服务,服务类型为内核驱动
    3,调用OpenService()取得服务句柄
    启动服务
    4,调用StartService()启动服务
    停止服务
    4,调用ControlService()停止服务
    删除服务
    4,调用DeleteService()删除服务
    5,调用CloseServiceHandle()关闭服务句柄

    操作驱动程序流程: 
    1,调用CreateFile()取得设备句柄
    2,调用DeviceIoControl()传递I/O控制代码
    3,调用CloseHandle()关闭设备句柄

    http://www.xfocus.net/tools/200411/882.html
    这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码

    完整代码:

    =================================================================================================================================

    参考资料

    《Windows 2000 DDK》

    《Windows 2000 驱动程序设计》


    附录代码:

    #ifndef __HELLOWORLD_C__ #define __HELLOWORLD_C__ #define DEBUGMSG #include <ntddk.h> #define DEVICE_HELLO_INDEX 0x860 //2个IOCTL宏 #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS) #define STOP_HELLPWORLD  CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS) #define NT_DEVICE_NAME L"//Device//HelloWorld"        //设备名称 #define DOS_DEVICE_NAME L"//DosDevices//HelloWorld"   //符号连接 NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp); VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject); //驱动入口 NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath) {     NTSTATUS ntStatus=STATUS_SUCCESS;     PDEVICE_OBJECT lpDeviceObject=NULL;       //指向设备对象的指针     UNICODE_STRING DeviceNameString={0};      //设备名称     UNICODE_STRING DeviceLinkString={0};      //符号连接     //调试信息     #ifdef DEBUGMSG            DbgPrint("Starting DriverEntry()/n");     #endif     RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME);  //初始化Unicode字符串     //创建设备     ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,0,FALSE,&lpDeviceObject);     //使用NT_SUCCESS宏检测函数调用是否成功     if (!NT_SUCCESS(ntStatus))     {         #ifdef DEBUGMSG                DbgPrint("IoCreateDevice() error reports 0x%08X/n",ntStatus);         #endif         return ntStatus;     }     RtlInitUnicodeString(&DeviceLinkString,

     

    连接:http://www.cnblogs.com/piccolo/articles/237507.html

     

     

     

    展开全文
  • Windows事件驱动机制及消息循环

    千次阅读 2010-12-14 18:55:00
    Windows事件驱动机制及消息循环
  • Boost库学习(3) linux 编程(17) Linux 驱动开发(7) windows socket编程(20) windows 串口编程(2) ...Ndis 网络驱动编程(28) 文学杂谈(3) 算法优化(6) 视频 图像处理(32) OpenCv(4) c c++(42
  • windows驱动编程学习路线

    千次阅读 2015-01-30 15:58:16
    大多学的驱动开发资料都以英文为主,这样让很多驱动初学者很头疼.本人从 事驱动开发时间不长也不短,大概也就3~4年时间.大多数人都认为会驱动开发的都是牛人, 高手之类的.其实高手,牛人不是这样定义的.我们在学习...
  • <br />面向过程的MS-DOS编程:   传统的DOS程序都有而且只有一个main函数,可以在main函数中调用其它的函数,完成...     面向用户的事件驱动编程:   当Windows操作系
  • 事件驱动编程的分类和3种具体实现

    千次阅读 2019-04-11 22:39:21
    1、观察者模式:java自带观察者模式类,可直接实现,一个事件源发生事件 触发对 应观察者 2、订阅发布模式:订阅端 事件通道 发布端 解耦度高,优选 二、三种实现 1、spring框架实现 自带了事件监听的相关类,是对...
  • windows事件驱动机制和消息循环

    千次阅读 2013-11-14 13:37:50
    而且大多在刚开始学习编程时也是先从DOS下的编程环境入手的,因此在习惯了DOS下的过程驱动形式的顺序程序设计方法后,往往在向Windows下的开发环境转型的过程中会对Windows所采取的事件驱动方式感到无法适应。...
  • Chap 7 Notes-------------------------- 一、 概述1、 ASP.NET允许使用事件驱动编程模型,这就像Windows本身一样,除非Windows响应了某个事件(如:单击图标、按“开始”菜单等),否则不会发生其他事件2、 ...
  • 一般在驱动编程中,使用ZwOpenKey的情况比较多见。下面以此为例讲解。ZwOpenKey的原型如下: C++代码 NTSTATUS ZwOpenKey( OUT  PHANDLE KeyHandle, IN ACCESS_MASK DesiredAccess, IN ...
  • 事件驱动引擎会取代多线程编程

    千次阅读 2018-08-07 13:39:29
    事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)...
  • 楚狂人Windows驱动编程基础教程

    千次阅读 2012-09-17 13:40:13
    实际上,读者现在见到的免费版本的《Windows驱动编程基础教程》是从这本书的第一部分中节选出来的。这本书篇幅比较大,大约有600-800页。主要内容如下:  第一章驱动编程基础   第二章磁盘设备驱动 ...
  • 但当仅仅学习完这些简单的基础之后,关于队列真正在实际的应用,还是很抽象,生疏,所以特地研究了几个应用队列的经典案例,对于初学者来说,事件驱动编程的设计和思想,一时还是难以完全接受的,下边是我学习过程中...
  • 采用基于事件驱动模型进行程序设计,其实现步骤包括以下5个步骤 (1)定义事件相关信息类。 (2)在事件发布者类(事件源)中声明事件,并声明一个负责触发事件的方法。 (3)在事件接收者类中声明事件产生时调用...
  • 驱动开发专家解读 寒江独钓 Windows内核安全编程
  • 驱动开发专家解读《寒江独钓——Windows内核安全编程》本文由网友 李丁盼 - 网名:doskey 提供 对专业技术人员来说,拿到一本书最关心的是书的实用性。市面上的技术书籍多如牛毛,《寒江独钓——Windows内核安全...
  • 我们在进行Windows编程的时候,经常需要进行遍历驱动器并获取驱动器属性,我们来实现遍历驱动器并获取驱动器属性。请见代码实现与注释讲解 #define _WIN32_WINNT 0x0501 /* 头文件 */ #include #include /* 预定...
  • Windows显示驱动(WDDM)编程初步(1)

    千次阅读 2013-07-26 10:41:12
    Windows显示驱动从Vista开始,使用新的WDDM编程框架,称为Windows Display Driver Model。也有一种最初的名称是LDDM,L代表Longhorn,但后来微软在所有产品线上都不再使用Longhorn代号,故而改成现在的名称。虽然在...
  • Windows系统上与安全软件相关的驱动开发过程中,“过滤(filter)”是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件和下层的真实驱动,就...
  • c# Windows窗体与事件驱动

    千次阅读 2017-05-23 04:16:22
    创建Windows应用程序基本步骤 打开Visual Studio软件 新建项目 项目类型:Visual C#项目 模板:Windows 应用程序 熟悉开发环境窗体的常用属性窗体的属性非常多, 以下是常用的几个。 Name: 窗体对象的名字,类似于...
  • Windows驱动开发模型

    千次阅读 2009-06-08 08:39:00
    Windows驱动开发模型本文节选自《寒江独钓:Windows内核安全编程》一书 在Windows的不同版本上开发的驱动程序“模型”(模型这个词语应该来源于单词“Mode”。在Windows NT上,驱动程序被称为Kernel Driver Mode...
  • Windows串口API编程

    千次阅读 2014-10-28 13:30:47
    Windows环境下的编程的最大特征之一就是设备无关性,它通过设备驱动程序将Windows应用程序同不同的外部设备隔离。Windows封装了Windows的通信机制,这种方式称为通信API,Windows程序可以利用Windows通信API进行编程...
  • 通过使用一种内核模式的设备驱动器和其它几种底层编程技巧,它绕过了Windows系统的保护机制。 WinNT/2000/XP下,WinIO函数库只允许被具有管理者权限的应用程序调用。如果使用者不是以管理者的身份进入的,则WinIO....
  • 在Virtualbox的win xp系统上进行驱动调试 (1)打开virtual box,安装好win xp系统,然后如下图,设备虚拟机xp系统的com端口: (2)然后打开虚拟机的系统,找到boot.ini文件并配置: ...
  • Windows的API(Application Programming Interface,应用程序编程接口),是Windows 操作系统提供给应用程序的大量函数,学习本课程,不仅可以掌握Windows界面编程,开发华丽的Windows桌面运用程序,也可以为MFC框架...
  • 事件驱动

    万次阅读 2011-06-04 17:45:00
    3.4事件驱动——有事我叫你,没事别烦我 劳心者治人,劳力者治于人 ——《孟子·滕文公上》 关键词:编程范式,事件驱动式,回调函数,framework,IoC,DIP,观察者模

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 120,963
精华内容 48,385
关键字:

windows的事件驱动编程