当前位置:文档之家› Boost源码剖析之:多重回调机制signal(下)

Boost源码剖析之:多重回调机制signal(下)

Boost源码剖析之:多重回调机制signal(下)
Boost源码剖析之:多重回调机制signal(下)

boost源码剖析之:多重回调机制signal(下)

刘未鹏

C++的罗浮宫(https://www.doczj.com/doc/1913478866.html,/pongba)

在本文的上篇中,我们大刀阔斧的剖析了signal的架构。不过还有很多精微之处没有提到,特别是一个遗留问题还没有解决:如果用户注册的是函数对象(仿函数),signal又当如何处理呢?

下篇:高级篇

概述

在本文的上篇中,我们已经分析了signal的总体架构。至于本篇,我们则主要集中于将函数对象(即仿函数)连接到signal的来龙去脉。signal库的作者在这个方面下了很多功夫,甚至可以说,并不比构建整个signal架构的功夫下得少。

之所以为架构,其中必然隐藏着一些或重要或精妙的思想。

学过STL的人都知道,函数对象1(function object)是STL中的重要概念和基石之一。它使得一个对象可以像函数一样被“调用”,而调用形式又是与函数一致的。这种一致性在泛型编程中乃是非常重要的,它意味着“泛化”,而这正是泛型世界所有一切的基础。而函数对象又由于其携带的信息较之普通函数大为丰富,从而具有更为强大的能力。

所以signal简直是“不得不”支持函数对象。然而函数对象又和普通函数不同:函数对象会析构。问题在于:如果某个函数对象连接到signal,那么,该函数对象析构时,连接是否应该断开呢?这个问题,signal的设计者留给用户来选择:如果用户觉得函数对象一旦析构,相应的连接也应该自动断开,则可以将其函数对象派生自boost::signals::trackable类,意即该对象是“可跟踪”的。反之则不用作此派生。这种跟踪对象析构的能力是很有用的,在某些情况下,用户需要这种语义:例如,一个负责数据库访问及更新的函数对象,而该对象的生命期受某个管理器的管理,现在,将它连接到某个代表用户界面变化的signal,那么,当该对象的生命期结束时,对应的连接显然应该断开——因为该对象的析构意味着对应的数据库不再需要更新了。

signal库支持跟踪函数对象析构的方式很简单,只要将被跟踪的函数对象派生自boost::signals::trackable类即可,不需要任何额外的步骤。解剖这个trackable类所隐藏的秘密正是本文的重点。

1函数对象即重载了operator()操作符的对象,故而可以以与函数调用一致的语法形式来“调用”。又称为functor,中文译为“仿函数”。

架构

很显然,trackable类是整个问题的关键。将函数对象派生自该类,就好比为函数对象安上了一个“跟踪器”。根据C++语言的规则,当某个对象析构时,先析构派生层次最高(most derived)的对象,再逐层往下析构其子对象。这就意味着,函数对象的析构最终将会导致其基类trackable子对象的析构,从而在后者的析构函数中,得到断开连接的机会。那么,哪些连接该断开呢?换句话说,该断开与哪些signal的连接呢?当然是该函数对象连接到的signals。而这些连接则全部保存在一个list里面。下面就是trackable的代码:

connected_signals是个list,其中保存的是该函数对象所连接到的signals。只不过是以connection的形式来表示的。这些connection都是“控制性”2的,一旦析构则自动断开连接。所以,trackable析构时根本不需要任何额外的动作,只要让该list自行析构就行了。

了解了这一点,就可以画出可跟踪的函数对象的基本结构,如图四:

图四

现在的问题是,每当该函数对象连接到一个signal,都会将相应connection的

2“控制性”是指该connection析构时会顺便将该连接断开。反之则不然。关于“控制性”和“非控制性”的connection的详细讨论见本文的上篇。

一个副本插入到其trackable子对象的connected_signals成员(一个list)中去。然而,这个插入究竟发生在何时何地呢?

在本文的上篇中曾经分析过连接的过程。对于函数对象,这个过程仍然是一样。不过,当时略过了一些细节,这些细节正是与函数对象相关的。现在一一道来:

如你所知,在将函数(对象)连接到signal时,函数(对象)会先被封装成一个slot 对象,slot类的构造函数如下:

bound_objects是slot类的成员,其类型为vector。可想而知,经过第二行代码“visit_each(...)”的调用,该vector中保存的将是指向f中的各个trackable子对象的指针。

“等等!”你敏锐的发现了一个问题:“前面不是说过,如果用户要让他的函数对象成为可跟踪的,则将该函数对象派生自trackable对象吗?那么,也就是说,如果f是个“可跟踪”的函数对象,那么其中的trackable子对象当然只有一个(基类对象)!但为什么这里bound_objects的类型却是一个vector呢?单单一个trackable*不就够了么?”

在分析这个问题之前,我们先来看一段例子代码:

3get_inspectable_slot()当且仅当f是个reference_wrapper时,返回f.get()——即其中封装的真实的函数(对象)。其它时候,该函数调用等同于f。关于reference_wrapper的详细介绍见boost 的官方文档。

boost::bind()将&S1::test4的“this”参数绑定为s1,从而生成一个“void()”型的仿函数,每次调用该仿函数就相当于调用s1.test(),然而,这个仿函数本身并非可跟踪的,不过,很显然,这里的s1对象一旦析构,则该仿函数就失去了意义,从而应该让连接断开。所以,我们应该使S1类成为可跟踪的(见struct S1的代码)。

然而,这又能说明什么呢?仍然只有一个trackable子对象!但是,答案已经很明显了:既然boost::bind可以绑定一个参数,难道不能绑定两个参数?对于一个延迟调用的函数对象5,一旦其某个按引用语义传递的参数析构了,该函数对象也就相应失效了。所以,对于这种函数对象,其按引用传递的参数都应该是可跟踪的。在上例中,s1就是一个按引用传递的参数6,所以是可跟踪的。所以,如果有多个这种参数绑定到一个仿函数,就会有多个trackable对象,其中任意一个对象的析构都会导致仿函数失效以及连接的断开。

例如,假设C1,C2类都是trackable的。并且函数test的类型为void(C1,C2)。那么boost::bind(&test,boost::ref(c1),boost::ref(c2))就会返回一个

void()型的函数对象,其中c1,c2作为test的参数绑定到了该函数对象。这时候,如果c1或c2析构,这个函数对象也就失效了。如果先前该函数对象曾连接到某个signal型的signal,则连接应该断开。

问题在于,如何获得绑定到某个函数对象的所有trackale子对象呢?

关键在于visit_each函数——我们回到slot的构造函数(见上文列出的源代码),其第二行代码调用了visit_each函数,该函数负责访问f中的各个trackable子对象,并将它们的地址保存在bound_objects这个vector中。

至于visit_each是如何访问f中的各个trackable子对象的,这并非本文的重点,我建议你自行参考源代码。

slot类的构造函数最后调用了create_connection函数,这个函数创建一个连接对象,表示函数对象和该slot的连接。“咦?为什么和slot连接,函数对象不是和signal连接的吗?”没错。但这个看似蛇足的举动其实是为了实现“delayed connect”,例如:

4&S1::test为指向成员函数的指针。其调用形式为(this_ptr->*mem_fun_ptr)()或

(this_ref.*mem_fun_ptr)(),而从一般语义上说,其调用形式为mem_fun_ptr(this_ref)或mem_fun_ptr(this_ptr)。所以,boost::bind可以将其“第一个”参数绑定为s1对象。

5command模式,其中封装的command对象就是一个延迟调用的函数对象,它暂时保存某函数及其调用的各个参数,并在恰当的时候调用该函数。

6boost::ref(s1)生成一个boost::reference_wrapper(s1)对象,其语义与“裸引用”几乎一样,只不过具有拷贝构造,以及赋值语义,这有点像java里面的对象引用。具体介绍见boost的官方文档。

这里,如果在slot连接到sig之前,f“不幸”析构了,则连接不会生效,只是返回一个空连接。

为了达到这个目的,slot类的构造函数使用create_connection构造一个连接,这个连接其实没有实际意义,只是用于“监视”函数对象是否析构。如果函数对象析构了,则该连接会变为“断开”态。下面是create_connection的源代码:

这段代码先new了一个连接,并将其三个成员设置妥当。由于该连接纯粹仅作“监视”该函数对象是否析构之用,并非真的“连接”到slot,所以signal_data成员只需闲置为0,而signal_disconnect所指的函数&bound_object_destructed 也只不过是个什么事也不做的空函数。关键是最后一行代码:

watch_bound_objects乃是slot类的成员,类型是connection,这行代码使其指向上面新建的con连接对象。注意,在后面省略掉的部分代码中,该连接的副本也被保存到待连接的函数对象的各个trackable子对象中(前面已经提到(参见图四),这系保存在一个list中),这才真正使得“监视”成为可能!因为这样做了之后,一旦代连接的函数对象析构了,将会导致con连接为“断开”状态。从而在sig.connect(slot)时可以通过查询slot中的watch_bound_objects副

本的连接状态得知该slot是否有效,如果无效,则返回一个空的连接。这里,connection巧妙的充当了一个“监视器”的作用。

说到这里,你应该也就明白了为什么basic_connection的signal和

signal_data成员的类型为void*而不是signal_base_impl*和

slot_iterator*了——是的,因为函数对象不但连接到signal,还“连接”到slot。将这两个成员类型设置为void*可以复用该类以使其充当“监视器”的角色。signal库的作者真可谓惜墨如金。

回到正题,我们接着考察如何将封装了函数对象的slot连接到signal。这里,我建议你先回顾本文的上篇,因为这与将普通函数连接到signal有很大一部分相同之处,只不过多做了一些额外的工作。

同样,可想而知的是,这个连接过程仍然是先将slot插入到signal中的slot管理器中去,并将signal的地址,插入后指向该slot的迭代器的地址,以及负责断开连接的函数地址分别保存到表示本次连接的basic_connection对象的三个成员7中去。这时,故事几乎已经结束了一半——用户已经可以通过该对象来控制相应连接了。但是,注意,只是“用户”!对于函数对象来说,不但用户能够控制连接,函数对象也必须能够“控制”连接,因为它析构时必须能够断开连接,所以,我们还需要将该连接对象的副本保存到函数对象的各个trackable子对象中去:

7signal成员指向连接到的signal,signal_data成员指向该函数在signal中保存的位置(一般为迭代器),而signal_disconnect则是个函数指针,负责断开连接,将前两个成员作为参数传给它就

可以断开连接。

在上面的代码中,for循环遍历绑定到该函数对象的各个trackable子对象,并将该连接的副本slot_connection保存到其中。这样,当某个trackable子对象析构时,就会通过保存在其中的副本来断开该连接,从而达到“跟踪”的目的。

但是,这里还有个问题:这里实际的连接只有一个,但却产生了多个副本,分别操纵在各个trackable子对象手中,如果用户愿意,用户还可以操纵一个或多个副本。但是,一旦该连接断开——不管是由于某个trackable子对象的析构还是用户手动断开——则保存在各个trackable子对象中的该连接的副本都应该被删除掉。不然既占空间又没有任何意义,还会导致这样的情况:只要其中有一个trackable对象还没有析构,表示该连接的basic_connection对象就不会被delete掉。特别是当连接由用户断开时,每个未析构的trackable对象中都会仍留有一个该连接对象的副本,直到trackable对象析构时该副本才会被删除。这就意味着,如果存在一个“长命百岁”的trackable函数对象,并在其生命期中频繁被用户连接到signal并频繁断开连接,那么,每次连接都会遗留一个连接副本在其trackable基类子对象中,这是个巨大的累赘。

那么,这个问题到底如何解决呢?basic_connection仍然是问题的核心,既然用户只能通过connection对象来控制连接,而connection对象实际上完全通过basic_connection来操纵连接,那么如何解决这个问题的责任当然落在basic_connection身上——既然它知道哪个函数(对象)连接到哪个signal并在其slot管理器中的位置,那么,为什么不能让它也知道“该连接在各个trackable对象中的副本所在何处”呢?

当然可以。答案就在于basic_connection的第四个成员bound_objects,其定义如下:

std::list bound_objects;

该成员正是用来记录“该连接在各个trackable对象中的副本所在何处”的。它的类型是std::list,其中每一个bound_object型的对象都代表“某一个连接副本所在之处”。有了它,在断开连接时,就可以依次删除各个trackable对象中的副本。

那么,这个bound_objects又是何时被填充的呢?当然是在连接时,因为只有在连接时才知道有几个trackable对象,并有机会将副本保存到它们内部。我们回顾上文的connect_slot函数的代码,其中有加底纹的部分刚才没有分析,这正是与此相关的。为了清晰起见,我们将分析以源代码注释的形式写出来:

要想完全搞清楚以上几行代码,我们还得来看看bound_object类的结构以及trackable::signal_connected到底干了些什么?先来看看bound_object的结构:

发现什么特别的没有?是的,它的结构简直就是basic_connection的翻版,只不过成员的名字不同了而已。basic_connection因为是控制连接的枢纽,所以其三个成员表现的是被连接的slot在signal中的位置。而bound_object表现的是connection副本在trackable对象中的位置。在介绍bound_object的三个成员之前,我们先来考察trackable::signal_connected函数,因为这个函数同时也揭示了这三个成员的含义:

分析完了这段代码,bound_object类的三个成员的含义不言自明。注意,其最后一个成员是个函数指针,指向trackable::signal_disconnected函数,这个函数负责将一个connection副本从某个trackable对象中删除,其参数有二,正是bound_object的前两个成员obj和data,它们合起来指明了一个connection 副本的位置。

当这些副本在各个trackable子对象中都安置妥当后,连接就算完成了。我们再来看看连接具体是如何断开的,对于函数对象,断开它与某个signal的连接的过程大致如下:首先,与普通函数一样,将函数对象从signal的slot管理器中erase 掉,这个连接就算断开了。其次就是只与函数对象相关的动作了:将保存在绑定到函数对象的各个trackable子对象中的connection副本清除掉。这就算完成了断开signal与函数对象的连接的过程。当然,得看到代码心里才踏实,下面就是:

前面已经说过,bound_object的第三个成员disconnect指向的函数为trackable::signal_disconnected,顾名思义,“signal”已经“disconnected”了,该是清除那些多余的connection副本的时候了,所以,上面的最后一行代码“i->disconnect(...)”就是调用该函数来做最后的清理工作的:

这就是故事的全部。这个清理工作一完成,函数对象与signal就再无瓜葛,从此分道扬镳。回过头来再看看signal库对函数对象所做的工作,可以发现,其主要围绕着trackable类的成员connected_signals和basic_connection的成员bound_objects而展开。这两个一个负责保存connection的副本以作跟踪之用,另一个则负责在断开连接时清除connection的各个副本。

分析还属其次,重要的是我们能够从中汲取到一些纳为己用的东西。关于trackable思想,不但可以用在signal中,在其它需要跟踪对象析构语义的场合也大可用上。这种架构之最妙之处就在于用户只要作一个简单的派生,就获得了完整的对象跟踪能力,一切的一切都在背后严密的完成。

蛇足&再谈调用

还记得在本文的上篇分析的“调用”部分吗?库的作者藉由一个所谓的

“slot_call_iterator”来完成遍历slot管理器和调用slot的双重任务。

slot_call_iterator和slot管理器本身的iterator语义几乎相同,只不过对前者解引用(dereference,即“*iter”)的背后其实调用了其指向的slot函数,并且返回的是slot函数的返回值。这种特殊的语义使得signal可以将slot_call_iterator 直接交给用户制定的返回策略(如max_value<>,min_value<>等),一石二鸟。但是这里面有一个难以察觉的漏洞:一个设计得不好的算法可能会使迭代

器在相同的位置上出现冗余的解引用,例如,一个设计的不好的max_value<>可能会像这样:

这个算法本身的逻辑并没有什么不妥,只不过注意到其中*first出现了两次,这意味着什么?如果按照以前的说法,每一次解引用都意味着一次函数调用的话,那么同一个函数将被调用两次。这可就不合逻辑了。signal必须保证每个注册的函数有且仅有一次执行的机会。

解决这个问题的任务落在库的设计者身上,无论如何,一个普通用户写出上面的算法的确是件无可非议的事。一个明显的解决方案是将函数的返回值缓存起来,第二次或第N次在同一位置解引用时只是从缓存中取值并返回。signal库的设计者正是采用的这种方法,只不过,slot_call_iterator将缓存的返回值交给一个shared_ptr来掌管。这是因为,用户可能会拷贝迭代器,以暂时保存区间中的某个位置信息,在拷贝迭代器时,如果缓存中已经有返回值,即函数已经调用过了,则新的迭代器也因该引用那个缓存。并且,当最后一个引用该缓存的迭代器消失时,就是该缓存被释放之时,这正是shared_ptr用武之地。具体的实现代码请你自行参考boost/signals/detail/slot_call_iterator.hpp。

值得注意的是,slot_call_iterator符合“single pass”(单向遍历)concept。对于这种类型的迭代器只能进行两种操作:递增和比较。这就防止了用户写出不规矩的返回策略——例如,二分查找(它要求一个随机迭代器)。如果用户硬要犯规,就会得到一个编译错误。

由此可见,设计一个完备的库不但需要技术,还要无比的细心。

结语

相对于C++精致的泛型技术的应用来说,其背后隐藏的思想更为重要。在signal 库中,泛型技术的应用其实也不可不谓淋漓尽致,但是语言只是工具,重要的是解决问题的思想。从这篇文章可以看出,作者为了构建一个功能完备,健壮,某些特性可定制的signal架构付出了多少努力。虽然某些地方看似简单,如connection对象,但是都是经过反复揣摩,时间检验后作出的设计抉择。而对于函数对象,更是以一个trackable基类就实现了完备的跟踪能力。以一个函数对象来定制返回策略则是符合policy-based设计的精髓。另外还有一些细致入微的设计细节,本篇并没有一一分析,一是为了让文章更紧凑,二是篇幅——只讲主要脉络文章尚已如此,再加上各个细节则更是“了得”了,干脆留给你自行理解,你将boost的源代码和本文列出的相应部分比较后或会发现一些不同之处,那些就是我故意省略掉的细节所在了。对于细节有兴趣的不妨自己分析分析。

回调函数

对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。 什么是回调函数? 简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。 为什么要使用回调函数? 因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。 如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。 回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。 另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。 不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。 一个简单的回调函数实现 下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。

【智能路由器】ndpi深度报文分析源码框架

【智能路由器】ndpi深度报文分析源码 框架 某些需求可能会要求路由能精确的分析出流经路由的流量是属于什么类型,比如qq,facebook,支付宝、京东…… 正好,有这么一个基于opendpi框架的深度报文分析的工具——ndpi ndpi是在opendpi的基础上编写而来的协议分析工具。源代码编译后生成两个部分,一个是内核层的xt_ndpi.ko模块,用来实时分析流量,另一个是应用层的lib库,给ndpiReader这个工具提供库,用来分析抓包工具提供的文件或者底层网卡提供的数据包。 开发者必须为其想要分析的app的流量对应开发一个协议分析器,ndpi已经提供了不少现成的协议分析器,如http,QQ,twitter,vmware,yahoo,mysql,pplive等等。 本篇博客中作者arvik只叙述ndpi源码中形成内核层的xt_ndpi.ko模块的源码部分。 之后可能会写一篇介绍ndpi中已有的QQ协议分析器是怎么分析出OICQ协议以识别流量类型和一篇实战型依葫芦画瓢编写微信协议分析器的博客。 ndpi的分析过程: 当底层一帧数据被送入ndpi钩子的时候,流经结构大致如下: 1. 打包该数据帧,搜集l3、l4层报头信息 2. 查询链接跟踪(如果已被标识,则直接获取到该数据帧所属协议类型) 3. 从链接跟踪中未获取流量所属协议类型,则进入深度报文分析过程 4. 率先进行协议猜测,调用相应的协议分析器分析 5. 猜测协议错误、此时ndpi会分类遍历相关类型的协议分析器进行分析,直至分析出结果或遍历完所有相关的协议分析器 6. 将分析出的协议类型标记到链接跟踪中,以便下次可直接从连接跟踪中拿到协议类型 先来看内核模块代码: 几个重要结构 ndpi_detection_module_struct:各种协议分析器都将自己的信息都保存在该结构中 struct ndpi_detection_module_struct { NDPI_PROTOCOL_BITMASK detection_bitmask; //等价于struct xxx { uint32_t fds_bits[8]}; struct xxx dection; 其中fds_bits[8]每一位可代表一种协议,最多可代表256中协议 NDPI_PROTOCOL_BITMASK generic_http_packet_bitmask; u_int32_t current_ts;

回调函数与回调机制

回调函数与回调机制 1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: ?属于工作流的一个部分; ?必须按照工作流指定的调用约定来申明(定义); ?他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 如上图所示,工作流提供了两个对外接口(获取参数、显示结果),以回调函数的形式实现。 ?“获取参数”回调函数,需要工作流使用者设定工作流计算需要的参数。 ?“显示结果”回调函数,提供计算结果给工作流使用者。

再以Windows的枚举顶级窗体为例。函数EnumWindows用于枚举当前系统中的所有顶级窗口,其函数原型为: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ); 其中lpEnumFunc是一个回调函数,他用于返回枚举过程中的获得的窗口的句柄。其定义约定为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ); 在这个例子中,EnumWindows 是一个工作流,这个工作流用于遍历windows的所有窗口并获得其句柄。用户使用EnumWindows工作流的目的是想通过工作流来来获取窗口的句柄以便针对特定的一个或多个窗口进行相关处理。于是EnumWindows就扩展出接口lpEnumFunc,用于返回遍历的窗口句柄。 EnumWindows工作流的结束有两个方式:1,用户在回调函数中返回FALSE;2,再也找不到顶级窗口。我们可以推测EnumWindows的实现机制如下: 注:下列代码中的FindFirstTopWindows(), FindNextTopWindow()为假设的,Windows API 没有此函数,只是为了表明Enumwindows的内部流程。 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ) { BOOL bRet = TRUE; HWND hWnd = ::FindFirstTopWindows(); // 此函数是假设的,查找第一个顶级窗口 // 当hWnd为0时表示再也找不到顶级窗口 while( hWnd ) { bRet = (*lpEnumFunc)( hWnd, value ); if( !bRet) break; // 终止EnumWindows工作流; hWnd = ::FindNextWindow(); // 此函数是假设的,查找下一个顶级窗口 } } 在EnumWindows(...)函数中,实现了窗口枚举的工作流,他通过回调机制把用户关心(顶级窗口句柄)的和枚举工作流分开,用户不需要知道EnumWindows的具体实现,用户只要知道,设定了lpEnumFunc函数,然后把函数指针传给EnumWindwos就可以获得想要的窗口句柄。

Android源代码结构分析

目录 一、源代码结构 (2) 第一层次目录 (2) bionic目录 (3) bootloader目录 (5) build目录 (7) dalvik目录 (9) development目录 (9) external目录 (13) frameworks目录 (19) Hardware (20) Out (22) Kernel (22) packages目录 (22) prebuilt目录 (27) SDK (28) system目录 (28) Vendor (32)

一、源代码结构 第一层次目录 Google提供的Android包含了原始Android的目标机代码,主机编译工具、仿真环境,代码包经过解压缩后,第一级别的目录和文件如下所示: . |-- Makefile (全局的Makefile) |-- bionic (Bionic含义为仿生,这里面是一些基础的库的源代码) |-- bootloader (引导加载器),我们的是bootable, |-- build (build目录中的内容不是目标所用的代码,而是编译和配置所需要的脚本和工具) |-- dalvik (JAVA虚拟机) |-- development (程序开发所需要的模板和工具) |-- external (目标机器使用的一些库) |-- frameworks (应用程序的框架层) |-- hardware (与硬件相关的库) |-- kernel (Linux2.6的源代码) |-- packages (Android的各种应用程序) |-- prebuilt (Android在各种平台下编译的预置脚本) |-- recovery (与目标的恢复功能相关) `-- system (Android的底层的一些库)

Mina2源码分析

Mina2.0框架源码剖析(一) 整个框架最核心的几个包是:org.apache.mina.core.service, org.apache.mina.core.session, org.apache.mina.core.polling以及 org.apache.mina.transport.socket。 这一篇先来看org.apache.mina.core.service。第一个要说的接口是IoService,它是所有IoAcceptor和IoConnector的基接口.对于一个IoService,有哪些信息需要我们关注呢?1)底层的元数据信息TransportMetadata,比如底层的网络服务提供者(NIO,ARP,RXTX等),2)通过这个服务创建一个新会话时,新会话的默认配置IoSessionConfig。3)此服务所管理的所有会话。4)与这个服务相关所产生的事件所对应的监听者(IoServiceListener)。5)处理这个服务所管理的所有连接的处理器(IoHandler)。6)每个会话都有一个过滤器链(IoFilterChain),每个过滤器链通过其对应的IoFilterChainBuilder来负责构建。7)由于此服务管理了一系列会话,因此可以通过广播的方式向所有会话发送消息,返回结果是一个WriteFuture集,后者是一种表示未来预期结果的数据结构。8)服务创建的会话(IoSession)相关的数据通过IoSessionDataStructureFactory来提供。9)发送消息时有一个写缓冲队列。10)服务的闲置状态有三种:读端空闲,写端空闲,双端空闲。11)还提供服务的一些统计信息,比如时间,数据量等。 IoService这个服务是对于服务器端的接受连接和客户端发起连接这两种行为的抽象。 再来从服务器看起,IoAcceptor是IoService 的子接口,它用于绑定到指定的ip和端口,从而接收来自客户端的连接请求,同时会fire相应的客户端连接成功接收/取消/失败等事件给自己的IoHandle去处理。当服务器端的Accpetor从早先绑定的ip和端口上取消绑定时,默认是所有的客户端会话会被关闭,这种情况一般出现在服务器挂掉了,则客户端收到连接关闭的提示。这个接口最重要的两个方法是bind()和unbind(),当这两个方法被调用时,服务端的连接接受线程就启动或关闭了。 再来看一看客户端的连接发起者接口IoConnector,它的功能和IoAcceptor基本对应的,它用于尝试连接到服务器指定的ip和端口,同时会fire相应的客户端连接事件给自己的IoHandle去处理。当connet方法被调用后用于连接服务器端的线程就启动了,而当所有的连接尝试都结束时线程就停止。尝试连接的超时时间可以自行设置。Connect方法返回的结果是ConnectFuture,这和前面说的WriteFuture类似,在后面会有一篇专门讲这个模式的应用。 前面的IoAcceptor和IoConnector就好比是两个负责握手的仆人,而真正代表会话的实际I/O操作的接口是IoProcessor,它对现有的Reactor模式架构的Java NIO框架继续做了一层封装。它的泛型参数指明了它能处理的会话类型。接口中最重要的几个方法,add用于将指定会话加入到此Processor中,让它负责处理与此会话相关的所有I/O操作。由于写操作会有一个写请求队列,flush就用于对指定会话的写请求队列进行强制刷数据。remove方法用于从此Processor中移除和关闭指定会话,

关于回调函数的几个例子(c)

以下是一个简单的例子。实现了一个repeat_three_times函数,可以把调用者传来的任何回调函数连续执行三次。 例 1. 回调函数 /* para_callback.h */ #ifndef PARA_CALLBACK_H #define PARA_CALLBACK_H typedef void (*callback_t)(void *); extern void repeat_three_times(callback_t, void *); #endif /* para_callback.c */ #include "para_callback.h" void repeat_three_times(callback_t f, void *para) { f(para); f(para); f(para); } /* main.c */ #include #include "para_callback.h" void say_hello(void *str) { printf("Hello %s\n", (const char *)str); } void count_numbers(void *num) { int i; for(i=1; i<=(int)num; i++) printf("%d ", i); putchar('\n');

} int main(void) { repeat_three_times(say_hello, "Guys"); repeat_three_times(count_numbers, (void *)4); return 0; } 回顾一下前面几节的例子,参数类型都是由实现者规定的。而本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void *指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是char *型的,那么在自己提供的回调函数中就应该知道参数要转换成char *型来解释。 回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)。下面实现的max函数可以在任意一组对象中找出最大值,可以是一组int、一组char或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。 例 2. 泛型算法 /* generics.h */ #ifndef GENERICS_H #define GENERICS_H typedef int (*cmp_t)(void *, void *); extern void *max(void *data[], int num, cmp_t cmp); #endif /* generics.c */ #include "generics.h" void *max(void *data[], int num, cmp_t cmp) { int i; void *temp = data[0];

gh0st源码分析,整理笔记

MICROSOFT SPEECH SDK 5.1\INCLUDE MICROSOFT SPEECH SDK 5.1\LIB 以上两个目录是教程里面提到的,可以不使用,一样可以编译 第一节课 新建MFC工程,单文档模式,基类用CListView,其余默认即可 打开gh0stView.cpp找到void CGh0stView::OnInitialUpdate()函数 新建一个结NONCLIENTMETRICS ncm; NONCLIENTMETRICS这个结构就包含了非客户区的一些属性 对ncm变量进行初始化memset(&ncm, 0, sizeof(NONCLIENTMETRICS)); 对这个结构的成员函数进行设置 ncm.cbSize = sizeof(NONCLIENTMETRICS); //指定了ncm的大小 使用宏 VERIFY(::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm,0)); 其目的是方便调试,在Debug版本中,如果计算的表达式为0则中断程序,并打印出错误信息,如果在Release版本中,出现表达式为0的时候,则不做任何的处理,当没有发生::SystemParametersInfo调用系统函数,查询或是设置系统参数,或是修改用户的一个外观,返回值是一个BOOL类型 第一个参数是获取非客户区参数 第二个参数是整个的非客户区结构的大小 第三个参数是将获得到的信息保存在ncm这个地址中 第四个参数由于是为了获取信息写0即可,如果是设置信息为目的,就需要换成其它 参考MSDN 第二节课 在gh0stView.h中声明一个私有成员变量 公有成员可以被所有类访问 受保护成员只能在类内部或其子类可以访问 私有成员只能在类内部访问,其子类是不可以访问的 private: CListCtrl* m_pListCtrl; 私有的列表控件的指针,为什么要声明这个呢,我们之前提到了基类是CListView,因此在这个里声明了一个指针 CPP是执行文件,H是头文件 接下来对我们在CPP文件中声明的私有成员变量进行赋值 m_pListCtrl=&GetListCtrl(); GetListCtrl()这个函数的原型是 CListView::GetListCtrl CListCtrl& GetListCtrl() const; 得到一个和视图相关联的一个列表控件的引用 引用的使用方法:引用相当于是一个别名,周瑜和周公谨是一个人,引用和变量也相当于是

回调函数的概念及其使用

回调函数的概念及其使用

回调函数的概念及其使用 1 什么是回调 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。 对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。 对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal 这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。 Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。 对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。 下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。 2 过程语言中的回调(C) 2.1 函数指针 回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子: void Func(char *s);// 函数原型 void (*pFunc) (char *);//函数指针 可以看出,函数的定义和函数指针的定义非常类似。 一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。 typedef void(*pcb)(char *); 回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。 被调函数的例子:

JQUERY源码解析(架构与依赖模块)

jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架。它是轻量级的js库,它兼容CSS3,还兼容各种浏览器(IE 6.0+,FF1.5+,Safari 2.0+,Opera9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器。jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用)、events、实现动画效果,并且方便地为网站提供AJAX交互。jQuery还有一个比较大的优势是,它的文档说明很全,而且各种应用也说得很详细,同时还有许多成熟的插件可供选择。jQuery能够使用户的html页面保持代码和html内容分离,也就是说,不用再在html里面插入一堆js来调用命令了,只需定义id即可。 The Write Less,Do More(写更少,做更多),无疑就是jQuery的核心理念,简洁的API、优雅的链式、强大的查询与便捷的操作。从而把jQuery打造成前端世界的一把利剑,所向披靡! 简洁的API: 优雅的链式: 强大的选择器: 便捷的操作:

为什么要做jQuery源码解析? 虽然jQuery的文档很完善,潜意识降低了前端开发的入门的门槛,要实现一个动画随手拈来,只要简单的调用一个animate方法传递几个执行的参数即可,但如果要我们自己实现一个定制的动画呢?我们要考虑的问题太多太多了,浏览器兼容、各种属性的获取、逻辑流程、性能等等,这些才是前端开发的基础核心。 如果我们只知道使用jQuery,而不知道其原理,那就是“知其然,而不知其所以然”,说了这么多,那就赶快跟着慕课网进入“高大上”之旅吧,深入来探究jQuery的内部架构! jQuery整体架构 任何程序代码不是一开始就复杂的,成功也不是一躇而蹴的,早期jQuery的作者John Resig 在2005年提议改进Prototype的“Behaviour”库时,只是想让其使用更简单才发布新的jQuery 框架。起初John Resig估计也没料想jQuery会如此的火热。我们可以看到从发布的第一个1. 0开始到目前最新的2.1.1其代码膨胀到了9000多行,它兼容CSS3,还兼容各种浏览器,jQu ery使用户能更方便地处理DOM、事件、实现动画效果,并且方便地为网站提供AJAX交互。 1、最新jQuery2.1.1版本的结构: 代码请查看右侧代码编辑器(1-24行) 2、jQuery的模块依赖网: (单击图片可放大)

回调函数实现dll向主程序传递数据

回调函数实现dll向主程序传递数据 用dll封装窗口主要是封装对话框。软件开发中经常要使用的一个功能是导入数据,且要求可视化操作,如为对应的变量选择对应的列,设置数据的起始和终止行等。希望将界面做成如下形式: 由于这个界面和数据导入的通用性,我们希望将其封装到dll,提供一些接口进行窗口显示和数据传递就可以了。但是我们遇到一个问题:这里的数据传递我们希望是在点击OK之后获取对话框也就是dll中的数据传递到主程序,而在主程序中我们装载dll的函数是一个顺序的执行过程,不能进行消息响应。因此我们想到了使用回调函数。在这里的具体思想就是:在点dll的时候,我们使用回调函数将数据传到主程序,调用主程序的函数进行处理。虽然这样的过程有些打乱程序的执行顺序,但是可以到达我们的目的。下面介绍实现过程: 1、我们建立一个MFC的规则dll将写好的导入数据的对话框代码进行复制, 封装dll。 2、除了导出读取数据和显示对话框的函数之外,导出一个传递函数(回调 函数)形参的函数,这里我们采用一个在一个h文件中定义导出函数, 并利用宏切换功能,实现dll和主程序包含同一个h文件就可以实现函数 的导出和导入。具体代码见程序实例中的DllExport.h 3、具体介绍使用回调函数传递数据功能的实现。在DllExport.h中声明一个 函数的指针 typedef void (* pFunc)(double **pdata,int* nConut); 其中的两个参数pdata和nCount分别为我们要传递数据(一个二维数组)的指针,和一共有多少行数据。 接着声明一个以这个函数指针为形参的导出函数:

APP项目功能和架构分析

APP项目功能和架构分析 2016 年2 月12 日提交 提交人: 电话: Email: Wechat: Reverb 项目功能和架构分析 (1) 项目目标............................................................................................................................................... (3) 平台类型 (3) 项目周期: (3) 交付内容............................................................................................................................................... (4) 功能描述............................................................................................................................................... ...5 (5) (7) (9) 开发计划............................................................................................................................................... .10 付款计划............................................................................................................................................... .11 团队项目经验介绍 (12) 项目可能用到的技术内容 (15) 项目目标 平台类型 Web 网站 iOS 端

直调、回调、异调

1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: 1、属于工作流的一个部分; 2、必须按照工作流指定的调用约定来申明(定义); 3、他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 ======================================================= java回调机制: 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。 同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用; 回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口; 异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。 回调和异步调用的关系非常紧密:使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。 ======================================================== 用Java里的例子: package callbackexample; public interface ICallBack { //需要回调的方法public void postExec(); } 另外的一个类: package callbackexample; public class FooBar { //组合聚合原则 private ICallBack callBack; public void setCallBack(ICallBack callBack) { this.callBack = callBack; doSth(); } public void doSth() { callBack.postExec(); } } 第二个类在测试类里面,是一个匿名类: package callbackexample; public class Test { public static void main(String[] args) { FooBar foo = new FooBar(); foo.setCallBack(new

labview的深入探索----labview与回调函数

labview的深入探索----labview与回调函数 回调函数是WINDOWS 编程(API 编程)的核心内容之一,在许多高级编程语 言,如VB,VC(MFC)中已经封装了回调函数,取而代之的是事件响应函数,但是,追 溯其本质,实际就是回调函数.所谓WINDOWS 回调函数,就是按照WINDOWS 的规范,编写的(CALLBACK)函数,当WINDOWS 检测到事件发生时,自动调用的 函数,WINDOWS 是通过函数指针调用的,因此,回调函数的内容是由用户决定的, 而何时调用是由操作系统决定的.我们看一下CVI 中的一般回调函数的定义int callback aaaa(int panel,int control,int event1,int event2,callbackdata *data);回调函数的参数是有操作系统提供的,比如上面的回调函数,panel---表示的哪个面板(窗口) 发生的事件control---表示的面板上哪个控件发生的事件event1 event2 表示事件 的类型和相应数据,比如鼠标坐标等回调函数是一般高级编程语言的基本功能, 但是,在LABVIEW8.X 之前是不支持的,这极大限制了LABVIEW 功能的扩展, 因为ACTIVEX,.NET 都需要回调函数.8.X 中,增加了回调函数的功能,主要用于ACTIVE,.NET 和LABVIEW 自身控件,LABVIEW 例子程序中提供了几个例子, 是有关ACTIVEX 和.NET 调用的,下面,我们通过LABVIEW 自身控件说明一下 回调函数的使用方法.在.NET 摸板中也提供了这个节点,从分类上就可以看出,注 册回调函数主要是用于ACTIVEX 和.NET 的.下面我们做一个简单的回调函数 的程序,有两个功能,返回当前值的变化和记录控件被点击的次数注册回调函数 需要三个参数:控件参考,用户参数和自动生成的回调函数,有了控件参考,我们就 可以选择事件的类型,用户参数主要是用于返回结果,因为回调函数是由操作系 统调用的,没有办法通过数据流返回处理结果.添加了这两个参数后,就可以自动 生成回调函数了回调函数如下图所示简单编程,CONTROL 的值传递给 INDICATOR 这样值变化的回调函数完成了,下面我们通过鼠标UP 事件来记录

Ceilometer源码分析

Ceilometer项目源码架构 ceilometer主要概念 sample:采样数据是每个采集时间点上meter对应的值; statistics:一般是统计学上某个周期内,meter对应的值(平均值之类); resource:是被监控的资源对象,这个可以是一台虚拟机,一台物理机或者一块云硬盘;alarm:是ceilometer的报警系统,可以通过阈值或者组合条件报警,并设置报警时触发的action,alarm的实现模型有两种,即单例报警系统和分布式报警系统; meter:被跟踪资源的测量值。一个实例有很多的计量值,比如实例运行时长、CPU使用时间、请求磁盘的数量等。在Ceilometer中有三种计量值: Cumulative:累计的,随着时间增长(如磁盘读写); Gauge:计量单位,离散的项目(如浮动IP,镜像上传)和波动的值(如对象存储数值); Delta:增量,随着时间的改变而增加的值(如带宽变化); Centralagent:运行在OpenStack架构中中央管理节点上用来测量和发送监控结果到收集器的服务; Computeagent:运行再OpenStack架构中计算节点上的服务,用来测量和发送监控结果到搜集器中; APIserver:Ceilometer的HTTPRESTAPI服务; collector:运行在OpenStack架构中的服务,用来监控来自其他OpenStack组件和监控代理发送来的通知,并且将其存入数据库中; ceilometer中的服务组件 需要说明这里是针对icehouse版对ceilometer模块进行源码解析。根据配置文件setup.cfg以及/ceilometer/cli.py可以知道,运行在ceilometer中的服务组件有如下所示: ceilometer-api:ceilometer.cli:api ceilometer-agent-central:ceilometer.cli:agent_central ceilometer-agent-compute:ceilometer.cli:agent_compute ceilometer-agent-notification:ceilometer.cli:agent_notification ceilometer-send-sample:ceilometer.cli:send_sample ceilometer-dbsync:ceilometer.cli:storage_dbsync ceilometer-expirer:ceilometer.cli:storage_expirer ceilometer-collector:ceilometer.cli:collector_service ceilometer-alarm-evaluator:ceilometer.cli:alarm_evaluator ceilometer-alarm-notifier:ceilometer.cli:alarm_notifier ceilometer监控数据采集机制 这里借助网上获取的一副图片来说明ceilometer监控数据的采集机制(对其作者表示感谢):

传奇源码分析---框架

传奇源码分析---框架 标签:游戏源码框架 2013-10-31 14:09 2253人阅读评论(1) 收藏举报分类: 游戏源码(4) 版权声明:本文为博主原创文章,未经博主允许不得转载。 最近看游戏源码,对于大一点的源码,完全不知道怎么开始,太庞大了,网狐的源码都达到了1G多了,vc6.0打开直接卡死,不得不说vs2010还是很不错的。大的源码看不懂,最后去看最小的源码,传奇服务端源码。 1.找到winmain函数(GameSvr.cpp),InitApplication()函数注册窗口回调函数MainWndProc(MainWndProc.cpp). InitInstance()函数主要对窗口编程。 2.开启服务回调函数调用OnCommand(),创建了一个线程InitializingServer;在线程里面调用ConnectToServer()函数。ConnectToServer()里面将监听套接字(没看全局变量,推测的)注册到窗口回调函数里面,消息的ID为:_IDM_CLIENTSOCK_MSG然后,自己连接这个服务器,到此ConnectToServer()函数结束。 3.由于上一步,收到了_IDM_CLIENTSOCK_MSG,窗口函数调用OnClientSockMsg()。这个函数里面创建了ProcessLogin、ProcessUserHuman、ProcessMonster、ProcessNPC 线程,然后通过调用InitServerSocket创建CreateIOCPWorkerThread完成端口。继续调用InitThread创建AcceptThread线程,OK到此程序基本框架搭建起来了。CreateIOCPWorkerThread里面创建了完成端口工作者线程ServerWorkerThread。 到此服务器的基本架构搭建起来了。 直接看图吧,思路清晰一些。

如何看懂源代码--(分析源代码方法)

如何看懂源代码--(分析源代码方法) 4 推 荐 由于今日计划着要看Struts 开源框架的源代码 昨天看了一个小时稍微有点头绪,可是这个速度本人表示非常不满意,先去找了下资 料, 觉得不错... 摘自(繁体中文 Traditional Chinese):http://203.208.39.132/translate_c?hl=zh-CN&sl=en&tl=zh-CN&u=http://ww https://www.doczj.com/doc/1913478866.html,/itadm/article.php%3Fc%3D47717&prev=hp&rurl=https://www.doczj.com/doc/1913478866.html,&usg=AL kJrhh4NPO-l6S3OZZlc5hOcEQGQ0nwKA 下文为经过Google翻译过的简体中文版: 我们在写程式时,有不少时间都是在看别人的代码。 例如看小组的代码,看小组整合的守则,若一开始没规划怎么看,就会“噜看噜苦(台语)”不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力。网路上有一篇关于分析看代码的方法,做为程式设计师的您,不妨参考看看,换个角度来分析。也能更有效率的解读你想要的程式码片段。 六个章节: ( 1 )读懂程式码,使心法皆为我所用。( 2 )摸清架构,便可轻松掌握全貌。( 3 )优质工具在手,读懂程式非难事。( 4 )望文生义,进而推敲组件的作用。( 5 )找到程式入口,再由上而下抽丝剥茧。( 6 )阅读的乐趣,透过程式码认识作者。 程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。但是,与其抗拒接收别人的程式码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。 对大多数的程式人来说,撰写程式码或许是令人开心的一件事情,但我相信,有更多人视阅读他人所写成的程式码为畏途。许多人宁可自己重新写过一遍程式码,也不愿意接收别人的程式码,进而修正错误,维护它们,甚至加强功能。 这其中的关键究竟在何处呢?若是一语道破,其实也很简单,程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。这是来自于人类内心深处对于陌生事物的原始恐惧。 读懂别人写的程式码,让你收获满满 不过,基于许多现实的原因,程式人时常受迫要去接收别人的程式码。例如,同事离职了,必须接手他遗留下来的工作,也有可能你是刚进部门的菜鸟,而同事经验值够了,升级了,风水轮流转,一代菜鸟换菜鸟。甚至,你的公司所承接的专案,必须接手或是整合客户前一个厂商所遗留下来的系统,你们手上只有那套系统的原始码(运气好时,还有数量不等的文件)。 诸如此类的故事,其实时常在程式人身边或身上持续上演着。许多程式人都将接手他人的程式码,当做一件悲惨的事情。每个人都不想接手别人所撰写的程式码,因为不想花时间去探索,宁可将生产力花在产生新的程式码,而不是耗费在了解这些程式码上。

Java 异步回调机制实例解析

Java 异步回调机制实例解析 回调,回调。要先有调用,才有调用者和被调用者之间的回调。下面给大家介绍Java异步回调机制实例解析,欢迎阅读! 一、什么是回调 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。 回调是一种特殊的调用,至于三种方式也有点不同。 1、同步回调,即阻塞,单向。 2、回调,即双向(类似自行车的两个齿轮)。 3、异步调用,即通过异步消息进行通知。 二、CS中的异步回调(java案例) 比如这里模拟个场景:客户端发送msg给服务端,服务端处理后(5秒),回调给客户端,告知处理成功。代码如下: 回调接口类: /** *@authorJeffLee *@sincexx-10-2121:34:21 *回调模式-回调接口类 */ publicinterfaceCSCallBack{ publicvoidprocess(Stringstatus); }

模拟客户端: /** *@authorJeffLee *@sincexx-10-2121:25:14 *回调模式-模拟客户端类 */ publicclassClientimplementsCSCallBack{ privateServerserver; publicClient(Serverserver){ this.server=server; } publicvoidsendMsg(finalStringmsg){ System.out.println("客户端:发送的消息为:"+msg); newThread(newRunnable(){ @Override publicvoidrun(){ server.getClientMsg(Client.this,msg); } }).start(); System.out.println("客户端:异步发送成功"); } @Override

相关主题
文本预览
相关文档 最新文档