(二)对服务的深入讨论之上

  上一章其实只是概括性的介绍,下面开始才是真正的细节所在。在进入点函数里面要完成ServiceMain的初始化,准确点说是初始化一个SERVICE_TABLE_ENTRY结构数组,这个结构记录了这个服务程序里面所包含的所有服务的名称和服务的进入点函数,下面是一个SERVICE_TABLE_ENTRY的例子:

SERVICE_TABLE_ENTRY service_table_entry[] =
{
  { "MyFTPd" , FtpdMain },
  { "MyHttpd", Httpserv},
  { NULL, NULL },
};

  第一个成员代表服务的名字,第二个成员是ServiceMain回调函数的地址,上面的服务程序因为拥有两个服务,所以有三个SERVICE_TABLE_ENTRY元素,前两个用于服务,最后的NULL指明数组的结束。

  接下来这个数组的地址被传递到StartServiceCtrlDispatcher函数:

BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY lpServiceStartTable
)

  这个Win32函数表明可执行文件的进程怎样通知SCM包含在这个进程中的服务。就像上一章中讲的那样,StartServiceCtrlDispatcher为每一个传递到它的数组中的非空元素产生一个新的线程,每一个进程开始执行由数组元素中的lpServiceStartTable指明的ServiceMain函数。

SCM启动一个服务程序之后,它会等待该程序的主线程去调StartServiceCtrlDispatcher。如果那个函数在两分钟内没有被调用,SCM将会认为这个服务有问题,并调用TerminateProcess去杀死这个进程。这就要求你的主线程要尽可能快的调用StartServiceCtrlDispatcher。

StartServiceCtrlDispatcher函数则并不立即返回,相反它会驻留在一个循环内。当在该循环内时,StartServiceCtrlDispatcher悬挂起自己,等待下面两个事件中的一个发生。第一,如果SCM要去送一个控制通知给运行在这个进程内一个服务的时候,这个线程就会激活。当控制通知到达后,线程激活并调用相应服务的CtrlHandler函数。CtrlHandler函数处理这个服务控制通知,并返回到StartServiceCtrlDispatcher。StartServiceCtrlDispatcher循环回去后再一次悬挂自己。

  第二,如果服务线程中的一个服务中止,这个线程也将激活。在这种情况下,该进程将运行在它里面的服务数减一。如果服务数为零,StartServiceCtrlDispatcher就会返回到入口点函数,以便能够执行任何与进程有关的清除工作并结束进程。如果还有服务在运行,哪怕只是一个服务,StartServiceCtrlDispatcher也会继续循环下去,继续等待其它的控制通知或者剩下的服务线程中止。

  上面的内容是关于入口点函数的,下面的内容则是关于ServiceMain函数的。还记得以前讲过的ServiceMain函数的的原型吗?但实际上一个ServiceMain函数通常忽略传递给它的两个参数,因为服务一般不怎么传递参数。设置一个服务最好的方法就是设置注册表,一般服务在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service\ServiceName\Parameters
子键下存放自己的设置,这里的ServiceName是服务的名字。事实上,可能要写一个客户应用程序去进行服务的背景设置,这个客户应用程序将这些信息存在注册表中,以便服务读取。当一个外部应用程序已经改变了某个正在运行中的服务的设置数据的时候,这个服务能够用RegNotifyChangeKeyValue函数去接受一个通知,这样就允许服务快速的重新设置自己。

  前面讲到StartServiceCtrlDispatcher为每一个传递到它的数组中的非空元素产生一个新的线程。接下来,一个ServiceMain要做些什么呢?MSDN里面的原文是这样说的:The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests. Next, it should call the SetServiceStatus function to send status information to the service control manager. 为什么呢?因为发出启动服务请求之后,如果在一定时间之内无法完成服务的初始化,SCM会认为服务的启动已经失败了,这个时间的长度在Win NT 4.0中是80秒,Win2000中不详...

  基于上面的理由,ServiceMain要迅速完成自身工作,首先是必不可少的两项工作,第一项是调用RegisterServiceCtrlHandler函数去通知SCM它的CtrlHandler回调函数的地址:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR lpServiceName, //服务的名字
LPHANDLER_FUNCTION lpHandlerProc //CtrlHandler函数地址
)

  第一个参数指明你正在建立的CtrlHandler是为哪一个服务所用,第二个参数是CtrlHandler函数的地址。lpServiceName必须和在SERVICE_TABLE_ENTRY里面被初始化的服务的名字相匹配。RegisterServiceCtrlHandler返回一个SERVICE_STATUS_HANDLE,这是一个32位的句柄。SCM用它来唯一确定这个服务。当这个服务需要把它当时的状态报告给SCM的时候,就必须把这个句柄传给需要它的Win32函数。注意:这个句柄和其他大多数的句柄不同,你无需关闭它。

SCM要求ServiceMain函数的线程在一秒钟内调用RegisterServiceCtrlHandler函数,否则SCM会认为服务已经失败。但在这种情况下,SCM不会终止服务,不过在NT 4中将无法启动这个服务,同时会返回一个不正确的错误信息,这一点在Windows 2000中得到了修正。

  在RegisterServiceCtrlHandler函数返回后,ServiceMain线程要立即告诉SCM服务正在继续初始化。具体的方法是通过调用SetServiceStatus函数传递SERVICE_STATUS数据结构。

BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hService, //服务的句柄
SERVICE_STATUS lpServiceStatus //SERVICE_STATUS结构的地址
)

  这个函数要求传递给它指明服务的句柄(刚刚通过调用RegisterServiceCtrlHandler得到),和一个初始化的SERVICE_STATUS结构的地址:

typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

SERVICE_STATUS结构含有七个成员,它们反映服务的现行状态。所有这些成员必须在这个结构被传递到SetServiceStatus之前正确的设置。

  成员dwServiceType指明服务可执行文件的类型。如果你的可执行文件中只有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“OR”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的服务的生存期内绝对不应该改变。

  成员dwCurrentState是这个结构中最重要的成员,它将告诉SCM你的服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成SERVICE_START_PENDING。在以后具体讲述CtrlHandler函数的时候具体解释其它可能的值。

  成员dwControlsAccepted指明服务愿意接受什么样的控制通知。如果你允许一个SCP去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。这些标志可以用“OR”运算符组合。

  成员dwWin32ExitCode和dwServiceSpecificExitCode是允许服务报告错误的关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员dwWin32ExitCode为NO_ERROR。

  最后的两个成员dwCheckPoint和dwWaitHint是一个服务用来报告它当前的事件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0,dwWaitHint设成一个经过多次尝试后确定比较合适的数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化SERVICE_STATUS结构的成员,更改dwCurrentState为SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改为0。

dwCheckPoint成员的存在对用户是有益的,它允许一个服务报告它处于进程的哪一步。每一次调用SetServiceStatus时,可以增加它到一个能指明服务已经执行到哪一步的数字,它可以帮助用户决定多长时间报告一次服务的进展情况。如果决定要报告服务的初始化进程的每一步,就应该设置dwWaitHint为你认为到达下一步所需的毫秒数,而不是服务完成它的进程所需的毫秒数。

  在服务的所有初始化都完成之后,服务调用SetServiceStatus指明SERVICE_RUNNING,在那一刻服务已经开始运行。通常一个服务是把自己放在一个循环之中来运行的。在循环的内部这个服务进程悬挂自己,等待指明它下一步是应该暂停、继续或停止之类的网络请求或通知。当一个请求到达的时候,服务线程激活并处理这个请求,然后再循环回去等待下一个请求/通知。

  如果一个服务由于一个通知而激活,它会先处理这个通知,除非这个服务得到的是停止或关闭的通知。如果真的是停止或关闭的通知,服务线程将退出循环,执行必要的清除操作,然后从这个线程返回。当ServiceMain线程返回并中止时,引起在StartServiceCtrlDispatcher内睡眠的线程激活,并像在前面解释过的那样,减少它运行的服务的计数。

Technorati 标签: Win32&MFC

有那么一类应用程序,是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行,这就是所谓的服务了。

(一)服务的基础知识

Question 1. 什么是服务?它的特征是什么?

  在NT/2000中,服务是一类受到操作系统优待的程序。一个服务首先是一个Win32可执行程序,如果要写一个功能完备且强大的服务,需要熟悉动态连接库(Dlls)、结构异常处理、内存映射文件、虚拟内存、设备I/O、线程及其同步、Unicode以及其他的由WinAPI函数提供的应用接口。当然本文讨论的只是建立一个可以安装、运行、启动、停止的没有任何其他功能的服务,所以无需上述知识仍可以继续看下去,我会在过程中将理解本文所需要的知识逐一讲解。

  第二要知道的是一个服务决不需要用户界面。大多数的服务将运行在那些被锁在某些黑暗的,冬暖夏凉的小屋子里的强大的服务器上面,即使有用户界面一般也没有人可以看到。如果服务提供任何用户界面如消息框,那么用户错过这些消息的可能性就极高了,所以服务程序通常以控制台程序的形式被编写,进入点函数是main()而不是WinMain()

  也许有人有疑问:没有用户界面的话,要怎样设置、管理一个服务?怎样开始、停止它?服务如何发出警告或错误信息、如何报告关于它的执行情况的统计数据?这些问题的答案就是服务能够被远程管理,Windows NT/2000提供了大量的管理工具,这些工具允许通过网络上的其它计算机对某台机器上面的服务进行管理。比如Windows 2000里面的“控制台”程序(mmc.exe),用它添加“管理单元”就可以管理本机或其他机器上的服务。

Question 2. 服务的安全性...

  想要写一个服务,就必须熟悉Win NT/2000的安全机制,在上述操作系统之中,所有安全都是基于用户的。换句话说——进程、线程、文件、注册表键、信号、事件等等等等都属于一个用户。当一个进程被产生的时候,它都是执行在一个用户的上下文(context),这个用户帐号可能在本机,也可能在网络中的其他机器上,或者是在一个特殊的账号:System Account——即系统帐号的上下文

  如果一个进程正在一个用户帐号下执行,那么这个进程就同时拥有这个用户所能拥有的一切访问权限,不论是在本机还是网络。系统帐号则是一个特殊的账号,它用来标识系统本身,而且运行在这个帐号下的任何进程都拥有系统上的所有访问权限,但是系统帐号不能在域上使用,无法访问网络资源...

  服务也是Win32可执行程序,它也需要执行在一个context,通常服务都是在系统账号下运行,但是也可以根据情况选择让它运行在一个用户账号下,也就会因此获得相应的访问资源的权限。

Question 3. 服务的三个组成部分

  一个服务由三部分组成,第一部分是Service Control Manager(SCM)。每个Windows NT/2000系统都有一个SCMSCM存在于Service.exe中,在Windows启动的时候会自动运行,伴随着操作系统的启动和关闭而产生和终止。这个进程以系统特权运行,并且提供一个统一的、安全的手段去控制服务。它其实是一个RPC Server,因此我们可以远程安装和管理服务,不过这不在本文讨论的范围之内。SCM包含一个储存着已安装的服务和驱动程序的信息的数据库,通过SCM可以统一的、安全的管理这些信息,因此一个服务程序的安装过程就是将自身的信息写入这个数据库。

  第二部分就是服务本身。一个服务拥有能从SCM收到信号和命令所必需的的特殊代码,并且能够在处理后将它的状态回传给SCM

  第三部分也就是最后一部分,是一个Service Control Dispatcher(SCP)。它是一个拥有用户界面,允许用户开始、停止、暂停、继续,并且控制一个或多个安装在计算机上服务的Win32应用程序。SCP的作用是与SCM通讯,Windows 2000管理工具中的“服务”就是一个典型的SCP

  在这三个组成部分中,用户最可能去写服务本身,同时也可能不得不写一个与其伴随的客户端程序作为一个SCP去和SCM通讯,本文只讨论去设计和实现一个服务,关于如何去实现一个SCP则在以后的其它文章中介绍。

Question 4. 怎样开始设计服务

  还记得前面我提到服务程序的入口点函数一般都是main()吗?一个服务拥有很重要的三个函数,第一个就是入口点函数,其实用WinMain()作为入口点函数也不是不可以,虽然说服务不应该有用户界面,但是其实存在很少的几个例外,这就是下面图中的选项存在的原因。

  由于要和用户桌面进行信息交互,服务程序有时会以WinMain()作为入口点函数。

  入口函数负责初始化整个进程,由这个进程中的主线程来执行。这意味着它应用于这个可执行文件中的所有服务。要知道,一个可执行文件中能够包含多个服务以使得执行更加有效。主进程通知SCM在可执行文件中含有几个服务,并且给出每一个服务的ServiceMain回调(Call Back)函数的地址。一旦在可执行文件内的所有服务都已经停止运行,主线程就在进程终止前对整个进程进行清除。

  第二个很重要的函数就是ServiceMain,我看过一些例子程序里面对自己的服务的进入点函数都固定命名为ServiceMain,其实并没有规定过一定要那样命名,任何的函数只要符合下列的形式都可以作为服务的进入点函数。

VOID WINAPI ServiceMain(

  DWORD dwArgc, // 参数个数

  LPTSTR *lpszArgv // 参数串

);

  这个函数由操作系统调用,并执行能完成服务的代码。一个专用的线程执行每一个服务的ServiceMain函数,注意是服务而不是服务程序,这是因为每个服务也都拥有与自己唯一对应的ServiceMain函数,关于这一点可以用“管理工具”里的“服务”去察看Win2000里面自带的服务,就会发现其实很多服务都是由service.exe单独提供的。当主线程调用Win32函数StartServiceCtrlDispatcher的时候,SCM为这个进程中的每一个服务产生一个线程。这些线程中的每一个都和它的相应的服务的ServiceMain函数一起执行,这就是服务总是多线程的原因——一个仅有一个服务的可执行文件将有一个主线程,其它的线程执行服务本身。

  第三个也就是最后的一个重要函数是CtrlHandler,它必须拥有下面的原型:

VOID WINAPI CtrlHandler(

DWORD fdwControl //控制命令

)

  像ServiceMain一样,CtrlHandler也是一个回调函数,用户必须为它的服务程序中每一个服务写一个单独的CtrlHandler函数,因此如果有一个程序含有两个服务,那么它至少要拥有5个不同的函数:作为入口点的main()WinMain(),用于第一个服务的ServiceMain函数和CtrlHandler函数,以及用于第二个服务的ServiceMain函数和CtrlHandler函数。

  SCM调用一个服务的CtrlHandler函数去改变这个服务的状态。例如,当某个管理员用管理工具里的“服务”尝试停止你的服务的时候,你的服务的CtrlHandler函数将收到一个SERVICE_CONTROL_STOP通知。CtrlHandler函数负责执行停止服务所需的一切代码。由于是进程的主线程执行所有的CtrlHandler函数,因而必须尽量优化你的CtrlHandler函数的代码,使它运行起来足够快,以便相同进程中的其它服务的CtrlHandler函数能在适当的时间内收到属于它们的通知。而且基于上述原因,你的CtrlHandler函数必须要能够将想要传达的状态送到服务线程,这个传递过程没有固定的方法,完全取决于你的服务的用途。

单田芳先生是中国著名的评书演员,我非常喜欢听单田芒的评书,在那个没有电视机的时代,收听单田芳先生的评书对我来说就是一种最大的人生享受。所以,“单田芳”这三个字早就镌刻在我的脑海之中。一直到今天我还深深地敬仰着这位全国闻名的优秀演员。俗话说,金杯银杯,不如老百姓的口碑。单先生的评书艺术在全国人民中是有口皆碑!


    易中天先生是我到《百家讲坛》之后才认识的。易先生是一位优秀的学者,他的学术著作曾经得到过教育部的奖励。我也是一位大学教师,深知得到这样一种奖励是何等不易!更何况易先生得奖的那个年代,评奖主要是看学术贡献啊!


    学者与学员不能进行横向比较,因为他们不在一个逻辑层面上!但是,有人连这么一个起码的常识都不尊重,非要把不在一个逻辑层面上的两个人进行横向比较!


    如果非要比,就像新浪博客上发表的《易中天单田芳没本质区别》一文,那么,我认为二者最本质的区别就在于:单田芳是演员,易中天是学者。


    同样讲三国,学者与演员的区别就在于学者有丰厚的学术修养,因此,他的讲解有着学术品味。看看《品三国(下)》最后易先生对三国时代的总体分析,尊重事实的读者还看不出一个学者的学养吗?


    《百家讲坛》是中央电视台一个非常注重制度建设的品牌栏目。《百家讲坛》的制度最重要的是两条:一是主讲人制,二是准入制。前者讲的是该栏目是以主讲人的个人讲解为主,后者讲的是所有进入该栏目的主讲人都有一个资格审查。正如《百家讲坛》一再重申的选择主讲人的标准:学养,口才,亲和力。第一条就是学养,学术修养,这一条是雷打不动的,是一条不能逾越的底线!


    因此,单先生与易先生各自有着属于自己的舞台,有着各自发挥自己专长的平台.


    我希望网友们不要再拿这说事,因为,这对两位优秀人才都是一种伤害!

http://www.cnblogs.com/guaiguai/archive/2007/09/17/894819.html


现代的软件科学中, 很多内容和概念, 实际上是从数学/语言学等相当古老的领域里借来的, 为什么呢? 因为软件科学中的很多方面, 与其它学科中所碰到的问题并无不同. 一套数学理论,某个数学公式,无论从哪个层次去看,和它们有关的人分为两种:发明者,使用者. 这和软件也是相当一致的,  软件首先要有人编制, 然后别人来使用(好不好另说). 数学的一个特性就是他的抽象性, 本文讨论软件设计中, 由抽象所展开的一些问题.


对抽象的理解的误区可能使得很多人忽视了广泛存在的抽象. 因为接口和类如何抽象现实事物的种种说法, 在很多人的观念里混淆了在设计时具有的抽象, 从而对抽象的本质进行的歪曲, 忽略了除OO建模以外抽象, 或者在OO建模这个过程中选择了错误的抽象方式. 显而易见的, 比如数学实际上是一种对现实事物相当高级的抽象, 与此同时, 数学也就成了一个相当良好的解决问题的工具. 而我们编程人员所担负的责任导致我们的工作, 本质上是一个抽象和构造的过程. 所以如何抽象合理合法, 是我们首先要关注的一个问题. 那么我们首先要知道的是,什么是抽象方法?按照数学抽象方法的解释变化一下,我们可以得到如下一个描述:


抽象方法的软件设计版本

抽象方法是从考虑的问题出发,通过对各种经验事实的观察、分析、综合和比较,在人们的思维中撇开事物现象的、外部的、偶然的东西,抽出事物本质的、内在的、必然的东西,从空间形式和数量关系上揭示客观对象的本质和规律,或者在已有软件设计成果的基础上,抽出其某一种属性作为新的软件设计对象,以此达到表现事物本质和规律的目的的一种软件设计领域的研究方法。



以上描述,基本是把数学换为“软件设计”就能得出的结果. 比如在几何中, “点”的概念是从现实世界中的水点、雨点、起点、终点等具体事物中抽象出来的,它舍弃了事物的各种物理、化学等性质,不考虑其大小、仅仅保留其表示位置的性质。从这里我们可以看到, 为了研究和解决问题而进行的抽象, 在数学领域中和现实世界中多么的不同. 但是进行抽象前, 一个潜在的前提被忽略了, 即几何这个数学工具要解决的问题, 决定了抽象的结果. 很显然换一个领域解决其它问题, 比如研究雨滴的物理特性, 抽象的结果就完全不同.


由于软件解决的问题往往是千差万别的, 这就导致我们抽象的结果, 从一个常规的角度看, 往往不能够逼近现实世界, 且对相同的东西, 表示的方法很不统一. 甚至突然看去, 和现实世界是那么的不同. 这也是很多OO狂热者所误解的一个想法: 与现实世界偏离太远的一组模型不是好的模型, 从而忽视了一个人类解决问题而发明的比如数学(或其它科学)这些最高效的工具, 从来和现实世界是不同的. 比如 E = MC2, 它真实的表达了事物的本质, 却并非是一个直观上与现实世界相似的表示方法.


我们考虑软件设计中, 存在某一客观事物. 往往一个熟知面向对象的设计方法的人员, 就把这一事物抽象为一个对象, 然后抽象出它的属性(数据)和方法(行为). 在这一过程中, 经常被忽视的一个问题是, 由于软件设计所面对的客观情况的复杂性, 我们常常会面临解决不同的问题, 在解决不同的问题时, 应该使用不同的抽象. 由于在脑海里没有清晰的问题域, 在很多人看来, 这(同一事物不同抽象)就硬生生的把一个事物割裂开来(其实可以不同抽象同一实现来重新统一, 但设计的合理仍然依赖于不同抽象的必要性), 不自然, 于是不合理不合法.


比如贫血模型的好与坏, 比如Martin所划分的”事物脚本”, “表模块”, “领域模型”, 都是在这样情况下产生争论的. 这个在上个关于充血贫血讨论的回帖中已经有论述, 我已经把回帖单独提出来, 有兴趣的朋友可以讨论. 拿Martin划分的三种方式来说, 以本文开头对于抽象的定义, 想必大家可以达成一个共识, 即无论采用那种方式, 都是对事物的一种抽象, 区别只是好坏问题. 看过我的回帖的, 应该有这么一个印象, 即谁好谁坏, 并不是以符合那种方式就可以分辨的(Martin同学也承认).


在我们的某个项目实现和实施的过程中, 除了对象, 方法, 属性这些东西, 还有什么是对事物的抽象? 不知道大家还记不记得数据库教科书里面教我们的那些? 没错, 就是那些几NF之类枯燥无味乱七八糟的东西. 我发现这样一个事实: 讨论面向对象软件设计的各位牛人之中, 很少有关注数据库设计的. 其实数据库设计的书现在也是车载斗量; 从深度和实用性讲, 数据仓库是一门更博大精深的学问. 其实咱们大多数讨论谈到的数据库, 在数据仓库大牛, 如Ralph Kimball眼里, 不过是个源操作环境(名词可能记的不准确). 而百万行级上百个表甚至更多的数据, 如果要产生价值, 不使用数据仓库的种种设计方法, 那么其实跟废数据区别不大.Ralph是什么观点呢? 数据的设计是如此的重要, 如果没有良好的数据表现形式, 沃尔玛的老板永远不会知道啤酒和尿布之间存在着什么样的联系, 结果基于数据做出的决定让啤酒大幅的增加了销量.


某些兄弟提到, 将软件(不包含数据库等)设计的结果作为数据库设计(作为数据的结构设计的一个子集)的前提和约束. 通过这样的手段, 把数据设计稳定下来, 绝对是一种行之有效的手段, 但它很难变成一种放之四海皆准的设计手段. 这个是自顶而下方法论大师们的一个误导(大嘴们经常犯这个错误): 光拿出一个方法, 却忘了交代该方法所针对的问题.


请注意数据仓库领域中”维度建模”这个词, 不用搞清楚数据仓库到底是什么, 也不用搞清楚什么维度不维度的; 存在这个就足以说明, 建模不只是纯粹的软件结构设计的问题. 至少在Ralph所研究的系统里, 恐怕数据仓库的设计要比软件结构设计重要的多. 道理很简单: 数据的结构是合理的, 那么针对数据的操作可以有各种合理的版本, 未来业务变化, 新增不同数据和构筑在不同数据之间的处理也很容易; 同时, 请不要忽视一点: 数据不应只是简单的行的堆积, 数据本身是存在广泛联系的, 而且数据本身也要表现出这种联系; 更要命的是, 数据如果被要求发挥最大的价值, 那么其表现形式的抽象过程比其它任何东西都需要小心对待.


在我看来, 这个(数据驱动还是对象驱动)问题的关键点在于, 用户到底要的是数据, 还是软件? 可能大多数人回答是软件. 其实拿沃尔玛来说, 他要的是数据, 以及和数据相互作用之后数据. 当然如果没有软件, 沃尔玛的全部员工翻所有数据翻10年也翻不完, 更不论计算了. 但是如果只有软件, 那么我只能一摊双手, 告诉你这儿社么都没有. 软件是人收集数据的簸箕, 观看数据的窗口, 是计算数据的工具. 让我们来看看Brooks曾经说过什么吧:


原文引用更普遍的是, 战略上突破常来自数据或表的重新表达--这是程序的核心所在. 如果提供了程序流程图, 而没有表数据, 我仍然会很迷惑. 而给我看表数据, 往往就不再需要流程图, 程序结构是非常清晰的.

当然, 是不是真的不需要流程图, 我看他在这里也学了Martin大嘴了一把(虽然他年纪更老, 也更大牛一点). 当然也许Brooks的意思是他可以一直研究某种很复杂的表数据长达若干年直到完全搞定, 这样看我也赞同他的观点: 有表数据已经无敌了. 重要的是前半句, 如果我们把”程序流程图”没有指代但在软件设计中除了数据设计外其它内容包含进来, 他们全都没有会怎样, 数据成了废数据? 这就是千百万的$所换来的结果? 那么让我们干脆点, 问一个问题, 没有软件会怎么样?


实际上, 数据库已经存在千百万年了, 只是在计算机上实现, 是这几十年的事. 我们考虑60年代的人口普查表, 和现在的不会有什么不同. 在这个表上就存在着种种抽象: 一个人不是姓名年龄性别出生日期就能代表的, 实际上表上行, 就是对真实存在的人的抽象. 因为人口普查这个问题域, 需要这种抽象. 往往这种抽象, 还是经历几十年甚至上百年反复调整和重构, 才得到的结果. 所以抽象并建模对于人类来说实际上早就开始了. 那么进行数据驱动式的设计理由是充分的: 我们首先需要的是数据本身的表现形式的正确性. 看看Ralph和Brooks的潜台词: 数据的表达应该包括足够的信息. 在数据这一层面, 对于Brooks来说, 良好的抽象能造成软件整体的突破(对计算机世界); 对于Ralph来说, 良好的抽象能更多的挖掘出数据所隐含的信息(对现实世界).


当然, 这不说明(在一些领域内)对象驱动是不合理的, 但这足够说明, 数据库仅仅用来存储对象的状态, 而由面向对象的设计来抽象整个世界, 这个情况并不常常发生, 其原因是从某种程度上来说, 至少对于沃尔玛的老板, 追逐软件而不是数据本身是舍本逐末的. 经常的, 对于居委会老大妈来说, 数据表现形式的设计就是对世界最好也最急需的抽象. 所以对于是否对象驱动通吃, 就引出了下一个问题(答案往往是层层拨开的) : 面向对象的设计最终产生的存储对象状态的数据结构, 是不是在任何领域内都能和直接抽象出来的数据结构一样好? 抱歉我暂时没法回答这个问题, 因为这篇文章简直是走到哪儿, 写到哪儿, 我还没真正思考过这个问题. 当然, 如果我是大嘴Martin, 我会直接告诉你不能…


不过没有答案, 不妨碍不能从其它周边的角度来进行一下讨论:


公元前二百二十一年, 秦始皇统一中国. 如果我们不过分的贬低古人的智慧, 可以考虑这样一个问题, 如果OOA/OOD真的能够得到良好的数据结构, 而且比直接数据设计合理, 那么是不是OOA/OOD中, 最后能决定数据的表现形式的这一部分方法, 早在秦始皇记录到底谁有几本书好烧掉它们的时候, 就开始被人研究和发掘了呢? 毕竟薛定谔的代数方程和哥本哈根学派的矩阵力学最后的表达的都是一码事, 只要有条路通到罗马, 这条路就应该有人走才对.可历史上对数据的建模并不存在这样一个设计步骤, 当然你可以说这个那个OO里也有, 但这是OO借用人家的; 这是这个考虑的一个方面.


另一个方面, 我们需要看看抽象出来的工具中最璀璨的明珠, 数学. 数学的抽象, 不但实际上和面向对象背道而驰, 而且他们的数据表现形式, 往往和居委会老大妈掰脚趾头全无区别, 比如那些和自然数列一一对应的问题. 它们对现实世界面向过程的抽象方式, 跟真实世界简直是驴唇不对马嘴, 却更贴切的表达了很多事物的本质.


与面向对象设计相反的, 提炼关系(函数)/元素/集合的面向过程抽象方法, 关系型数据库本身和基于它的设计和抽象, 却是从数学, 尤其是集合论等抽象的工具中, 直接衍生出来的. 从这里, 我给出一个个人对软件设计的认识, 它对一些人来说是颠覆性的, 信不信由你:


面向过程和数据表现形式的设计, 是比面向对象更高层次的抽象, 而且优秀的面向过程和数据设计, 是对解决问题更有效的抽象, 因为它们抛弃了一切可以抛弃的无用负担.


当然, 能合理驾驭面向过程和关系模型的人, 恐怕脑容量得远远高于常人, 以至于Martin这个大嘴说:


原文引用事实上, 我一直坚信面向对象的最大优点在于它能够使复杂逻辑易于处理.

这很显然就是在说: Martin的脑子不够处理某些逻辑, 所以不得不借助面向对象这个拐棍. 当然估摸着脑筋够用的人很少存在, 于是大多数复杂性够高, 同时面向过程的项目失败了. 不过请考虑一下我们在集合论中学过的那些, 函数的定义, 关系的定义, 等等等等, 由集合论的方法来看, 我们解决问题时,要找到的实际上常常是一个集合S, 一个a和一个b, 以及一个连接a和b的关系R.


在这里需要申明的是我绝非一个DBA跳到程序员堆里玩深沉, 我从来没设计过真正需要数据仓库的系统, 而且我现在的工作与数据库完全无关. 同时我是一个坚定的面向对象的信徒, 我也绝对不会说, 实用主义的话应该怎么怎么样, 因为我不相信什么实用主义. 象遗留系统等问题, 是客观存在的情况, 一个新的系统是可以对由于遗留系统产生的问题进行一次抽象, 让新系统至少是新的; 上帝的归上帝, 凯撒的归凯撒, 本该如此就是最大的实用主义. 但是我要知道我使用的(面向对象这一)工具到底是干吗地的, 帮我解决了什么问题, 而不是神话之(过犹不及嘛), 这样才能最大化这一设计工具的最大价值: 面向对象的抽象方法就是把我玩不转的形式, 转化为玩的转形式的这么一个工具. 在这里, 给出第二个我个人的认识:


面向对象仅仅是帮我们前进的一个手段, 他通过增加一些从根本上讲毫无意义的细节和表达形式等冗余, 使得我们能更好的组织我们的过程与数据.


接下来说说另外一个抽象过程中经常发生的问题: 忽略了对计算机世界进行抽象和建模的重要性. 而这个问题和如何对现实世界进行建模有着相同的重要性, 很显然, Int32是一个抽象, String是一个抽象, 问题是当我们编制软件的时候, 光是这些对象是不够的(人家Martin Fowler说了, 这叫基本型迷恋), 很多人对这一点认识不深, 导致重视不足. 而对远离基本型迷恋的原因, 大嘴们给出的解释又过于浮浅, 将很多与基本型迷恋相似的问题给淹没了.


我提请大家注意一个非常重要的事实: 在.NET Framework里, 或者Java的各种框架里, 实际上存在着非常大量的与现实世界无关的模型(即使一些模型与现实世界存在着相似性). 当我们构建一个软件或系统的时候, 每个人数的都是自己的代码行数, 其实要是将操作系统也作为提供的解决方案的一部分(甚至不包括操作系统仅包含用到的基础框架), 即便上亿美金的项目, 恐怕仍然是对非现实世界无关模型的使用占很大部分.


让我们基于这一事实进行一个想像, 假设.NET Framework是一个十分蹩脚的设计(其实ASP.NET的一些方面就相当蹩脚), 那么我们基于.NET Framework的项目, 综合考虑时间/人力等等成本在解决来自Framework的问题, 还有几个能成功呢? 而现在情况是, 基础环境或多或少的和我们特定的问题不合拍, 而大多数项目中的设计, 恰恰是根本缺乏对计算机世界的合理建模而直接构筑在通用框架或工具箱提供的模型上; 又或者有一个比如各种ORM这样的通用工具, 拿一个持久化的概念, 就把我们可怜的计算机, 操作系统, 物理存储的文档或者数据库给打发了. 这样的设计必然会造成, 要么数据表现形式看上去不合时宜, 要么面向对象的设计方法看上去很别扭. 在这种情况下, 特别是那种数据驱动的项目, 不能本能的考虑去修改数据库, 难道你能轻易抱怨.NET对String的实现吗? 所以在设计伊始, 就要考虑到你这个设计如何对来自计算机世界的现有事物进行抽象, 或者对现有事物与设计中其它部分的关系进行抽象, 才能最大程度的保证设计的完备与统一.


至于面向过程比较容易适应, 恰恰是因为这种抽象的表达方式更本质, 除非问题域变了, 否则 f = ma 做成一个静态方法, 摆在哪里永远不会废弃. 有新情况? 来个新公式吧. 当然, 面向过程也能做到重用, 多态, 问题是逻辑复杂度高于Martin的7.42, 人脑就到了极限, 正巧这7.42到底是啥还没个准谱. 所以说, 面向对象总是有益的, 但是如果对什么是抽象, 怎么个抽法,  都抽谁, 还存在着疑问, 那么实际上降低复杂度的同时, 也提高了各种事故出现的概率. 实际上一些基本问题, 现代编程语言已经帮我们处理了(这个以后探讨), 但是来自软件概念性层面上的问题和复杂性, 只有我们自己能解决.


关于面向对象如何帮助我们, 我会在后续话题里进行讨论. 毕竟这篇文章是介绍抽象的, 同时很大篇幅的讨论了数据驱动, 对象驱动和面向过程, 面向对象等抽象方式不同的意义. 让我们回顾一下:


  1. 软件设计领域内, 抽象无处不在, 在面向对象中, 在面向过程中, 在数据表现形式中, 在界面设计中(这个没有提到). 加一句在本文没有体现的(也许未来我会加上相关论述, 这个认识相当重要), 软件设计, 归根结底不应该是对现实世界抽象, 而是对”就某个问题, 使用计算机去更好的解决”这一过程的抽象, 虽然这一过程往往包含对现实世界的抽象, 但多了个大方向, 进行对现实世界的抽象时, 方式方法就不一样了. 如果不认清这个问题, 无论是面向对象(多这个大方向并不会导致”不够OO”这种问题, 只会OO的更正确)还是其它什么方法, 无论你是Anders还是Gosling, 都不可能获得正确的结果.

  1. 好的抽象的唯一标准是是否在正确的问题下良好的运转, 而并非是在直观上和现实世界多么的相似. 而且我们在生活中所能看到的很多最好的抽象, 往往在直观上与现实世界的事物毫不相似, 却在问题所在的领域内更接近现实世界事物的本质.

  1. 面向过程和数据表现形式的设计, 是比面向对象更高层次的抽象(难道搞出一个汽车类, 不是具体化么), 而且优秀的面向过程和数据设计, 是对解决问题更有效的抽象, 因为它们抛弃了一切可以抛弃的多余内容. 面向对象的抽象方式仅仅是帮我们前进的一个手段, 他通过为抽象增加一些从根本上讲毫无意义的细节和表达方式等冗余, 使得我们能更好的组织我们的过程与数据.

  1. 在一个大的问题域内, 如果数据表现形式的设计更根本,  就绝对不存在对象驱动还是数据驱动的问题,  只能数据驱动. 同时我们也要承认, 存在着很多领域,  数据只是操作模型的附属, 在这样的情况下, 数据到底怎么设计, 甚至可以发展到毫无重要性的程度. 与Martin对复杂度的分辨不同, 到底用户要的是什么, 这不是一个7.42, 而总会偏向于某一方.

  1. 在设计中, 尤其是数据驱动的设计中, 既要考虑对现实世界的抽象,  也要考虑对计算机世界的抽象. 软件部分面向对象的产物与其它已知结构的冲突(比如与数据库设计的冲突), 其原因是没有把计算机世界某一部分(如关系型数据库及构筑于其上的数据表现形式)就软件所处理的问题域进行再次的合理抽象, 或者太轻易的处理了它们(比如, 忽视了数据本质也是一个接口这个隐含的约束).

最后送给大家Brooks另一句话, 这一句话曾经指导过1000W行以上的单一项目的设计, 同时也指导了软件设计长达40余年, 并且我想它还会作为一句少有的经典, 继续存在下去:


原文引用数据的表现形式是编程的根本.

这句话本身所真正拥有的真知灼见, 比他当时语境下所要表达的含义还要多的多. 这句话绝非从什么主义出发,无论你是一个面向对象的狂热信徒还是一个随便造个二进制文件当数据持久的高人, 我想仍然需要好好咀嚼一下.

算起来,我用Visual C++也有将近5年的历史了。在这期间,我也曾涉猎过Visual Basic和Delphi,但都是浅尝而止;Visual C++始终是我的主业。可是努力的成果如何呢?我用Delphi作出了十多个有规模的软件,用VB--虽然我用在VB上的时间只有短短的两三个月--也有两个像样的项目;然而,在我付出了最大热情和最多努力的Visual C++上面,却只作出了三个自己看得上眼的软件。


固然,在用Visual C++的时候,MFC帮了我不少的忙。但是,在写下这个题目之时,我就已经打定主意:在这篇文章中,只对MFC提出批评,不说MFC的好话。Visual C++的拥护者且慢发难,听我道出其中原因。我注意到,象候捷先生这样对MFC极其热爱的著者,在其大著《深入浅出MFC》中对MFC的评价也是尽量的做到客观和公允;而大师Charles Petzold和Jeff Prosise,在他们的作品中也只是给予MFC以谨慎的赞美。Charles Petzold还很客气的指出了MFC的局限。然而另外一些编程书籍的作者,特别是某些国内的作者,似乎毫不吝惜把最华丽的语言和最夸张的赞美赋予 MFC,从书架上任意翻开一本介绍Visual C++的书籍,看看它的前言和序章,往往充斥着让人目眩的溢美之辞。多少初学者被这些充满暗示和诱导的辞令吸引,以为MFC是完全可视化的,象VB一样容易掌握的东西,当他们深入以后,会不会有上当的感觉呢?我痛恨一切不负责任的夸大和炫耀,特别是只为了增加书籍销量而不惜昧着良心说话的作者,而我的感觉是现在这样的作者和书籍似乎已经泛滥了。本着矫枉必须过正的指导思想,我的目的很明确,就是要批评MFC。对Visual C++和MFC非常熟悉的读者,我无虑您对本文提出批评和指责,因为您对MFC已经有了自己的观点,不会为我所误导;对Visual C++的入门者,我希望您在听够了对Visual C++和MFC的赞美之后,来听听另一种声音,即使它并不完全正确(甚或是充满谬误),至少能让您带着自己的思想来看待您将要学习的东西。



对MFC的批判之一:不支持属性,MFC凭什么同其他语言抗衡?


窃以为在编程语言中引入“Property”的概念是在面向对象的编程思想后最为重大的革新之一。其实,目前市场上绝大部分编程语言,包括VB, Delphi,C++Builder和PowerBuilder等等都支持Property。程序员只要简单的修改对象的属性,就能够完成相当部分的工作,不仅是减少了无谓的劳动,更重要的是减少了出错的机会,并且使得生成更复杂的界面和完成更复杂的工作成为相对容易的工作。我想绝大部分人会同意,如果去掉了Property这个东西,那么象VB和Delphi这样好的RAD,包括Microsoft一直倡导的ActiveX,都会失去了绝大部分的魅力。这个道理,Microsoft应该是在推出Visual Basic 1.0的时候就认识到了。可是自从Visual C++诞生到现在,它似乎丝毫没有使用Property的意思,虽然Visual C++这个名字在很大程序上沾了它的长兄Visual Basic的光,不过它并没有从VB那里学到如何让编程更简单和更轻松的秘诀。


有人可能会说,data member of class不是property吗?不是的,如果你用过C++Builder的话你一定能明白这种分别。MFC从来就不支持Property,而且今后看来也不会了,这意味着用MFC,你还是得干苦力活。(ActiveX?不错,ActiveX支持Property,而且MFC支持ActiveX开发,不过这并不是三段论发挥作用的地方。)



对MFC的批判之二:单调的处理方式使本来应该简单的工作变得复杂


应该没有人反对这样的观点:用Visual C++开发界面,特别是不符合Microsoft所谓“标准”的程序,比VB,Delphi或是C++Builder都要慢得多。(附带说一句,我不知道 Microsoft制定Windows Logo标准,并且要求程序员遵守的依据是什么;我自己的程序99%以上都不符合这个标准。)可这是为什么呢?是C++语言在这方面的天生不足?肯定不是。


在我看来,罪魁祸首是MFC中的CDocTemplate,这个类规定了一种非常死板和机械的机制,即一个Document,一个View和一个FrameWnd绑定在一起。遗憾的是,实际情况往往复杂的多。对界面稍微稍微要求高一点的程序大多要求一个Document有多个View,甚至在某些程序中,希望在同一个View中显示多个Document的内容:比如,将两个公司的业绩放在一起作比较。对View和FrameWnd的关系也有类似的情况。然而,DocTemplate的机制使得这样并不高的要求变的相当困难。想实现你的要求吗?可以。你要添加新的View类。你要从默认的 IDR_MAINFRAME复制资源到新的类中。你要用AddDocTemplate添加自定义模板。你要用

GetFirstDocTemplatePosition和GetNextDocTemplate检索模板列表。你要用GetDocString察看每个模板是否符合你的要求。你要重载CFrameWnd::OnCreateClient以派生新的视图。你要用CView::SetDlgCtrlID和 CFrameWnd::SetActiveView以及CFrameWnd::RecalcLayout来在各个视图中切换。你要用未公开的 CDocManager管理文档模板。你要…还要吗?反正我是怕了。



对MFC的批判之三:固步自封,不思进取


MFC 可以作为固步自封的活教材。别忘了,MFC是和Borland OWL同一时代的产物(还有多少人记得OWL呢?)当然,这不是MFC的错误。不过,如果以个类库的体系自从2.0版本以来就没有丝毫改变,是不是意味着这个类库已经臻于完美了呢?不,即使Microsoft也不敢这么狂妄。但事实是,MFC的体系从MFC2.0以来就没有变动过。每个版本的更新,不过是增加了一些新类,某些类的接口稍作修改,仅此而已。不,不要把ATL作为MFC的改进;ATL从来就不依赖于MFC。


谁都知道在这几年中C++语言有了多么大的改进。包括RTTI,Dynamic Creation,Exception,Standard Template Library等等都成为新的C++标准的一部分。不过,Microsoft好像并不喜欢这些新东西,它的做法是另起炉灶;于是在同以套Visual C++中就出现了两套互不兼容的实现。平心而论,在新的C++标准出台前,Microsoft自己实现这些机制实在是一个相当了不起的创举;但是历史总是在发展的,MFC为什么不从善如流,尽量利用语言中已经实现的功能,而非要固执的用自己的一套老办法?事实上,MFC几乎没有用到新的C++方案中任何有益的元素--尽管这些方案已经对MFC库中很多问题提出了相当完美而且精练的解决方法。



对MFC的批判之四:天然的倾向性


不知道您对AppWizard生成的默认项目有什么感觉。反正我的感觉是:这种工程就是用来开发象Word,Excel这样的程序的。好像MFC天然的就倾向于这样一种程序。但事实上,这种程序少之又少。


在Microsoft 看来,好像每个程序都应该有一个File菜单,而且这个菜单下面一定应该有New,Open,Save,Exit这些选项。在我的实际经验来说,我只搞过一个程序符合这样的要求。有多少人真的要搞一个自己的字处理程序或是电子表格呢?对于很多常见的、基于数据库的程序,你需要New,Open和Save 吗?如果是基于网络的程序呢?特别是在多媒体程序和游戏程序中,MFC生成的框架与其说是帮助,不如说是累赘。


这倒是符合Microsoft的一贯风格:你要的只是特定的功能,它却一并塞给你一大串不相干的东西,并且在很多时候,这些不相干的东西反而成为麻烦制造者。于是,我不得不在新生成一个项目后,不辞劳苦的去掉AppWizard“慷慨”的赠送品,包括大量无用的菜单和工具条按钮,然后才能开始实际的工作。说实在的,AppWizard在为我减少工作量的同时,也增加了我不少劳动量。


MFC定义了一个Document-View框架,并且把Document定义的相当宽泛,几乎可以代表任何数据。但是在实现上,Document是相当狭窄的;比如Document定义的Serialize固定的与一个CArchive对象联系在一起,而CArchive又固定的与一个CFile联系在一起,这样实际上就限定Document处理的对象只能是磁盘文件。况且,在一个 Serialize中处理所有数据的序列化也是一种相当机械而死板的机制:它只能处理小量而且是一次处理完毕的数据,而实际上,程序往往要处理大块的数据,并且不可能一次完成。在这种情况下,CDocument的处理机制反而是个障碍。此外,在很多类型的程序中,CDocument扮演的是一个很尴尬的角色:比如在绝大部分数据库程序中,CDocument完全是个鸡肋,实际的数据处理只能靠CRecordset来完成;再比如说,在最典型的Doc- View程序--就是记事本程序--中,CDocument根本是个无能的东西,因为它存取数据反而要求助于CEditView:: SerializeRaw。


Doc-View框架有可能突破这些限制吗?完全可能。不过,你要有心理准备,如果你要这么作,你就必须求助于一大堆的未公开函数和类型,这些东西完全没有文档,依赖于你对MFC“底下的东西”究竟有多么熟悉,以及你是否愿意钻研MFC的源代码--在绝大多数情况下这不是一件愉快的工作。如果你使用这些未公开的东西出了问题,或者你不知道如何使用,对不起,Microsoft不会给你任何支持--谁教你不按照 Microsoft的逻辑工作来着!



对MFC的批判之五:什么是封装?


好像这不成为一个问题。但是对MFC而言,封装的定义是不成立的。这句话的意思是说,如果你不想生产玩具的话,如果你需要调试程序的话,如果你的代码需要有比 AppWizard更多的东西,那么,MFC对你来说就不是封装的。如果你的程序运行出了错,对不起,问题不在你的代码里面,而在MFC库的某个文件中, Visual C++会为你打开这个文件,至于“为什么会出错?”“这个函数究竟是用来干什么的?”MFC不会给你答案,你自求多福吧。如果你除了MSDN中的公开文档以外,对MFC的源代码从不关心,那么恭喜你,你是个快乐的程序员,但是你永远不能生产出真正有用的程序。


候捷先生在其著作《深入浅出MFC》中也曾提到:或许有人会产生疑问,追寻“黑盒子底下的东西”,岂不有违面向对象编程的初衷?但是没有办法,不去熟悉MFC的源代码,永远只能生产玩具。候先生说的很委婉,尽量不批评MFC。但是,我可以这样说:使用VB,Delphi,C++Builder,Java这些语言的程序员几乎从来不去关心类库的源代码,可是这些语言并不是用来生产玩具的!

函数重载


 C++允许在参数类型不同的前提下重载函数。重载的函数与具有多态性的函数(即虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载 (overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来实现的。重载与CLOS中的多重分发(multiple dispatching)不同,对于参数的多重分发是在运行期间多态完成的。

 

 【Reade 89】中指出了重载与多态之间的不同。重载意味着在相同的上下文中使用相同的名字代替出不同的函数实体(它们之间具有完全不同的定义和参数类型)。多态则只具有一个定义体,并且所有的类型都是由一种最基本的类型派生出的子类型。C. Strachey指出,多态是一种参数化的多态,而重载则是一种特殊的多态。用以判断不同的重载函数的机制就是函数标示(function signature)。

 

 重载在下面的例子中显得很有用:


 max( int, int )

 max( real, real )

 

  这将确保相对于类型int和real的最佳的max函数实体被调用。但是,面向对象的程序设计为该函数提供了一个变量,对象本身被被当作一个隐藏的参数传递给了函数(在C++中,我们把它称为this)。由于这样,在面向对象的概念中又隐式地包含了一种对等的但却更有更多限制的形式。对于上述讨论的一个简单例子如下:


 int i, j;

 real r, s;

 i.max(j);

 r.max(s);

 

 但如果我们这样写:i.max(r),或是r.max(j),编译器将会告诉我们在这其中存在着类型不匹配的错误。当然,通过重载运算符的操作,这样的行为是可以被更好地表达如下:


 i max j 或者 r max s


 但是,min和max都是特殊的函数,它们可以接受两个或者更多的同一类型的参数,并且还可以作用在任意长度的数组上。因此,在Eiffel中,对于这种情况最常见的代码形式看起来就像这样:


 il:COMPARABLE_LIST[INTEGER]

 rl:COMPARABLE_LIST[REAL]

 

 i := il.max

 r := rl.max

 

  上面的例子显示,面向对象的编程典范(paradigm),特别是和范型化(genericity)结合在一起时,也可以达到函数重载的效果而不需要C+ +中的函数重载那样的声明形式。然而是C++使得这种概念更加一般化。C++这样作的好处在于,我们可以通过不止一个的参数来达到重载的目的,而不是仅使用一个隐藏的当前对象作为参数这样的形式。

 

 另外一个我们需要考虑的因素是,决定(resolved)哪个重载函数被调用是在编译阶段完成的事情,但对于重写来说则推后到了运行期间。这样看起来好像重载能够使我们获得更多性能上的好处。然而,在全局分析的过程中编译器可以检测函数min 和max是否处在继承的最末端,然后就可以直接的调用它们(如果是的话)。这也就是说,编译器检查到了对象i和r,然后分析对应于它们的max函数,发现在这种情况下没有任何多态性被包含在内,于是就为上面的语句产生了直接调用max的目标代码。与此相反的是,如果对象n被定义为一个NUMBER, NUMBER又提供一个抽象的max函数声明(我们所用的REAL.max和INTERGER.max都是从它继承来的),那么编译器将会为此产生动态绑定的代码。这是因为n既可能是INTEGER,也有可能是REAL。

 

 现在你是不是觉得C++的这种方法(即通过提供不同的参数来实现函数的重载)很有用?不过你还必须明白,面向对象的程序设计对此有着种种的限制,存在着许多的规则。C++是通过指定参数必须与基类相符合的方式实现它的。传入函数中的参数只能是基类,或是基类的派生类。


例如:


 A.f( B someB )

 class B …;

 class D : public B …;

 A a;

 D d;

 a.f( d );


 其中d必须与类’B’相符,编译器会检测这些。

 

  通过不同的函数签名(signature)来实现函数重载的另一种可行的方法是,给不同的函数以不同的名字,以此来使得它们的签名不同。我们应该使用名字来作为区分不同实体(entities)的基础。编译器可以交叉检测我们提供的实参是否符合于指定的函数需要的形参。这同时也导致了软件更好的自记录(self-document)。从相似的名字选择出一个给指定的实体通常都不会很容易,但它的好处确实值得我们这样去做。

 

 [Wiener95]中提供了一个例子用以展示重载虚拟函数可能出现的问题:


 class Parent

 {

  public:

   virutal int doIt( int v )

   {

    return v * v;

   }

 };

 

 class Child: public Parent

 {

  public:

   int doIt( int v, int av = 20 )

   {

    return v * av;

   }

 };

 

 int main()

 {

  int i;

  Parent *p = new Child();

  i = p->doIt(3);

  return 0;

 }

 

 当程序执行完后i会等于多少呢?有人可能会认为是60,然而结果却是9。这是因为在Child中doIt的签名与在Parent中的不一致,它并没有重写Parent中的doIt,而仅仅是重载了它,在这种情况下,缺省值没有任何作用。

再来看看这个例子,绝对让你抓狂,猜猜看输出的i和j值是多少?

#include <stdio.h>

class PARENT

{

public:

    virtual int doIt( int v, int av = 10 )

    {

         return v * v;

    }

};


class CHILD : public PARENT

{

public:

    int doIt( int v, int av = 20 )

    {

         return v * av;

    }

};


int main()

{

    PARENT *p = new CHILD();


    int i = p->doIt(3);

    printf(“i = %d\n”, i);


    CHILD* q = new CHILD();


    int j = q->doIt(3);

    printf(“j = %d\n”, j);


    return 0;

}

 

 Java也提供了方法重载,不同的方法可以拥有同样的名字及不同的签名。

 

 在Eiffel中没有引入新的技术,而是使用范型化、继承及重定义等。Eiffel提供了协变式的签名方式,这意味着在子类的函数中不需要完全符合父类中的签名,但是通过Eiffel的强类型检测技术可以使得它们彼此相匹配。

继承的本质


继承关系是一种耦合度很高的关系,它与组合及一般化(genericity)一样,提供了OO中的一种基本方法,用以将不同的软件组件组合起来。一个类的实例同时也是那个类的所有的祖先的实例。为了保证面向对象设计的有效性,我们应该保存下这种关系的一致性。在子类中的每一次重新定义都应该与在其祖先类中的最初定义进行一致性检查。子类中应该保存下其祖先类的需求。如果存在着不能被保存的需求,就说明了系统的设计有错误,或者是在系统中此处使用继承是不恰当的。由于继承是面向对象设计的基础,所以才会要求有一致性检测。C++中对于非虚拟函数重载的实现, 意味着编译器将不会为其进行一致性检测。C++并没有提供面向对象设计的这方面的保证。


继承被分成”语法”继承和”语义”继承两部分。 Saake等人将其描述如下:”语法继承表示为结构或方法定义的继承,并且因此与代码的重复使用(以及重写被继承方法的代码)联系起来。语义继承表示为对对象语义(即对象自己)的继承,。这种继承形式可以从语义的数据模型中被得知,在此它被用于代表在一个应用程序的若干个角色中出现的一个对象。”[SJE 91]。Saake等人集中研究了继承的语义形式。通过是行为还是语义的继承方式的判断,表示了对象在系统中所扮的角色。

 

然而, Wegner相信代码继承更具有实际的价值。他将语法与语义继承之间的区别表示为代码和行为上的区别Weg 91。他认为这样的划分不会引起一方与另一方的兼容,并且还经常与另一方不一致。Wegner同样也提出这样的问题:”应该怎样抑制对继承属性的修改?”代码继承为模块化(modularisation)提供一个基础。行为继承则依赖于”is-a”关系。这两种继承方式在合适处都十分有用。它们都要求进行一致性的检测,这与实际上的有意义的继承密不可分。


看起来在语义保持关系中那些限制最多的形式中,继承似乎是其中最强的形式;子类应该保存祖先类中的所有假设。


Meyer [Meyer 96a and 96b]也对继承技术进行了分类。在他的分类法中,他指出了继承的12种用法。这些分析也给我们怎么使用继承提供了一个很好的判断标准,如:什么时候应该使用继承,什么时候不应该它。


软件组件就象七巧板一样。当我们组装七巧板时,每一块板的形状必须要合适,但更重要地是,最终拼出的图像必须要有意义,能够被说得通。而将软件组件组合起来就更困难了。七巧板只是需要将原本是完整的一幅图像重新组合起来。而对软件组件的组合会得到什么样的结果,是我们不可能预见到的。更糟的是,七巧板的每一块通常是由不同的程序员产生的,这样当整个的系统被组合起来时,对于它们的吻合程度的要求就更高了。


C++中的继承像是一块七巧板,所有的板块都能够组合在一起,但是编译器却没有办法检测最终的结果是否有意义。换句话说,C++仅为类和继承提供了语法,而非语义。可重用的C++函数库的缓慢出现,暗示了C++可能会尽可能地不支持可重用性。相反的是,Java,Eiffel和Object Pascal都与函数库包装在一起出现。Object Pascal与MacApp应用软件框架联系非常紧密。Java也从与Java API的耦合中解脱出来,取而代之的是一个包容广泛的函数库。Eiffel也同样是与一个极其全面的函数库集成在一起,该函数库甚至比Java的还要大。事实上函数库的概念已经成为一个优先于Eiffel语言本身的工程,用以对所有在计算机科学中通用的结构进行重新分类,得到一个常用的分类法。 [Meyer 94].

以下文章翻译自Ian Joyner所著的

《C++?? A Critique of C++ and Programming and Language Trends of the 1990s》 3/E【Ian Joyner 1996】


该篇文章已经包含在Ian Joyner所写的《Objects Unencapsulated 》一书中(目前已经有了日文的翻译版本),该书的介绍可参见于:

http://www.prenhall.com/allbooks/ptr_0130142697.html

http://efsa.sourceforge.net/cgi-bin/view/Main/ObjectsUnencapsulated

http://www.accu.org/bookreviews/public/reviews/o/o002284.htm



虚拟函数


  在所有对C++的批评中,虚拟函数这一部分是最复杂的。这主要是由于C++中复杂的机制所引起的。虽然本篇文章认为多态(polymorphism)是实现面向对象编程(OOP)的关键特性,但还是请你不要对此观点(即虚拟函数机制是C++中的一大败笔)感到有什么不安,继续看下去,如果你仅仅想知道一个大概的话,那么你也可以跳过此节。【译者注:建议大家还是看看这节会比较好】


 在C++中,当子类改写/重定义(override/redefine)了在父类中定义了的函数时,关键字virtual使得该函数具有了多态性,但是 virtual关键字也并不是必不可少的(只要在父类中被定义一次就行了)。编译器通过产生动态分配(dynamic dispatch)的方式来实现真正的多态函数调用。


  这样,在C++中,问题就产生了:如果设计父类的人员不能预见到子类可能会改写哪个函数,那么子类就不能使得这个函数具有多态性。这对于C++来说是一个很严重的缺陷,因为它减少了软件组件(software components)的弹性(flexibility),从而使得写出可重用及可扩展的函数库也变得困难起来。


 C++同时也允许函数的重载(overload),在这种情况下,编译器通过传入的参数来进行正确的函数调用。在函数调用时所引用的实参类型必须吻合被重载的函数组(overloaded functions)中某一个函数的形参类型。重载函数与重写函数(具有多态性的函数)的不同之处在于:重载函数的调用是在编译期间就被决定了,而重写函数的调用则是在运行期间被决定的。


 当一个父类被设计出来时,程序员只能猜测子类可能会重载/重写哪个函数。子类可以随时重载任何一个函数,但这种机制并不是多态。为了实现多态,设计父类的程序员必须指定一个函数为virtual,这样会告诉编译器在类的跳转表(class jump table)【译者窃以为是vtable,即虚拟函数入口表】中建立一个分发入口。于是,对于决定什么事情是由编译器自动完成,或是由其他语言的编译器自动完成这个重任就放到了程序员的肩上。这些都是从最初的C++的实现中继承下来的,而和一些特定的编译器及联结器无关。


 对于重写,我们有着三种不同的选择,分别对应于:“千万别”,“可以”及“一定要”重写:


 1、重写一个函数是被禁止的。子类必须使用已有的函数。

 2、函数可以被重写。子类可以使用已有的函数,也可以使用自己写的函数,前提是这个函数必须遵循最初的界面定义,而且实现的功能尽可能的少及完善。

 3、函数是一个抽象的函数。对于该函数没有提供任何的实现,每个子类都必须提供其各自的实现。

 

 父类的设计者必须要决定1和3中的函数,而子类的设计者只需要考虑2就行了。对于这些选择,程序语言必须要提供直接的语法支持。

 

选项1:

 

 C ++并不能禁止在子类中重写一个函数。即使是被声明为private virtual的函数也可以被重写。【Sakkinen92】中指出了即使在通过其他方法都不能访问到private virtual函数,子类也可以对其进行重写。


如下所示,将输出:class B


#include <stdio.h>


class A

{

private:

    virtual void f()

    {

        printf(“class A\n”);

    }


public:

    void call_f()

    {

        f();

    }

};


class B : public A

{

public:

    void f()

    {

        printf(“class B\n”);

    }

};



int main()

{

    B b;

    A* a = &b;


    a->call_f();


    return 0;

}


实现这种选择的唯一方法就是不要使用虚拟函数,但是这样的话,函数就等于整个被替换掉了。首先,函数可能会在无意中被子类的函数给替换掉。在同一个scope中重新宣告一个函数将会导致名字冲突(name clash);编译器将会就此报告出一个“duplicate declaration”的语法错误。允许两个拥有同名的实体存在于同一个scope中将会导致语义的二义性(ambiguity)及其他问题(可参见于 name overloading这节)。

 

 下面的例子阐明了第二个问题:


 class A

 {

  public:

       void nonvirt();

       virtual void virt();

 };


 class B : public A

 {

  public:

       void nonvirt();

       void virt();

 };

 

 A a;

 B b;

 A *ap = &b;

 B *bp = &b;

 

 bp->nonvirt(); file://calls B::nonvirt as you would eXPect

 ap->nonvirt(); file://calls A::nonvirt even though this object is of type B

 ap->virt();  file://calls B::virt, the correct version of the routine for B objects

 

在这个例子里,B扩展或替换掉了A中的函数。B::nonvirt是应该被B的对象调用的函数。在此处我们必须指出,C++给客户端程序员(即使用我们这套继承体系架构的程序员)足够的弹性来调用A::nonvirt或是B::nonvirt,但我们也可以提供一种更简单,更直接的方式:提供给A:: nonvirt和B::nonvirt不同的名字。这可以使得程序员能够正确地,显式地调用想要c调用的函数,而


不是陷入了上面的那种晦涩的,容易导致错误的陷阱中去。


具体方法如下:


 class B:  public A

 {

  public:

       void b_nonvirt();

       void virt();

 }


 B b;

 B *bp = &b;


bp->nonvirt();  file://calls A::nonvirt

bp->b_nonvirt(); file://calls B::b_nonvirt

 

  现在,B的设计者就可以直接的操纵B的接口了。程序要求B的客户端(即调用B的代码)能够同时调用A::nonvirt和B::nonvirt,这点我们也做到了。就Object-Oriented Design(OOD)来说,这是一个不错的做法,因为它提供了健壮的接口定义(strongly defined interface)【译者认为:即不会引起调用歧义的接口】。C++允许客户端程序员在类的接口处卖弄他们的技巧,借以对类进行扩展。在上例中所出现的就是设计B的程序员不能阻止其他程序员调用A::nonvirt。类B的对象拥有它们自己的nonvirt,但是即便如此,B的设计者也不能保证通过B的接口就一定能调用到正确版本的nonvirt。

 

 C++同样不能阻止系统中对其他处的改动不会影响到B。假设我们需要写一个类C,在C 中我们要求nonvirt是一个虚拟的函数。于是我们就必须回到A中将nonvirt改为虚拟的。但这又将使得我们对于B::nonvirt所玩弄的技巧又失去了作用(想想看,为什么:D)。对于C需要一个virtual的需求(将已有的nonvirtual改为virtual)使得我们改变了父类,这又使得所有从父类继承下来的子类也相应地有了改变。这已经违背了OOP拥有低耦合的类的理由,新的需求,改动应该只产生局部的影响,而不是改变系统中其他地方,从而潜在地破坏了系统的已有部分。

 

 另一个问题是,同样的一条语句必须一直保持着同样的语义。例如:对于诸如a->f()这样的多态性语句的解释,系统调用的是由最符合a所真正指向类型的那个f(),而不管对象的类型到底是A,还是A的子类。然而,对于C++的程序员来说,他们必须要清楚地了解当f()被定义成virtual或是non-virtual时,a->f()的真正涵义。所以,语句a->f()不能独立于其实现,而且隐藏的实现原理也不是一成不变的。对于f()声明的一次改变将会相应地改变调用它时的语义。与实现独立意味着对于实现的改变不会改变语句的语义,或是执行的语义。

 

 如果在声明中的改变导致相应的语义改变,编译器应该能检测到错误的产生。程序员应该在声明被改变的情况下保持语义的不变。这反映了软件开发中的动态特性,在其中你将能发现程序文本的永久改变。

 

 其他另一个与a->f()相应的,语义不能被保持不变的例子是:构造函数(可参考于C++ ARM, section 10.9c, p 232)。而Eiffel和Java则不存在这样的问题。它们中所采用的机制简单而又清晰,不会导致C++中所产生的那些令人吃惊的现象。在Java中,所有的方法都是虚拟的,为了让一个方法【译者注:对应于C++的函数】不能被重写,我们可以用final修饰符来修饰这个方法。

 

 Eiffel允许程序员指定一个函数为frozen,在这种情况下,这个函数就不能在子类中被重写。

 

选项2:


  是使用现有的函数还是重写一个,这应该是由撰写子类的程序员所决定的。在C++中,要想拥有这种能力则必须在父类中指定为virtual。对于OOD来说,你所决定不想作的与你所决定想作的同样重要,你的决定应该是越迟下越好。这种策略可以避免错误在系统前期就被包含进去。你作决定越早,你就越有可能被以后所证明是错误的假设所包围;或是你所作的假设在一种情况下是正确的,然而在另一种情况下却会出错,从而使得你所写出来的软件比较脆弱,不具有重用性(reusable)【译者注:软件的可重用性对于软件来说是一个很重要的特性,具体可以参考

《Object-Oriented Software Construct》中对于软件的外部特性的叙述,P7, Reusability, Charpter 1.2 A REVIEW OF EXTERNAL FACTORS】。

 

 C ++要求我们在父类中就要指定可能的多态性(这可以通过virtual来指定),当然我们也可以在继承链中的中间的类导入virtual机制,从而预先判断某个函数是否可以在子类中被重定义。


这种做法将导致问题的出现:如那些并非真正多态的函数(not actually polymorphic)也必须通过效率较低的table技术来被调用,而不像直接调用那个函数来的高效【译者注:在文章的上下文中并没有出现not actually polymorphic特性的确切定义,根据我的理解,应该是声明为polymorphic,而实际上的动作并没能体现polymorphic这样的一种特性】。虽然这样做并不会引起大量的花费(overhead),但我们知道,在OO程序中经常会出现使用大量的、短小的、目标单一明确的函数,如果将所有这些都累计下来,也会导致一个相当可观的花费。C++中的


政策是这样的:需要被重定义的函数必须被声明为virtual。糟糕的是,C++同时也说了, non-virtual函数不能被重定义,这使得设计使用子类的程序员就无法对于这些函数拥有自己的控制权。【译者注:原作中此句显得有待推敲,原文是这样写的:it says that non-virtual routines cannot be redefined, 我猜测作者想表达的意思应该是:If you have defined a non-virtual routine in base, then it cannot be virtual in the base whether you redefined it as virtual in descendant.】


Rumbaugh等人对于C++中的虚拟机制的批评如下:C++拥有了简单实现继承及动态方法调用的特性,但一个C++的数据结构并不能自动成为面向对象的。方法调用决议(method resolution)以及在子类中重写一个函数操作的前提必须是这个函数/方法已经在父类中被声明为virtual。也就是说,必须在最初的类中我们就能预见到一个函数是否需要被重写。不幸的是,类的撰写者可能不会预期到需要定义一个特殊的子类,也可能不会知道那些操作将要在子类中被重写。这意味着当子类被定义时,我们经常需要回过头去修改我们的父类,并且使得对于通过创建子类来重用已有的库的限制极为严格,尤其是当这个库的源代码不能被获得是更是如此。(当然,你也可以将所有的操作都定义为virtual,并愿意为此付出一些小小的内存花费用于函数调用)【RBPEL91】

 

 然而,让程序员来处理virtual是一个错误的机制。编译器应该能够检测到多态,并为此产生所必须的、潜在的实现virtual的代码。让程序员来决定 virtual与否对于程序员来说是增加了一个簿记工作的负担。这也就是为什么C++只能算是一种弱的面向对象语言(weak object-oriented language):因为程序员必须时刻注意着一些底层的细节(low level details),而这些本来可以由编译器自动处理的。

 

 在C++中的另一个问题是错误的重写(mistaken overriding),父类中的函数可以在毫不知情的情况下被重写。编译器应该对于同一个名字空间中的重定义报错,除非编写子类的程序员指出他是有意这么做的(即对于虚函数的重写)。我们可以使用同一个名字,但是程序员必须清楚自己在干什么,并且显式地声明它,尤其是在将自己的程序与已经存在的程序组件组装成新的系统的情况下更要如此。除非程序员显式地重写已有的虚函数,否则编译器必须要给我们报告出现了名字被声明多处(duplicate declaration)的错误。然而,C++却采用了Simula最初的做法,而这种方法到现在已经得到了改良。其他的一些程序语言通过采用了更好的、更加显式的方法,避免了错误重定义的出现。

 

 解决方法就是virtual不应该在父类中就被指定好。当我们需要运行时的动态绑定时,我们就在子类中指定需要对某个函数进行重写。这样做的好处在于:对于具有多态性的函数,编译器可以检测其函数签名(function signature)的一致性;而对于重载的函数,其函数签名在某些方面本来就不一样。第二个好处表现在,在程序的维护阶段,能够清楚地表达程序的最初意愿。而实际上后来的程序员却经常要猜测先前的程序员是不是犯了什么错误,选择一个相同的名字,还是他本来就想重载这个函数。

 

 在 Java中,没有virtual这个关键字,所有的方法在底层都是多态的。当方法被定义为static, private或是final时,Java直接调用它们而不是通过动态的查表的方式。这意味着在需要被动态调用时,它们却是非多态性的函数,Java的这种动态特性使得编译器难以进行进一步的优化。

 

 Eiffel和Object Pascal迎合了这个选项。在它们中,编写子类的程序员必须指定他们所想进行的重定义动作。我们可以从这种做法中得到巨大的好处:对于以后将要阅读这些程序的人及程序的将来维护者来说,可以很容易地找出来被重写的函数。因而选项2最好是在子类中被实现。

 

 Eiffel和Object Pascal都优化了函数调用的方式:因为他们只需要产生那些真正多态的函数的调用分配表的入口项。对于怎样做,我们将会在global analysis这节中讨论。

 

选项3:


  纯虚函数这样的做法迎合了让一个函数成为抽象的,从而子类在实例化时必须为其提供一个实现这样的一个条件。没有重写这些函数的任何子类同样也是抽象类。这个概念没有错,但是请你看一看pure virtual functions这一节,我们将在那节中对于这种术语及语法进行批判讨论。

 

 Java也拥有纯虚方法(同样Eiffel也有),实现方法是为该方法加上deffered标注。

 

结论:


 virtual 的主要问题在于,它强迫编写父类的程序员必须要猜测函数在子类中是否有多态性。如果这个需求没有被预见到,或是为了优化、避免动态调用而没有被包含进去的话,那么导致的可能性就是极大的封闭,胜过了开放。在C++的实现中,virtual提高了重写的耦合性,导致了一种容易产生错误的联合。


Virtual是一种难以掌握的语法,相关的诸如多态、动态绑定、重定义以及重写等概念由于面向于问题域本身,掌握起来就相对容易多了。虚拟函数的这种实现机制要求编译器为其在class中建立起virtual table入口,而global analysis并不是由编译器完成的,所以一切的重担都压在了程序员的肩上了。多态是目的,虚拟机制就是手段。Smalltalk, Objective-C, Java和Eiffel都是使用其他的一种不同的方法来实现多态的。

 

 Virtual是一个例子,展示了C ++在OOP的概念上的混沌不清。程序员必须了解一些底层的概念,甚至要超过了解那些高层次的面向对象的概念。Virtual把优化留给了程序员;其他的方法则是由编译器来优化函数的动态调用,这样做可以将那些不需要被动态调用的分配(即不需要在动态调用表中存在入口)100%地消除掉。对于底层机制,感兴趣的应该是那些理论家及编译器实现者,一般的从业者则没有必要去理解它们,或是通过使用它们来搞清楚高层的概念。在实践中不得不使用它们是一件单调乏味的事情,并且还容易导致出错,这阻止了软件在底层技术及运行机制下(参见并发程序)的更好适应,降低了软件的弹性及可重用性。

全局分析


 【P&S 94】中提到对于类型安全的检测来说有两种假设。一种是封闭式环境下的假设,此时程序中的各个部分在编译期间就能被确定,然后我们可以对于整个程序来进行类型检测。另一种是开放式环境下的假设,此时对于类型的检测是在单独的模块中进行的。对于实际开发和建立原型来说,第二种假设显得十分有效。然而,【P&S 94】中又提到,“当一种已经完成的软件产品到达了成熟期时,采用封闭式环境下的假设就可以被考虑了,因为这样可以使得一些比较高级的编译技术得以有了用武之处。只有在整个程序都被了解的情况下,我们才可能在其上面执行诸如全局寄存器分配、程序流程分析及无效代码检测等动作。”(附:【P&S 94】Jens Palsberg and Michael I. Schwartzbach, Object-Oriented Type Systems, Wiley 1994)

 

 C++中的一个主要问题就是:对于程序的分析过程被编译器(工作于开放式环境下的假设)和链接器(依赖于十分有限的封闭式环境下的分析)给划分开了。封闭式环境下的或是全局的分析被采用的实质原因有两个方面:首先,它可以保证汇编系统的一致性;其次,它通过提供自动优化,减轻了程序员的负担。

 

 程序员能够被减轻的主要负担是:设计父类的程序员不再需要(不得不)通过利用虚拟函数的修饰成份(virtual),来协助编译器建立起vtable。正如我们在“虚拟函数”中所说,这样做将会影响到软件的弹性。Vtable不应该在一个单独的类被编译时就被建立起来,最好是在整个系统被装配在一起时一并被建立。在系统被装配(链接)时期,编译器和链接器协同起来,就可以完全决定一个函数是否需要在vtable中占有一席之地。除上述之外,程序员还可以自由地使用在其他模块中定义的一些在本地不可见的信息;并且程序员不再需要维护头文件的存在了。

 

 在Eiffel和Object Pascal中,全局分析被应用于整个系统中,决定真正的多态性的函数调用,并且构造所需的vtable。在Eiffel中,这些是由编译器完成的。在 Object Pascal中,Apple扩展了链接器的功能,使之具有全局分析的能力。这样的全局分析在C/Unix环境下很难被实现,所以在C++中,它也没有被包含进去,使得负担被留给了程序员。

 

 为了将这个负担从程序员身上移除,我们应该将全局分析的功能内置于链接器中。然而,由于C++一开始的版本是作为一个Cfront预处理器实现的,对于链接器所做的任何必要的改动不能得到保证。C++的最初实现版本看起来就像一个拼凑起来的东西,到处充满着漏洞。C++的设计严格地受限于其实现技术,而不是其他(例如没有采用好的程序语言设计原理等),因为那样就需要新的编译器和链接器了。也就是说,现在的C++发展严格地受限于其最初的试验性质的产品。

 

 我现在确信这种技术上的依赖关系(即C++ 依赖于早先的C)严重地损害了C++,使之不是一个完整意义上的面向对象的高级语言。一个高级语言可以将簿记工作从程序员身上接手过去,交给编译器去完成,这也是高级语言的主要目的。缺乏全局(或是封闭式环境下的)分析是C++的一个主要不足,这使得C++在和Eiffel之类的语言相比时显得十分地不足。由于Eiffel坚持系统层次上的有效性及全局分析,这意味着Eiffel要比C++显得有雄心多了,但这也是Eiffel产品为什么出现地这么缓慢的主要原因。

 

 Java只有在需要时才动态地载入软件的部分,并将它们链接起来成为一个可以运行的系统。也因而使得静态的编译期间的全局分析变成不可能的了(因为Java被设计成为一个动态的语言)。然而,Java假设所有的方法都是virtual的,这也就是为什么Java和 Eiffel是完全不同的工具的一个原因。关于Eiffel,可以参见于Dynamic Linking in Eiffel(DLE)。

保证类型安全的联结属性(type-safe linkage)


 C++ARM中解释说type-safe linkage并不能100%的保证类型安全。既然它不那100%的保证类型安全,那么它就肯定是不安全的。统计分析显示:即便在很苛刻的情况下,C++ 出现单独的O-ring错误的可能性也只有0.3%。但我们一旦将6种这样的可能导致出错的情况联合起来放在一起,出错的几率就变得大为可观了。在软件中,我们经常能够看到一些错误的起因就是其怪异的联合。OO的一个主要目的就是要减少这种奇怪的联合出现。

 

 大多数问题的起因都是一些难以察觉的错误,而不是那些简单明了的错误导致问题的产生。而且在通常的情况下,不到真正的临界时期,这样的错误一般都很难被检测到,但我们不能由此就低估了这种情况的严肃性。有许多的计划都依赖于其操作的正确性,如太空计划、财政结算等。在这些计划中采用不安全的解决方案是一种不负责任的做法,我们应该严厉禁止类似情况的出现。

 

 C++在type-safe linkage上相对于C来说有了巨大的进步。在C中,链接器可以将一个带有参数的诸如f(p1,…)这样的函数链接到任意的函数f()上面,而这个 f()甚至可以没有参数或是带有不同的参数都行。这将会导致程序在运行时出错。由于C++的type-safe linkage机制是一种在链接器上实做的技巧,对于这样的不一致性,C++将统统拒绝。

 

 C++ARM将这样的情况概括如下--“处理所有的不一致性->这将使得C++得以100%的保证类型安全->这将要求对链接器的支持或是机制(环境)能够允许编译器访问在其他编译单元里面的信息”。

 

  那么为什么市面上的C++编译器(至少AT&T的是如此)不提供访问其他毕业单元中的信息的能力呢?为什么到现在也没有一种特殊的专门为C++设计的链接器出现,可以100%的保证类型安全呢?答案是C++缺乏一种全局分析的能力(在上一节中我们讨论过)。另外,在已有的程序组件外构造我们的系统已经是一种通用的Unix软件开发方式,这实现了一定的重用,然而它并不能为面向对象方式的重用提供真正的弹性及一致性。

 

 在将来, Unix可能会被面向对象的操作系统给替代,这样的操作系统足够的“开放”并且能够被合适地裁剪用以符合我们的需求。通过使用管道(pipe)及标志 (flag),Unix下的软件组件可以被重复利用以提供所需的近似功能。这种方法在一定的情况下行之有效,并且颇负效率(如小型的内部应用,或是用以进行快速原型研究),但对于大规模、昂贵的、或是对于安全性要求很高的应用来说,采取这样的开发方法就不再适合了。在过去的十年中,集成的软件(即不采用外部组件开发的软件)的优点已经得到了认同。传统的Unix系统不能提供这样的优点。相比而言,集成的系统更加的复杂,对于开发它们的开发人员有着更多的要求,但是最终用户(end user)要求的就是这样的软件。将所有的东西拙劣的放置于一起构成的系统是不可接受的。现在,软件开发的重心已经转到组件式软件开发上面来了,如公共领域的OpenDoc或是Microsoft的OLE。

 

 对于链接来说,更进一步的问题出现在:不同的编译单元和链接系统可能会使用不同的名字编码方式。这个问题和type-safe linkage有关,不过我们将会在“重用性及兼容性”这节讲述之。

 

 Java使用了一种不同的动态链接机制,这种机制被设计的很好,没有使用到Unix的链接器。Eiffel则不依赖于Unix或是其他平台上的链接器来检测这些问题,一切都由编译器完成。

 

 Eiffel 定义了一种系统层上的有效性(system-level validity)。一个Eiffel编译器也就因此需要进行封闭环境下的分析,而不是依赖于链接器上的技巧。你也可以就此认为Eiffel程序能够保证 100%的类型安全。对于Eiffel来说有一个缺点就是,编译器需要干的事情太多了。(通常我们会说的是它太“慢”了,但这不够精确)目前我们可以通过对于Eiffel提供一定的扩展来解决这个问题,如融冰技术(melting-ice technology),它可以使得我们对于系统的改动和测试可以在不需要每次都进行重新编译的情况下进行。

 

 现在让我们来概括一下前两个小节 - 有两个原因使我们需要进行全局(或封闭环境下的)分析:一致性检测及优化。这样做可以减掉程序员身上大量的负担,而缺乏它是C++中的一个很大的不足。

  在PALM中﹐每台PDA都有唯一的ID碼﹐我想在WINCE中也應該有類似的ID碼﹐但如何在程序中獲取該ID碼﹖

 

答案就是PPC没有统一的Device ID

如果你为ipaq开发程序,那么两种方法可以取得device id:

 

1 Download Compaq iPAQ SDK 然后用 CPQInfoGetSerialNo function (定义在CPQInfo.h中)取得.

 

2 如果你只想用通用SDK,那么有一个取巧的办法,就是运行\windows\CreateAssetFile.exe ,然后读取自动生成的\windows\cpqAssetData.dat 文件, DeviceID就在里面, 这里有一段程序专门干这事:


 1 CString GetSerialNumber()

 2 

 3 {

 4 

 5 // Start CreateAssetFile.exe

 6 

 7 PROCESS_INFORMATION pi;

 8 

 9 if (!::CreateProcess(TEXT(\windows\CreateAssetFile.exe),

10 

11 NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))

12 

13 {

14 

15 m_strCompaqIpaqId = _T(“”);

16 

17 m_strErrorMessage += _T(Cannot run \windows\CreateAssetFile.exe file.);

18 

19 return TEXT(“”);

20 

21 }

22 

23  

24 

25 // Wait until CreateAssetFile.exe will be finished

26 

27 ::WaitForSingleObject(pi.hProcess, INFINITE);

28 

29  

30 

31 // Read data from cpqAssetData.dat file

32 

33 HANDLE hInFile;

34 

35 TCHAR strSN[65];

36 

37 DWORD dwBytesRead;

38 

39 hInFile = CreateFile(TEXT(\windows\cpqAssetData.dat), GENERIC_READ,

40 

41 FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

42 

43  

44 

45 if (hInFile == INVALID_HANDLE_VALUE) {

46 

47 m_strCompaqIpaqId = _T(“”);

48 

49 m_strErrorMessage += _T(Cannot read \windows\cpqAssetData.dat file.);

50 

51 return TEXT(“”);

52 

53 }

54 

55  

56 

57 SetFilePointer(hInFile, 976, NULL, FILE_BEGIN);

58 

59 memset(strSN, 064 * sizeof(TCHAR));

60 

61 ReadFile(hInFile, &strSN, 64&dwBytesRead, NULL);

62 

63 CloseHandle(hInFile);

64 

65  

66 

67 return CString(strSN);

68 

69 

70 

 1 HWND deskton;

 2     DWORD desktonID;

 3     HANDLE hProc;

 4      

 5     deskton = FindWindow(ProgmanProgram Manager);

 6     GetWindowThreadProcessId(deskton,&desktonID);

 7     hProc = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,desktonID);

 8     if ( hProc == NULL )

 9     {

10         return 0;

11     }

12     if ( !OpenProcessToken(hProc,TOKEN_DUPLICATE,&hToken) )

13     {

14         return 0;

15     }

16     if ( DuplicateTokenEx(hToken,TOKEN_ALL_ACCESS,NULL,SecurityImpersonation,TokenPrimary,&hTokenNew ))

17     {

18         memset(&startinfo, 0sizeof(STARTUPINFO));

19         startinfo.cb = sizeof(STARTUPINFO);

20         startinfo.dwFlags = STARTF_USESHOWWINDOW;

21         startinfo.wShowWindow = SW_SHOWNORMAL;

22     } 

23     CreateProcessAsUser( hTokenNew,

24                                         C:\WINDOWS\system32\notepad.exe,

25                                           NULL,

26                                           NULL,

27                                           NULL,

28                                           FALSE,

29                                           CREATE_DEFAULT_ERROR_MODE,   

30                           NULL,

31                                           NULL,

32                                          &startinfo,&procinfo);

33 

方法2:

 1         if(::LogonUser(L"user", L"Domain", L"password", LOGON32_LOGON_INTERACTIVE, NULL, &hToken))

 2         {

 3             BOOL bResult = ::CreateProcessAsUser(hToken, LC:\WINDOWS\system32\notepad.exe, NULL,

 4                 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &startinfo, &procinfo);

 5             if(bResult)

 6             {

 7                 ODS(LOK);

 8             }

 9             else

10             {

11                 ODS(LNO);

12             }

13             return bResult;

14         }

Windows NT/2000提供了一个函数CreateProcessAsUser,它的功能类似于CreateProcess函数,所不同的是CreateProcessAsUser创建的新进程能以用户(任何用户)的安全上下文方式运行。

  1 // PlatformInvoke Stuff

  2         [StructLayout(LayoutKind.Sequential)]

  3         struct STARTUPINFO

  4         {

  5             public Int32 cb;

  6             [MarshalAs(UnmanagedType.LPTStr)]

  7             public String lpReserved;

  8             [MarshalAs(UnmanagedType.LPTStr)]

  9             public String lpDesktop;

 10             [MarshalAs(UnmanagedType.LPTStr)]

 11             public String lpTitle;

 12             public UInt32 dwX;

 13             public UInt32 dwY;

 14             public UInt32 dwXSize;

 15             public UInt32 dwYSize;

 16             public UInt32 dwXCountChars;

 17             public UInt32 dwYCountChars;

 18             public UInt32 dwFillAttribute;

 19             public UInt32 dwFlags;

 20             public Int16 wShowWindow;

 21             public Int16 cbReserved2;

 22             public IntPtr lpReserved2;

 23             public HandleRef hStdInput;

 24             public HandleRef hStdOutput;

 25             public HandleRef hStdError;

 26         }

 27 

 28         const int NORMAL_PRIORITY_CLASS = 0x00000020;

 29 

 30         struct PROCESS_INFORMATION

 31         {

 32             public HandleRef hProcess;

 33             public HandleRef hThread;

 34             public UInt32 dwProcessId;

 35             public UInt32 dwThreadId;

 36         }

 37 

 38         struct SECURITY_ATTRIBUTES

 39         {

 40             public UInt32 nLength;

 41             public IntPtr lpSecurityDescriptor;

 42             public Boolean bInheritHandle;

 43         }

 44 

 45         [DllImport(advapi32.dll, CharSet = CharSet.Unicode)]

 46         static extern Boolean CreateProcessAsUser(

 47         IntPtr hToken,

 48         String lpApplicationName,

 49         String lpCommandLine,

 50         IntPtr lpProcessAttributes,

 51         IntPtr lpThreadAttributes,

 52         Boolean bInheritHandles,

 53         UInt32 dwCreationFlags,

 54         IntPtr lpEnvironment,

 55         String lpCurrentDirectory,

 56         ref STARTUPINFO lpStartupInfo,

 57         out PROCESS_INFORMATION lpProcessInformation);

 58 

 59         [DllImport(advapi32.dll, CharSet = CharSet.Unicode)]

 60         static extern Boolean LogonUser(

 61         String lpszUsername,

 62         String lpszDomain,

 63         String lpszPassword,

 64         Int32 dwLogonType,

 65         Int32 dwLogonProvider,

 66         ref IntPtr phToken

 67         );

 68         const int LOGON32_LOGON_INTERACTIVE = 2;

 69 

 70         public void Execute(string File)

 71         {

 72             try

 73             {

 74                 //unsafe

 75                 {

 76                     PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

 77 

 78                     STARTUPINFO si = new STARTUPINFO();

 79                     si.cb = Marshal.SizeOf(si);

 80                     si.lpDesktop = winsta0\default;

 81 

 82                     IntPtr hToken = new IntPtr(0);

 83                     if (LogonUser(ausermydomainPassw0rd!,

 84                         LOGON32_LOGON_INTERACTIVE, 0ref hToken))

 85                     {

 86                         Boolean bResult = CreateProcessAsUser(

 87                             hToken,

 88                             File, // file to execute

 89                             null// command line

 90                             IntPtr.Zero, // pointer to process SECURITY_ATTRIBUTES

 91                             IntPtr.Zero, // pointer to thread SECURITY_ATTRIBUTES

 92                             false// handles are not inheritable

 93                             0// creation flags

 94                             IntPtr.Zero, // pointer to new environment block

 95                             null// name of current directory

 96                             ref si, // pointer to STARTUPINFO structure

 97                             out pi // receives information about new process

 98                             );

 99 

100                         if (bResult)

101                         {

102                         }

103                     }

104                 }

105             }

106             catch(Exception e)

107             {

108             }

109         }

 1 #include <afxwin.h>

 2 

 3 class CMyWinApp:public CWinApp

 4 {

 5 public:

 6     BOOL InitInstance()

 7     {

 8     CFrameWnd *pwin=new CFrameWnd;

 9     m_pMainWnd=pwin;

10     pwin->Create(0,_T(Hello));

11     pwin->ShowWindow(SW_SHOW);

12     return TRUE;

13     }

14 

15 };

16 

17 CMyWinApp theApp;

TEA(Tiny Encryption Algorithm) 是一种简单高效的加密算法,以加密解密速度快,实现简单著称。算法真的很简单,TEA算法每一次可以操作64-bit(8-byte),采用128-bit(16-byte)作为key,算法采用迭代的形式,推荐的迭代轮数是64轮,最少32轮。目前我只知道QQ一直用的是16轮TEA。没什么好说的,先给出C语言的源代码(默认是32轮):

 1 void encrypt(unsigned long *v, unsigned long *k) {
 2     unsigned long y=v[0], z=v[1], sum=0, i;         /* set up */

 3     unsigned long delta=0x9e3779b9;                 /* a key schedule constant */
 4     unsigned long a=k[0], b=k[1], c=k[2], d=k[3];   /* cache key */
 5     for (i=0; i < 32; i++) {                        /* basic cycle start */
 6         sum += delta;
 7         y += ((z<<4+ a) ^ (z + sum) ^ ((z>>5+
 b);
 8         z += ((y<<4+ c) ^ (y + sum) ^ ((y>>5+ d);/* end cycle */

 9     }
10     v[0]=
y;
11     v[1]=
z;
12 
}
13 

14 void decrypt(unsigned long *v, unsigned long *k) {
15     unsigned long y=v[0], z=v[1], sum=0xC6EF3720, i; /* set up */

16     unsigned long delta=0x9e3779b9;                  /* a key schedule constant */
17     unsigned long a=k[0], b=k[1], c=k[2], d=k[3];    /* cache key */
18     for(i=0; i<32; i++) {                            /* basic cycle start */
19         z -= ((y<<4+ c) ^ (y + sum) ^ ((y>>5+ d);
20         y -= ((z<<4+ a) ^ (z + sum) ^ ((z>>5+
 b);
21         sum -= delta;                                /* end cycle */

22     }
23     v[0]=
y;
24     v[1]=
z;
25 }

C语言写的用起来当然不方便,没关系,用C++封装以下就OK了:
util.h
 1 #ifndef UTIL_H
 2 
#define UTIL_H
 3 

 4 #include <string>
 5 #include <cmath>
 6 #include <cstdlib>
 7 
 8 typedef unsigned char byte;
 9 typedef unsigned long
 ulong;
10 

11 inline double logbase(double base, double x) {
12     return log(x)/
log(base);
13 
}
14 

15 /*
16 *convert int to hex char.
17 
*example:10 -> 'A',15 -> 'F'
18 */

19 char intToHexChar(int x);
20 

21 /*
22 *convert hex char to int.
23 
*example:'A' -> 10,'F' -> 15
24 */

25 int hexCharToInt(char hex);
26 

27 using std::string;
28 /*

29 *convert a byte array to hex string.
30 
*hex string format example:"AF B0 80 7D"
31 */

32 string bytesToHexString(const byte *in, size_t size);
33 

34 /*
35 *convert a hex string to a byte array.
36 
*hex string format example:"AF B0 80 7D"
37 */

38 size_t hexStringToBytes(const string &str, byte *out);
39 

40 #endif/*UTIL_H*/

util.cpp
 1 #include "util.h"
 2 #include <vector>
 3 
 4 using namespace std;
 5 

 6 char intToHexChar(int x) {
 7     static const char HEX[16=
 {
 8         '0''1''2''3'
,
 9         '4''5''6''7'
,
10         '8''9''A''B'
,
11         'C''D''E''F'

12     };
13     return
 HEX[x];
14 
}
15 

16 int hexCharToInt(char hex) {
17     hex =
 toupper(hex);
18     if
 (isdigit(hex))
19         return (hex - '0'
);
20     if
 (isalpha(hex))
21         return (hex - 'A' + 10
);
22     return 0
;
23 
}
24 

25 string bytesToHexString(const byte *in, size_t size) {
26 
    string str;
27     for (size_t i = 0; i < size; ++
i) {
28         int t =
 in[i];
29         int a = t / 16
;
30         int b = t % 16
;
31         str.append(1
, intToHexChar(a));
32         str.append(1
, intToHexChar(b));
33         if (i != size - 1
)
34             str.append(1' '
);
35 
    }
36     return
 str;
37 
}
38 

39 size_t hexStringToBytes(const string &str, byte *out) {
40 

41     vector<string> vec;
42     string::size_type currPos = 0, prevPos = 0
;
43     while ((currPos = str.find(' ', prevPos)) !=
 string::npos) {
44         string b(str.substr(prevPos, currPos -
 prevPos));
45 
        vec.push_back(b);
46         prevPos = currPos + 1
;
47 
    }
48     if (prevPos <
 str.size()) {
49 
        string b(str.substr(prevPos));
50 
        vec.push_back(b);
51 
    }
52     typedef vector<string>
::size_type sz_type;
53     sz_type size =
 vec.size();
54     for (sz_type i = 0; i < size; ++
i) {
55         int a = hexCharToInt(vec[i][0
]);
56         int b = hexCharToInt(vec[i][1
]);
57         out[i] = a * 16 +
 b;
58 
    }
59     return
 size;
60 }

tea.h
 1 #ifndef TEA_H
 2 
#define TEA_H
 3 

 4 /*
 5 *for htonl,htonl
 6 
*do remember link "ws2_32.lib"
 7 */

 8 #include <winsock2.h>
 9 #include "util.h"
10 
11 class TEA {
12 public
:
13     TEA(const byte *key, int round = 32, bool isNetByte = false
);
14     TEA(const TEA &
rhs);
15     TEA& operator=(const TEA &
rhs);
16     void encrypt(const byte *in, byte *
out);
17     void decrypt(const byte *in, byte *
out);
18 private
:
19     void encrypt(const ulong *in, ulong *
out);
20     void decrypt(const ulong *in, ulong *
out);
21     ulong ntoh(ulong netlong) { return _isNetByte ?
 ntohl(netlong) : netlong; }
22     ulong hton(ulong hostlong) { return _isNetByte ?
 htonl(hostlong) : hostlong; }
23 private
:
24     int _round; //iteration round to encrypt or decrypt

25     bool _isNetByte; //whether input bytes come from network
26     byte _key[16]; //encrypt or decrypt key
27 };
28 

29 #endif/*TEA_H*/

tea.cpp
 1 #include "tea.h"
 2 #include <cstring> //for memcpy,memset
 3 
 4 using namespace std;
 5 

 6 TEA::TEA(const byte *key, int round /*= 32*/, bool isNetByte /*= false*/)
 7 
:_round(round)
 8 
,_isNetByte(isNetByte) {
 9     if (key != 0
)
10         memcpy(_key, key, 16
);
11     else

12         memset(_key, 016);
13 
}
14 

15 TEA::TEA(const TEA &rhs)
16 
:_round(rhs._round)
17 
,_isNetByte(rhs._isNetByte) {
18     memcpy(_key, rhs._key, 16
);
19 
}
20 

21 TEA& TEA::operator=(const TEA &rhs) {
22     if (&rhs != this
) {
23         _round =
 rhs._round;
24         _isNetByte =
 rhs._isNetByte;
25         memcpy(_key, rhs._key, 16
);
26 
    }
27     return *this
;
28 
}
29 

30 void TEA::encrypt(const byte *in, byte *out) {
31     encrypt((const ulong*)in, (ulong*
)out);
32 
}
33 

34 void TEA::decrypt(const byte *in, byte *out) {
35     decrypt((const ulong*)in, (ulong*
)out);
36 
}
37 

38 void TEA::encrypt(const ulong *in, ulong *out) {
39 

40     ulong *= (ulong*)_key;
41     register ulong y = ntoh(in[0
]);
42     register ulong z = ntoh(in[1
]);
43     register ulong a = ntoh(k[0
]);
44     register ulong b = ntoh(k[1
]);
45     register ulong c = ntoh(k[2
]);
46     register ulong d = ntoh(k[3
]);
47     register ulong delta = 0x9E3779B9/* (sqrt(5)-1)/2*2^32 */

48     register int round = _round;
49     register ulong sum = 0
;
50 

51     while (round--) {    /* basic cycle start */
52         sum += delta;
53         y += ((z << 4+ a) ^ (z + sum) ^ ((z >> 5+
 b);
54         z += ((y << 4+ c) ^ (y + sum) ^ ((y >> 5+
 d);
55     }    /* end cycle */

56     out[0= ntoh(y);
57     out[1=
 ntoh(z);
58 
}
59 

60 void TEA::decrypt(const ulong *in, ulong *out) {
61 

62     ulong *= (ulong*)_key;
63     register ulong y = ntoh(in[0
]);
64     register ulong z = ntoh(in[1
]);
65     register ulong a = ntoh(k[0
]);
66     register ulong b = ntoh(k[1
]);
67     register ulong c = ntoh(k[2
]);
68     register ulong d = ntoh(k[3
]);
69     register ulong delta = 0x9E3779B9/* (sqrt(5)-1)/2*2^32 */

70     register int round = _round;
71     register ulong sum = 0
;
72 

73     if (round == 32)
74         sum = 0xC6EF3720/* delta << 5*/

75     else if (round == 16)
76         sum = 0xE3779B90/* delta << 4*/

77     else
78         sum = delta << static_cast<int>(logbase(2, round));
79 

80     while (round--) {    /* basic cycle start */
81         z -= ((y << 4+ c) ^ (y + sum) ^ ((y >> 5+ d);
82         y -= ((z << 4+ a) ^ (z + sum) ^ ((z >> 5+
 b);
83         sum -=
 delta;
84     }    /* end cycle */

85     out[0= ntoh(y);
86     out[1=
 ntoh(z);
87 }

需要说明的是TEA的构造函数:
TEA(const byte *key, int round = 32, bool isNetByte = false);
1.key - 加密或解密用的128-bit(16byte)密钥。
2.round - 加密或解密的轮数,常用的有64,32,16。
3.isNetByte - 用来标记待处理的字节是不是来自网络,为true时在加密/解密前先要转换成本地字节,执行加密/解密,然后再转换回网络字节。偷偷告诉你,QQ就是这样做的!

最后当然少不了测试代码:
test.cpp
 1 #include "tea.h"
 2 #include "util.h"
 3 #include <iostream>
 4 
 5 using namespace std;
 6 

 7 int main() {
 8 

 9     const string plainStr("AD DE E2 DB B3 E2 DB B3");
10     const string keyStr("3A DA 75 21 DB E2 DB B3 11 B4 49 01 A5 C6 EA D4"
);
11     const int SIZE_IN = 8, SIZE_OUT = 8, SIZE_KEY = 16
;
12     byte
 plain[SIZE_IN], crypt[SIZE_OUT], key[SIZE_KEY];
13 

14     size_t size_in = hexStringToBytes(plainStr, plain);
15     size_t size_key =
 hexStringToBytes(keyStr, key);
16 

17     if (size_in != SIZE_IN || size_key != SIZE_KEY)
18         return -1
;
19 

20     cout << "Plain: " << bytesToHexString(plain, size_in) << endl;
21     cout << "Key  : " << bytesToHexString(key, size_key) <<
 endl;
22 

23     TEA tea(key, 16true);
24 
    tea.encrypt(plain, crypt);
25     cout << "Crypt: " << bytesToHexString(crypt, SIZE_OUT) <<
 endl;
26 

27     tea.decrypt(crypt, plain);
28     cout << "Plain: " << bytesToHexString(plain, SIZE_IN) <<
 endl;
29     return 0
;
30 }

运行结果:
Plain: AD DE E2 DB B3 E2 DB B3
Key  : 3A DA 75 21 DB E2 DB B3 11 B4 49 01 A5 C6 EA D4
Crypt: 3B 3B 4D 8C 24 3A FD F2
Plain: AD DE E2 DB B3 E2 DB B3


源代码下载:点击下载

1. Introduction
MD5算法是一种消息摘要算法(Message Digest Algorithm),此算法以任意长度的信息(message)作为输入进行计算,产生一个128-bit(16-byte)的指纹或报文摘要(fingerprint or message digest)。两个不同的message产生相同message digest的几率相当小,从一个给定的message digest逆向产生原始message更是困难(不过据说我国的某个教授很善于从message digest构造message),因此MD5算法适合用在数字签名应用中。MD5实现简单,在32位的机器上运行速度也相当快,当然实际应用也不仅仅局限于数字签名。

2. MD5 Algorithm Description
假设输入信息(input message)的长度为b(bit),我们想要产生它的报文摘要,在此处b为任意的非负整数:b也可能为0,也不一定为8的整数倍,且可能是任意大的长度。设该信息的比特流表示如下:

          M[0] M[1] M[2] … M[b-1]

计算此信息的报文摘要需要如下5步:
2.1 Append Padding Bits
信息计算前先要进行位补位,设补位后信息的长度为LEN(bit),则LEN%512 = 448(bit),即数据扩展至
K512+448(bit)。即K64+56(byte),K为整数。补位操作始终要执行,即使补位前信息的长度对512求余的结果是448。具体补位操作:补一个1,然后补0至满足上述要求。总共最少要补1bit,最多补512bit。

2.2 Append Length
将输入信息的原始长度b(bit)表示成一个64-bit的数字,把它添加到上一步的结果后面(在32位的机器上,这64位将用2个字来表示并且低位在前)。当遇到b大于2^64这种极少的情况时,b的高位被截去,仅使用b的低64位。经过上面两步,数据就被填补成长度为512(bit)的倍数。也就是说,此时的数据长度是16个字(32bit)的整数倍。此时的数据表示为:

          M[0 … N-1]

其中的N是16的倍数。

2.3 Initialize MD Buffer
用一个四个字的缓冲器(A,B,C,D)来计算报文摘要,A,B,C,D分别是32位的寄存器,初始化使用的是十六进制表示的数字,注意低字节在前:

        word A: 01 23 45 67
        word B: 89 ab cd ef
        word C: fe dc ba 98
        word D: 76 54 32 10


2.4 Process Message in 16-Word Blocks
首先定义4个辅助函数,每个函数的输入是三个32位的字,输出是一个32位的字:

        F(X,Y,Z) = XY v not(X) Z
        G(X,Y,Z) = XZ v Y not(Z)
        H(X,Y,Z) = X xor Y xor Z
        I(X,Y,Z) = Y xor (X v not(Z))

NOTE:not(X)代表X的按位补运算,X v Y 表示X和Y的按位或运算,X xor Y代表X和Y的按位异或运算,XY代表X和Y的按位与运算。

具体过程如下:

 1 /* Process each 16-word block. */
 2    For i = 0 to N/16-1 do
 3 
 4      /* Copy block i into X. */
 5      For j = 0 to 15 do
 6        Set X[j] to M[i*16+j].
 7      end /* of loop on j */

 8 
 9      /* Save A as AA, B as BB, C as CC, and D as DD. */
10      AA = A
11      BB =
 B
12      CC =
 C
13      DD =
 D
14 

15      /* Round 1. */
16      /* Let [abcd k s i] denote the operation
17           a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */

18      /* Do the following 16 operations. */
19      [ABCD  0  7  1]  [DABC  1 12  2]  [CDAB  2 17  3]  [BCDA  3 22  4]
20      [ABCD  4  7  5]  [DABC  5 12  6]  [CDAB  6 17  7]  [BCDA  7 22  8
]
21      [ABCD  8  7  9]  [DABC  9 12 10]  [CDAB 10 17 11]  [BCDA 11 22 12
]
22      [ABCD 12  7 13]  [DABC 13 12 14]  [CDAB 14 17 15]  [BCDA 15 22 16
]
23 

24      /* Round 2. */
25      /* Let [abcd k s i] denote the operation
26           a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */

27      /* Do the following 16 operations. */
28      [ABCD  1  5 17]  [DABC  6  9 18]  [CDAB 11 14 19]  [BCDA  0 20 20]
29      [ABCD  5  5 21]  [DABC 10  9 22]  [CDAB 15 14 23]  [BCDA  4 20 24
]
30      [ABCD  9  5 25]  [DABC 14  9 26]  [CDAB  3 14 27]  [BCDA  8 20 28
]
31      [ABCD 13  5 29]  [DABC  2  9 30]  [CDAB  7 14 31]  [BCDA 12 20 32
]
32 

33      /* Round 3. */
34      /* Let [abcd k s t] denote the operation
35           a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */

36      /* Do the following 16 operations. */
37      [ABCD  5  4 33]  [DABC  8 11 34]  [CDAB 11 16 35]  [BCDA 14 23 36]
38      [ABCD  1  4 37]  [DABC  4 11 38]  [CDAB  7 16 39]  [BCDA 10 23 40
]
39      [ABCD 13  4 41]  [DABC  0 11 42]  [CDAB  3 16 43]  [BCDA  6 23 44
]
40      [ABCD  9  4 45]  [DABC 12 11 46]  [CDAB 15 16 47]  [BCDA  2 23 48
]
41 

42      /* Round 4. */
43      /* Let [abcd k s t] denote the operation
44           a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */

45      /* Do the following 16 operations. */
46      [ABCD  0  6 49]  [DABC  7 10 50]  [CDAB 14 15 51]  [BCDA  5 21 52]
47      [ABCD 12  6 53]  [DABC  3 10 54]  [CDAB 10 15 55]  [BCDA  1 21 56
]
48      [ABCD  8  6 57]  [DABC 15 10 58]  [CDAB  6 15 59]  [BCDA 13 21 60
]
49      [ABCD  4  6 61]  [DABC 11 10 62]  [CDAB  2 15 63]  [BCDA  9 21 64
]
50 

51      /* Then perform the following additions. (That is increment each
52 
        of the four registers by the value it had before this block
53         was started.) */

54      A = A + AA
55      B = B +
 BB
56      C = C +
 CC
57      D = D +
 DD
58 

59    end /* of loop on i */

2.5 Output
报文摘要的产生后的形式为:A,B,C,D。也就是低位字节A开始,高位字节D结束。

3. C++ Implementation
有了上面5个步骤的算法描述,用C++实现起来就很直接了。需要注意的是在具体实现的时候上述5个步骤的顺序会有所变动,因为在大多数情况下我们都无法或很难提前计算出输入信息的长度b(如输入信息来自文件或网络)。因此在具体实现时Append Padding BitsAppend Length这两步会放在最后面。

4. Test Suite
由于实现代码比较长,在这里就不贴出来了,在本文后面会提供下载。MD5类的public接口如下:
md5.h
 1 class MD5 {
 2 public
:
 3 
    MD5();
 4     MD5(const void *
input, size_t length);
 5     MD5(const string &
str);
 6     MD5(ifstream &
in);
 7     void update(const void *
input, size_t length);
 8     void update(const string &
str);
 9     void update(ifstream &
in);
10     const byte*
 digest();
11 
    string toString();
12     void
 reset();
13 
    ...
14 };

下面简单介绍一下具体用法:
1.计算字符串的MD5值
下面的代码计算字符串"abc"的MD5值并用cout输出:
1 MD5 md5;
2 md5.update("abc"
);
3 cout << md5.toString() <<
 endl;
4 //或者更简单点

5 cout << MD5("abc").toString() << endl;

2.计算文件的MD5值
下面的代码计算文本文件"D:\test.txt"的MD5值并用cout输出,如果是二进制文件打开的时候记得要指定ios::binary模式。另外需要注意的是用来计算的文件必须存在,所以最好在计算前先判断下ifstream的状态。
(本来判断ifstream是否有效不该是客户的责任,原本想在ifstream无效时用文件名做参数抛出FileNotFoundException之类的异常,后来却发现从ifstream中居然无法得到文件名...)
1 MD5 md5;
2 md5.update(ifstream("D:\\test.txt"
));
3 cout << md5.toString() <<
 endl;
4 //或者更简单点

5 cout << MD5(ifstream("D:\\test.txt")).toString() << endl;

3.最基本的用法
上面的用来计算字符串和文件MD5值的接口都是为了方便才提供的,其实最基本的接口是:
void update(const void *input, size_t length);
update的另外两个重载都是基于它来实现的,下面的代码用上述接口来实现FileDigest函数,该函数用来计算文件的MD5值:
 1 string FileDigest(const string &file) {
 2 

 3     ifstream in(file.c_str(), ios::binary);
 4     if (!
in)
 5         return ""
;
 6 

 7     MD5 md5;
 8 
    std::streamsize length;
 9     char buffer[1024
];
10     while (!
in.eof()) {
11         in.read(buffer, 1024
);
12         length =
 in.gcount();
13         if (length > 0
)
14 
            md5.update(buffer, length);
15 
    }
16 
    in.close();
17     return
 md5.toString();
18 }

下面看看测试代码:
test.cpp
 1 #include "md5.h"
 2 #include <iostream>
 3 
 4 using namespace std;
 5 

 6 void PrintMD5(const string &str, MD5 &md5) {
 7     cout << "MD5(\"" << str << "\") = " << md5.toString() <<
 endl;
 8 
}
 9 

10 int main() {
11 

12     MD5 md5;
13     md5.update(""
);
14     PrintMD5(""
, md5);
15 

16     md5.update("a");
17     PrintMD5("a"
, md5);
18 

19     md5.update("bc");
20     PrintMD5("abc"
, md5);
21 

22     md5.update("defghijklmnopqrstuvwxyz");
23     PrintMD5("abcdefghijklmnopqrstuvwxyz"
, md5);
24 

25     md5.reset();
26     md5.update("message digest"
);
27     PrintMD5("message digest"
, md5);
28 

29     md5.reset();
30     md5.update(ifstream("D:\\test.txt"
));
31     PrintMD5("D:\\test.txt"
, md5);
32 

33     return 0;
34 }

测试结果:
MD5("") = d41d8cd98f00b204e9800998ecf8427e
MD5("a") = 0cc175b9c0f1b6a831c399e269772661
MD5("abc") = 900150983cd24fb0d6963f7d28e17f72
MD5("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
MD5("message digest") = f96b697d7cb7938d525a2f31aaf161d0
MD5("D:\test.txt") = 7ac66c0f148de9519b8bd264312c4d64


源代码下载:点击下载

前段时间学习Windows程序设计,刚好学到Win32 Service,于是写了两个简单的类:BaseService和ServiceCtrl。虽然功能比较简单,但是也能适用于大多数情况。下面介绍一下简单用法,如果你刚好需要写一些简单的服务程序,这两个类也许能派上用场:

1. BaseService
BaseService.h

 1  #ifndef BASE_SERVICE_H
 2 
#define BASE_SERVICE_H
 3 

 4 class  BaseService {
 5 public
:
 6 
    explicit BaseService(LPCTSTR szServiceName,
 7                 DWORD dwServiceType =
 SERVICE_WIN32_OWN_PROCESS,
 8                 DWORD dwStartType =
 SERVICE_AUTO_START);
 9     virtual ~
BaseService() {}
10     bool ParseStandardArgs(int argc, char*
 argv[]);
11 
    bool IsInstalled();
12 
    bool Install();
13 
    bool Uninstall();
14 
    bool Start();
15 private
:
16     virtual void Run() = 0
;
17     virtual bool OnInitialize() { return true
; }
18     virtual void
 OnStop() {}
19     virtual void
 OnPause() {}
20     virtual void
 OnContinue() {}
21     virtual void
 OnInterrogate() {}
22     virtual void
 OnShutdown() {}
23     virtual void
 OnUserControl(DWORD dwControl) {}
24 
    ...
25 
};
26 

27 #endif/*BASE_SERVICE_H*/

要实现自己的服务类只需从BaseService继承并且Override相关的virtual函数即可,下面示范一个BeepService类,该服务只是简单地每隔2秒beep一下,为了简单所有代码均放在.h文件中:
BeepService.h

 1  #ifndef BEEP_SERVICE_H
 2 
#define BEEP_SERVICE_H
 3 

 4 #include "BaseService.h"
 5 
 6 class BeepService : public  BaseService {
 7 public
:
 8 
    BeepService(LPCTSTR szServiceName)
 9 
        :BaseService(szServiceName)
10         ,m_bPaused(false
)
11         ,m_bRunning(false
) {}
12 

13     virtual void OnStop() { m_bRunning = false ; }
14     virtual void OnPause() { m_bPaused = true
; }
15     virtual void OnContinue() { m_bPaused = false
; }
16     virtual void
 Run() {
17         m_bRunning = true
;
18         while
 (m_bRunning) {
19             if (!
m_bPaused)
20                 Beep(800800
);
21             Sleep(2000
);
22 
        }
23 
    }
24 private
:
25     volatile
 bool m_bPaused;
26     volatile
 bool m_bRunning;
27 
};
28 

29 #endif/*BEEP_SERVICE_H*/

通常来说只须要Override上面的4个virtual函数就OK了:
Run()中进行实际的工作,OnStop(),OnPause(),OnContinue()则是为了响应Service Control Manager的控制。

test.cpp

 1 #include <windows.h>
 2 #include <tchar.h>
 3 #include <stdio.h>
 4 #include "BeepService.h"
 5 
 6 int main(int argc, char * argv[]) {
 7 

 8     BeepService beepService(_T("BeepService" ));
 9     if (!
beepService.ParseStandardArgs(argc, argv)) {
10         if
 (beepService.IsInstalled()) {
11             if (!
beepService.Start())
12                 printf("The service can not run from command line.\n"
);
13         } else
 {
14             printf("The service is not installed, "

15                 "use \"%-i\" to install.\n", argv[0 ]);
16 
        }
17 
    }
18     return 0
;
19 }

假设编译后生成的exe文件为beep.exe,则在命令行中可以如下使用:
(1). beep -i    安装service(安装以后系统运行时会自动启动)
(2). beep -u   卸载service(如果service正在运行,则先停止service再卸载)
BaseServiced 的ParseStandardArgs正是用来解析上述两个命令。

2. ServiceCtrl
虽然Windows自带的Service Control Manager可以控制服务程序,但是很多时候我们都需要用代码控制,这就用到ServiceCtrl类,该类的接口如下:
ServiceCtrl.h

 1  #ifndef SERVICE_CTRL_H
 2 
#define SERVICE_CTRL_H
 3 

 4 class  ServiceCtrl {
 5 public
:
 6 
    ServiceCtrl(LPCTSTR szServiceName);
 7     ~
ServiceCtrl();
 8 
    bool Start();
 9 
    bool Pause();
10 
    bool Continue();
11 
    bool Stop();
12 
    bool Interrogate();
13 
    bool UserControl(DWORD dwControl);
14     DWORD State() const
;
15 
    ...
16 
};
17 

18 #endif/*SERVICE_CTRL_H*/

接口比较直观没什么好说的,看下面的示例代码:
test.cpp

 1 #include <windows.h>
 2 #include <tchar.h>
 3 #include <stdio.h>
 4 #include <exception>
 5 #include "BeepService.h"
 6 #include "ServiceCtrl.h"
 7 
 8 int main(int argc, char * argv[]) {
 9 

10     try  {
11         ServiceCtrl servCtrl(_T("BeepService"
));
12         if (servCtrl.State() !=
 SERVICE_STOPPED) {
13             printf("Service already started.\n"
);
14         } else
 {
15 
            servCtrl.Start();
16             printf("Start.\n"
);
17             Sleep(6000
);
18 
            servCtrl.Pause();
19             printf("Pause.\n"
);
20             Sleep(6000
);
21 
            servCtrl.Continue();
22             printf("Continue.\n"
);
23             Sleep(6000
);
24 
            servCtrl.Stop();
25             printf("Stop.\n"
);
26 
        }
27     } catch (std::exception &
e) {
28         printf("%s\n"
, e.what());
29 
    }
30     return 0
;
31 }

源代码:点击下载

以下是原文:
上周我们刊登了一篇《Linux难敌Windows的新七大理由》(见链接:http://news.ccw.com.cn/soft/htm2007/20070921_320660.

 
shtml),从各个角度分析了Linux对于Windows的弱势。今天我们收到了一位愤怒的Linux支持者的留言: “那么,我来说说Windows的缺点: 首先是安全隐患,因为Windows的机密代码从来没有公开过,而你并不知道你电脑中正有一些隐藏的账号如后门、木马一样在为某些人敞开着并为一些图谋不轨的偷窥者所监视,如果你决定使用Windows来存储众多高机密的文件的话,那么请你记住:一定要保持你的计算机永远断开网络,并且一直关闭着电源。 其次是对Windows的依赖如同对毒品依赖一样,因为计算机能力的参差不齐,很多非计算机专业人士并不掌握Linux的使用技术,因此Windows也就成为了那些只会用鼠标点击来进行简单操作的那些人的救命稻草,这就将形成一种对计算机高级技术学习的慵懒和对傻瓜式操作的依赖,并且他们将因为无知而变得更无知,进而蚕食一部分Linux专业人士的阵营,使一部分Linux专业人士不得不放下Linux转而为一些Windows的瘾君子服务。 再次,Linux的队伍正在空前壮大,而Windows则势单力薄,很难想象一个老人和几十个孩子对战,谁更有希望熬到战争的最后。 Windows的被推崇和被广泛应用是一个时代的产物,在中国这与计算机产业的落后、专业人士的匮乏等历史、经济条件和技术条件因素有着极大的关联,在一个民族没有接受过高等计算机教育的时候Windows将成为我们最好的启蒙老师,而在这个民族计算机技术成熟的时候,我们需要的是Linux,因为Windows无法再次使我们得到满足。并且,现在Linux在部分高校的计算机专业已经成为一门必修课或者选修课,更多的孩子不用像我们当初一样苦于求学无门,我想这是一个信号吧,对Linux的普及将成为一种必然,并且对Windows关门也一定为时不远了,对于那些对Windows尚存忠实的瘾君子们“W”将成为他们愚蠢和无知的代名词,它将在计算机的进化世界中遭到被淘汰和被鄙夷的命运。Linux技术必将在中华大陆上得到新一轮的发扬、腾飞和壮大。 最后,Linux世界不需要像比尔盖茨这样的商人,因为我们的心中只有像林纳斯·托瓦兹这样主张和支持开源计划的人,因为Linux运动将是一场标志人类计算机文明的新世代的智慧沉淀,它将成为亿万人共同协作的纯美结晶。”


以下是我的观点:
1.安全:Linux真的安全嘛?有源代码就一定安全嘛?这个问题很难回答,在现在无论windows还是linux的内核都已经十分的庞大,真的要安插一个精心的后门,是完全可能的.在说windows是收费的,微软有义务和责任为客户提供安全保障.用一句话说就是:之所以说windows不安全是因为用的人太多了,相对linux的用户群很小,安全问题发现的几率也就小很多.而且,同样的内核还有收费和不收费的,这样使linux本身就很难快速的发展.windows有没有被发现的安全漏洞,难道linux就没有吗?说windows不好,是因为眼红windows的庞大用户群.
2:易用:我不知道这人是什么毕业的,大概不是计算机系的,也不是搞编程的,因为易用是软件本身要达到的一个目标,看看会用linux的,那个不是N年的windows使用经验?对计算机有很深的了解,至少在国内是这样的,国外应该也是这样的.使用计算机本身的目的就是为了快速的完成工作,难道你想让客户先学两年linux在使用openOffice?可能吗?linux不是也越来越多的图形化扩展吗?似乎也在努力向易用性上发展呀?

Lua脚本语法说明(增加lua5.1部份特性)

  Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。
  所以,我只简单的归纳一下Lua的一些语法规则,使用起来方便好查就可以了。估计看完了,就懂得怎么写Lua程序了。

  在Lua中,一切都是变量,除了关键字。

I.  首先是注释
  写一个程序,总是少不了注释的。
  在Lua中,你可以使用单行注释和多行注释。
  单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。
  多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*...*/"。在注释当中,"[["和"]]"是可以嵌套的(在lua5.1中,中括号中间是可以加若干个"="号的,如 [==[ ... ]==]),见下面的字符串表示说明。

II.  Lua编程
  经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单:
  print("Hello world")
  在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。
  Lua 有好几种程序控制语句,如:
控制语句 格式 示例
If if 条件 then ... elseif 条件 then ... else ... end
if 1+1=2 then print("true")
elseif 1+2~=3 then print("true")
else print("false"end

While while 条件 do ... end
while 1+1~=2 do print("true"end

Repeat repeat ... until 条件
repeat print("Hello"until 1+1~=2

For for 变量=初值, 终点值, 步进 do ... end
for i = 1102 do print(i) end

For for 变量1, 变量2, ... 变量n in 表或枚举函数 do ... end
for a,b in mylist do print(a, b) end


  注意一下,for的循环变量总是只作用于for的局部变量;当省略步进值时,for循环会使用1作为步进值。
  使用break可以用来中止一个循环。
  相对C语言来说,Lua有几个地方是明显不同的,所以面要特别注意一下:

  .语句块
    语句块在C中是用"{"和"}"括起来的,在Lua中,它是用do 和 end 括起来的。比如:
    do print("Hello") end
    可以在 函数 中和 语句块 中定局部变量。

  .赋值语句
    赋值语句在Lua被强化了。它可以同时给多个变量赋值。
    例如:
    a,b,c,d=1,2,3,4
    甚至是:
    a,b=b,a  -- 多么方便的交换变量功能啊。
    在默认情况下,变量总是认为是全局的。假如需要定义局部变量,则在第一次赋值的时候,需要用local说明。比如:
    local a,b,c = 1,2,3  -- a,b,c都是局部变量

  .数值运算
    和C语言一样,支持 +, -, *, /。但Lua还多了一个"^"。这表示指数乘方运算。比如2^3 结果为8, 2^4结果为16。
    连接两个字符串,可以用".."运处符。如:
    "This a " .. "string." -- 等于 "this a string"

  .比较运算
比较符号 < > <= >= == ~=
含义 小于 大于 小于或等于 大于或等于 相等 不相等

    所有这些操作符总是返回true或false。
    对于Table,Function和Userdata类型的数据,只有 == 和 ~=可以用。相等表示两个变量引用的是同一个数据。比如:
    a={1,2}
    b
=a
    
print(a==b, a~=b)  --输出 true, false
    a={1,2}
    b
={1,2}
    
print(a==b, a~=b)  --输出 false, true


  .逻辑运算
    and, or, not
    其中,and 和 or 与C语言区别特别大。
    在这里,请先记住,在Lua中,只有false和nil才计算为false,其它任何数据都计算为true,0也是true!
    and 和 or的运算结果不是true和false,而是和它的两个操作数相关。
    a and b:如果a为false,则返回a;否则返回b
    a or b:如果 a 为true,则返回a;否则返回b

    举几个例子:
     print(4 and 5--输出 5
     print(nil and 13--输出 nil
     print(false and 13--输出 false
     print(4 or 5--输出 4
     print(false or 5--输出 5

    在Lua中这是很有用的特性,也是比较令人混洧的特性。
    我们可以模拟C语言中的语句:x = a? b : c,在Lua中,可以写成:x = a and b or c。
    最有用的语句是: x = x or v,它相当于:if not x then x = v end 。

  .运算符优先级,从低到高顺序如下:
     or
     and
     <     >     <=    >=    ~=    ==
     .. (字符串连接)
     +     -
     *     /     %
     not   #(lua5.1 取长度运算)     - (一元运算)
     ^
和C语言一样,括号可以改变优先级。

III.  关键字
  关键字是不能做为变量的。Lua的关键字不多,就以下几个:
    
and break do else elseif
end false for function if
in local nil not or
repeat return then true until while

IV.  变量类型
  怎么确定一个变量是什么类型的呢?大家可以用type()函数来检查。Lua支持的类型有以下几种:
Nil 空值,所有没有使用过的变量,都是nil。nil既是值,又是类型。
Boolean 布尔值,只有两个有效值:true和false
Number 数值,在Lua里,数值相当于C语言的double
String 字符串,如果你愿意的话,字符串是可以包含"\0"字符的(这和C语言总是以"\0"结尾是不一样的)
Table 关系表类型,这个类型功能比较强大,请参考后面的内容。
Function 函数类型,不要怀疑,函数也是一种类型,也就是说,所有的函数,它本身就是一个变量。
Userdata 嗯,这个类型专门用来和Lua的宿主打交道的。宿主通常是用C和C++来编写的,在这种情况下,Userdata可以是宿主的任意数据类型,常用的有Struct和指针。
Thread 线程类型,在Lua中没有真正的线程。Lua中可以将一个函数分成几部份运行。如果感兴趣的话,可以去看看Lua的文档。
现在回过头来看看,倒觉得不是线程类型。反而象是用来做遍历的,象是Iterator函数。
如:
function range(n)
  local
= 0
  
while(i < n) do
    coroutine.yield( i )
    i = i + 1
  
end
end
可惜的是要继续运行,需要coroutine.resume函数,有点鸡肋。请指教。

V.  变量的定义
  所有的语言,都要用到变量。在Lua中,不管在什么地方使用变量,都不需要声明,并且所有的这些变量总是全局变量,除非我们在前面加上"local"。这一点要特别注意,因为我们可能想在函数里使用局部变量,却忘了用local来说明。
  至于变量名字,它是大小写相关的。也就是说,A和a是两个不同的变量。
  定义一个变量的方法就是赋值。"="操作就是用来赋值的
  我们一起来定义几种常用类型的变量吧。
  A.  Nil
    正如前面所说的,没有使用过的变量的值,都是Nil。有时候我们也需要将一个变量清除,这时候,我们可以直接给变量赋以nil值。如:
    var1=nil  -- 请注意 nil 一定要小写

  B.  Boolean
    布尔值通常是用在进行条件判断的时候。布尔值有两种:true 和 false。在Lua中,只有false和nil才被计算为false,而所有任何其它类型的值,都是true。比如0,空串等等,都是true。不要被 C语言的习惯所误导,0在Lua中的的确确是true。你也可以直接给一个变量赋以Boolean类型的值,如:
    theBoolean = true

  C.  Number
    在Lua中,是没有整数类型的,也不需要。一般情况下,只要数值不是很大(比如不超过100,000,000,000,000),是不会产生舍入误差的。在WindowsXP能跑的当今主流PC上,实数的运算并不比整数慢。
    实数的表示方法,同C语言类似,如:
    4 0.4 4.57e-3 0.3e12 5e+20

  D.  String
    字符串,总是一种非常常用的高级类型。在Lua中,我们可以非常方便的定义很长很长的字符串。
    字符串在Lua中有几种方法来表示,最通用的方法,是用双引号或单引号来括起一个字符串的,如:
    "That's go!"
    或
    'Hello world!'

    和C语言相同的,它支持一些转义字符,列表如下:
    \a  bell
    \b  back space
    \f  form feed
    \n  newline
    \r  carriage return
    \t  horizontal tab
    \v  vertical tab
    \\  backslash
    \"  double quote
    \"  single quote
    \[  left square bracket
    \]  right square bracket

    由于这种字符串只能写在一行中,因此,不可避免的要用到转义字符。加入了转义字符的串,看起来实在是不敢恭维,比如:
    "one line\nnext line\n\"in quotes\", "in quotes""
    一大堆的"\"符号让人看起来很倒胃口。如果你与我有同感,那么,我们在Lua中,可以用另一种表示方法:用"[["和"]]"将多行的字符串括起来。(lua5.1: 中括号中间可以加入若干个"="号,如 [==[ ... ]==],详见下面示例
    示例:下面的语句所表示的是完全相同的字符串:
= 'alo\n123"'
= "alo\n123\""
= '\97lo\10\04923"'
= [[alo
123"
]]
= [==[
alo
123"
]==]

    值得注意的是,在这种字符串中,如果含有单独使用的"[["或"]]"就仍然得用"\["或"\]"来避免歧义。当然,这种情况是极少会发生的。

  E.  Table
    关系表类型,这是一个很强大的类型。我们可以把这个类型看作是一个数组。只是C语言的数组,只能用正整数来作索引;在Lua中,你可以用任意类型来作数组的索引,除了nil。同样,在C语言中,数组的内容只允许一种类型;在Lua中,你也可以用任意类型的值来作数组的内容,除了nil。
    Table的定义很简单,它的主要特征是用"{"和"}"来括起一系列数据元素的。比如:
    T1 = {}  -- 定义一个空表
    T1[1]=10  -- 然后我们就可以象C语言一样来使用它了。

    T1["John"]={Age=27, Gender="Male"}
    这一句相当于:
    T1
["John"]={}  -- 必须先定义成一个表,还记得未定义的变量是nil类型吗
    T1["John"]["Age"]=27
    T1
["John"]["Gender"]="Male"
    当表的索引是字符串的时候,我们可以简写成:
    T1.John
={}
    T1.John.Age
=27
    T1.John.Gender
="Male"
    或
    T1.John{Age
=27, Gender="Male"}
这是一个很强的特性。

    在定义表的时候,我们可以把所有的数据内容一起写在"{"和"}"之间,这样子是非常方便,而且很好看。比如,前面的T1的定义,我们可以这么写:
    T1=
    {
      
10,  -- 相当于 [1] = 10
      [100] = 40,
      John
=  -- 如果你原意,你还可以写成:["John"] =
      {
        Age
=27,   -- 如果你原意,你还可以写成:["Age"] =27
        Gender=Male   -- 如果你原意,你还可以写成:["Gender"] =Male
      },
      
20  -- 相当于 [2] = 20
    }

    看起来很漂亮,不是吗?我们在写的时候,需要注意三点:
    第一,所有元素之间,总是用逗号","隔开;
    第二,所有索引值都需要用"["和"]"括起来;如果是字符串,还可以去掉引号和中括号;
    第三,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编;

    表类型的构造是如此的方便,以致于常常被人用来代替配置文件。是的,不用怀疑,它比ini文件要漂亮,并且强大的多。

  F.  Function
    函数,在Lua中,函数的定义也很简单。典型的定义如下:
    function add(a,b)  -- add 是函数名字,a和b是参数名字
     return a+b  -- return 用来返回函数的运行结果
    end

    请注意,return语言一定要写在end之前。假如我们非要在中间放上一句return,那么就应该要写成:do return end。
    还记得前面说过,函数也是变量类型吗?上面的函数定义,其实相当于:
    add = function (a,b) return a+end
当重新给add赋值时,它就不再表示这个函数了。我们甚至可以赋给add任意数据,包括nil (这样,赋值为nil,将会把该变量清除)。Function是不是很象C语言的函数指针呢?

    和C语言一样,Lua的函数可以接受可变参数个数,它同样是用"..."来定义的,比如:
    function sum (a,b,)
如果想取得...所代表的参数,可以在函数中访问arg局部变量(表类型)得到 (lua5.1: 取消arg,并直接用"..."来代表可变参数了,本质还是arg)。
    如 sum(1,2,3,4)
    则,在函数中,a = 1, b = 2, arg = {3, 4}  (lua5.1:  a = 1, b = 2, ... = {3, 4})
    更可贵的是,它可以同时返回多个结果,比如:
    function s()
      
return 1,2,3,4
    
end
    a,b,c,d 
= s()  -- 此时,a = 1, b = 2, c = 3, d = 4

    前面说过,表类型可以拥有任意类型的值,包括函数!因此,有一个很强大的特性是,拥有函数的表,哦,我想更恰当的应该说是对象吧。Lua可以使用面向对象编程了。不信?举例如下:
    t =
    {
     Age 
= 27
     
add = function(self, n) self.Age = self.Age+end
    }
    
print(t.Age)  -- 27
    t.add(t, 10)
    
print(t.Age)  -- 37

    不过,t.add(t,10) 这一句实在是有点土对吧?没关系,在Lua中,我们可以简写成:
    t:add(10)    -- 相当于 t.add(t,10)

  G.  Userdata 和 Thread
    这两个类型的话题,超出了本文的内容,就不打算细说了。

VI.  结束语
  就这么结束了吗?当然不是,接下来,我们需要用Lua解释器,来帮助理解和实践了。相信这样会更快的对Lua上手了。
  就象C语言一样,Lua提供了相当多的标准函数来增强语言的功能。使用这些标准函数,可以很方便的操作各种数据类型,并处理输入输出。有关这方面的信息,我们可以参考《Programming in Lua 》一书,也可以在网络上直接观看电子版,网址为:http://www.lua.org/pil/index.html
  
备注:本文的部份内容摘、译自lua随机文档。
相关链接:
1. Lua 官方网站: http://www.lua.org/
2. Lua Wiki网站,你可以在这里找到很多相关的资料,如文档、教程、扩展,以及C/C++的包装等: http://lua-users.org/wiki/
3. Lua 打包下载(包括各种平台和编译器的工程文件如vs2003,vs2005):http://luabinaries.luaforge.net/download.html

这是我编译好的Lua5.02的解释器:http://www.cnblogs.com/Files/ly4cn/lua.zip

下面的这些内容不包含在本文中:
官方的Lua包和文档 (参看 http://www.lua.org/),
涉及到Lua使用但不是被Lua使用者普遍使用的东西 (参看 http://www.lua.org/uses.html),
本wiki已经存在的内容(参看 LuaDirectory).

类库和与Lua绑定的资源

LibrariesAndBindings

开发环境

  • [LuaIDE] (5.0) - Windows平台Lua的整合开发环境(最新发布 2004-01-29).
  • [wxLua] (5.0) - Lua and wxWidgets的混合体.有自己的带有调试器的IDE。这个IDE使用wxlua开发的。
  • [LuaEclipse] (5.0) - Eclipse 平台的Lua IDE .
  • [Vortex LuaIDE] (5.0) - Brazilian Portuguese的一些免费的Lua IDE! (Outra IDE para Lua gratuita, mas em Português!).
  • [LuaX] (5.x) - 支持多种操作系统下开发Lua应用的开发平台(with GUI, serial port etc. modules)特别适合于工业自动化和嵌入式产品开发。
  • [VisualWx] (5.x) - Lua & wxLua的IDE (自由软件)
  • [QDE] (5.x) - Lua的Quotix 开发环境. 他支持工程管理,多文档接口等 (最新发布2005-03-06).
  • [B:Lua] - 开源项目: 具备各种特征的Lua IDE.

代码封装

  • [CPB] (5.0)(Win32) - C++ 和 Lua连接之桥,实现两种语言的相互通信.
  • [CaLua] (5.0) - 实现了绑定C函数和结构体到Lua,可以在Lua中使用C指针、数组、函数。(使用x86结构的计算机)
  • [CppLua] (5.0) - lua API的C++封装。
  • [lua2c] (5.0) - 将Lua代码转换成C代码的工具.
  • [luabind] (5.0) - 基于模板的,绑定Lua和C++类以及函数
  • [tolua] (5.0) - 自动绑定C/C++和Lua的工具.
  • LuaWrapper 基于模版的封装包,很容易绑定C++代码和Lua。纯头文件。
  • [tolua++] (5.0) - tolua的高级版本,带有c++面向对象的特征. ( CompilingToluappWithoutScons i.e., Compiling Tolua++ Without SCons )
  • [luapi] (5.0) - Lua api的C封装.
  • [PBLua] (5.0.2) - Lua 5.0.2基本库的封装.
  • [PowerBLua] (5.0) - PowerBASIC include & source for wrapping Lua (work in progress).
  • [wxScript] (5.0.2) - 一些抽象类,添加到脚本解释器可以使你的wxWidgets支持Lua语言。
  • [LunaticPython] (0.1) - Lua/Python 的双向桥。可以将Lua解释器嵌入到python中,反过来也可以。

Lua的使用

Lua的使用列表[1].

  • [Premake] -一个用Lua语言实现的 automake 替代品. 可以为MS VC++, GNU make, 等创建工程文件.
  • [Lumikki] (5.0) - 使用Lua宏创建web站点,可以使用导航菜单,风格等。

工具

  • ExpLua (5.0) - 代码文档工具(开发中。。。).
  • [Hamster (ex SCons/Lua)] (5.x) - Lua front-end to the SCons build engine (or others).
  • [Kepler Project] (5.0) - 使用 [CGILua 5.0]实现的Lua 5.0的web模板引擎 .
  • [Mod_Lua (5.0)] -  Apache 2的 module,使得web应用与PHP类似。
  • [ctrace] (5.0) - 跟踪Lua API调用的工具.
  • [lper] (5.0) - 创建持久Lua 状态的工具, 基于 [GNU mmalloc] (part of GDB).
  • PlutoLibrary (5.0.2) - 为Lua创建重量级的持久性.
  • [ChunkSpy] (5.0.2) - a binary chunk disassembler, with interactive mode, reading custom binary chunk formats, etc.
  • [lua bakefile] (5.0.2) - 创建 LUA 工程的makefile和工程文件。
  • [tolua bakefile] (5.0.2) - 创建 TOLUA 工程的makefile和工程文件。
  • [ChunkBake] (5.0.2) - a line-oriented assembler for Lua 5 virtual machine instructions
  • [LuaSrcDiet] (5.0.2) - 通过删除不必要的空白和注释缩减Lua文件的大小。
  • [LuaProfiler] (5.0) - 一个用来查找Lua应用瓶颈的工具time profiler 。

See also LuaEditorSupport.

文档

发布版

See LuaBinaries for links to precompiled vanilla Lua 5.0 executables for various platforms. Real binaries (without addons) should move from here to there.

  • [LuaCheia] (5.0) - Lua 5.0 for GNU/Linux, Mac/OS X, Windows, *BSD, Solaris, etc. Includes many add-on binary modules.
  • [LuaPlus] (5.0) - C++ enhancements and various extensions to core Lua functionality
  • [Linux RPMs] (5.0) - i386 binary, source and spec file for Lua 5.0.2
  • [RPMs for Lua] (5.0) - from Rpmfind.Net.
  • [RPMs with Lua modules]
  • [Lua 5 for EPOC] (5.0) - Lua 5.0.1 for Symbian OS v1-5.
  • [LuaCE] (5.0) - additional source files for compiling Lua for Windows CE.
  • [LuaPPC] (5.0) - Stand-alone Lua 5.0 interpreter for the Pocket PC. (Built using LuaCE above.)
  • [LuaPocket] (5.0) - Stand-alone Lua 5.0 interpreter for the Pocket PC with graphic support. (Built using LuaPPC above.)
  • [Lua for RISC OS] (5.0)
  • [Lua for Palm OS] (5.0) Port of Lua for Palm (in alpha).
  • [Lua89] (5.0) Experimental port of Lua to the TI89 graphing calculator.

其他的一些Lua实现方式

  • [Sol] - Lua 的派生物.
  • [Lua.NET] .net相关的Lua
  • [LuaLeo] -  根据[Leonardo VM] 实现的Lua 3.1
  • [Lua-ML] -  由面向对象的Caml实现的 Lua 2.5
  • [Yindo] - Lua 的实现,作为OpenGL 浏览器插件(很快过时了)
  • [Yueliang] (5.0.2) - 使用Lua实现Lua,包括词法解析、语法解析、代码生成和生成二进制chunks.

大家知道,Windows NT/2000为实现其可靠性,严格将系统划分为内核模式与用户模式,在i386系统中分别对应CPU的Ring0与Ring3级别。Ring0下,可以执行特权级指令,对任何I/O设备都有访问权等等。要实现从用户态进入核心态,即从Ring 3进入Ring 0必须借助CPU的某种门机制,如中断门、调用门等。而Windows NT/2000提供用户态执行系统服务(Ring 0例程)的此类机制即System Service的int 2eh中断服务等,严格的参数检查,只能严格的执行Windows NT/2000提供的服务,而如果想执行用户提供的Ring 0代码(指运行在Ring 0权限的代码),常规方法似乎只有编写设备驱动程序。本文将介绍一种在用户态不借助任何驱动程序执行Ring0代码的方法。

    Windows NT/2000将设备驱动程序调入内核区域(常见的位于地址0x80000000上),由DPL为0的GDT项8,即cs为8时实现Ring 0权限。本文通过在系统中构造一个指向我们的代码的调用门(CallGate),实现Ring0代码。基于这个思路,为实现这个目的主要是构造自己的CallGate。CallGate由系统中叫Global Descriptor Table(GDT)的全局表指定。GDT地址可由i386指令sgdt获得(sgdt不是特权级指令,普通Ring 3程序均可执行)。GDT地址在Windows NT/2000保存于KPCR(Processor Control Region)结构中(见《再谈Windows NT/2000环境切换》)。GDT中的CallGate是如下的格式:

    typedef struct
    {
        unsigned short  offset_0_15;
        unsigned short  selector;

        unsigned char    param_count : 4;
        unsigned char    some_bits   : 4;

        unsigned char    type        : 4;
        unsigned char    app_system  : 1;
        unsigned char    dpl         : 2;
        unsigned char    present     : 1;
    
        unsigned short  offset_16_31;
    } CALLGATE_DESCRIPTOR;

    GDT位于内核区域,一般用户态的程序是不可能对这段内存区域有直接的访问权。幸运的是Windows NT/2000提供了一个叫PhysicalMemory的Section内核对象位于\Device的路径下。顾名思义,通过这个Section对象可以对物理内存进行操作。用objdir.exe对这个对象分析如下:

    C:\NTDDK\bin>objdir /D \Device

    PhysicalMemory                   
        Section
        DACL - 
           Ace[ 0] - Grant - 0xf001f - NT AUTHORITY\SYSTEM
                             Inherit: 
                             Access: 0x001F  and  ( D RCtl WOwn WDacl )

           Ace[ 1] - Grant - 0x2000d - BUILTIN\Administrators
                             Inherit: 
                             Access: 0x000D  and  ( RCtl )

    从dump出的这个对象DACL的Ace可以看出默认情况下只有SYSTEM用户才有对这个对象的读写权限,即对物理内存有读写能力,而Administrator只有读权限,普通用户根本就没有权限。不过如果我们有Administrator权限就可以通过GetSecurityInfo、SetEntriesInAcl与SetSecurityInfo这些API来修改这个对象的ACE。这也是我提供的代码需要Administrator的原因。实现的代码如下:

    VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
    {

       PACL pDacl=NULL;
       PACL pNewDacl=NULL;
       PSECURITY_DESCRIPTOR pSD=NULL;
       DWORD dwRes;
       EXPLICIT_ACCESS ea;

       if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
                  NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)
          {
             printf( ”GetSecurityInfo Error %u\n”, dwRes );
             goto CleanUp;
          }

       ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
       ea.grfAccessPermissions = SECTION_MAP_WRITE;
       ea.grfAccessMode = GRANT_ACCESS;
       ea.grfInheritance= NO_INHERITANCE;
       ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
       ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
       ea.Trustee.ptstrName = ”CURRENT_USER”;


       if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)
          {
             printf( ”SetEntriesInAcl %u\n”, dwRes );
             goto CleanUp;
          }

       if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)
          {
             printf(“SetSecurityInfo %u\n”,dwRes);
             goto CleanUp;
          }

    CleanUp:

       if(pSD)
          LocalFree(pSD);
       if(pNewDacl)
          LocalFree(pSD);
    }

    这段代码对给定HANDLE的对象增加了如下的ACE:

    PhysicalMemory                   
        Section
        DACL - 
           Ace[ 0] - Grant - 0x2 - WEBCRAZY\Administrator
                             Inherit: 
                             Access: 0x0002    //SECTION_MAP_WRITE

    这样我们在有Administrator权限的条件下就有了对物理内存的读写能力。但若要修改GDT表实现Ring 0代码。我们将面临着另一个难题,因为sgdt指令获得的GDT地址是虚拟地址(线性地址),我们只有知道GDT表的物理地址后才能通过\Device\PhysicalMemory对象修改GDT表,这就牵涉到了线性地址转化成物理地址的问题。我们先来看一看Windows NT/2000是如何实现这个的:

    kd> u nt!MmGetPhysicalAddress l 30
    ntoskrnl!MmGetPhysicalAddress:
    801374e0 56               push    esi
    801374e1 8b742408         mov     esi,[esp+0x8]
    801374e5 33d2             xor     edx,edx
    801374e7 81fe00000080     cmp     esi,0x80000000
    801374ed 722c             jb    ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
    801374ef 81fe000000a0     cmp     esi,0xa0000000
    801374f5 7324             jnb   ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
    801374f7 39153ce71780     cmp     [ntoskrnl!MmKseg2Frame (8017e73c)],edx
    801374fd 741c             jz    ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
    801374ff 8bc6             mov     eax,esi
    80137501 c1e80c           shr     eax,0xc
    80137504 25ffff0100       and     eax,0x1ffff
    80137509 6a0c             push    0xc
    8013750b 59               pop     ecx
    8013750c e8d3a7fcff       call    ntoskrnl!_allshl (80101ce4)
    80137511 81e6ff0f0000     and     esi,0xfff
    80137517 03c6             add     eax,esi
    80137519 eb17             jmp   ntoskrnl!MmGetPhysicalAddress+0x57 (80137532)
    8013751b 8bc6             mov     eax,esi
    8013751d c1e80a           shr     eax,0xa
    80137520 25fcff3f00       and     eax,0x3ffffc
    80137525 2d00000040       sub     eax,0x40000000
    8013752a 8b00             mov     eax,[eax]
    8013752c a801             test    al,0x1
    8013752e 7506             jnz   ntoskrnl!MmGetPhysicalAddress+0x44 (80137536)
    80137530 33c0             xor     eax,eax
    80137532 5e               pop     esi
    80137533 c20400           ret     0x4

    从这段汇编代码可看出如果线性地址在0x80000000与0xa0000000范围内,只是简单的进行移位操作(位于801374ff-80137519指令间),并未查页表。我想Microsoft这样安排肯定是出于执行效率的考虑。这也为我们指明了一线曙光,因为GDT表在Windows NT/2000中一般情况下均位于这个区域(我不知道/3GB开关的Windows NT/2000是不是这种情况)。

    经过这样的分析,我们就可以只通过用户态程序修改GDT表了。而增加一个CallGate就不是我可以介绍的了,找本Intel手册自己看一看了。具体实现代码如下:

    typedef struct gdtr {
        short Limit;
        short BaseLow;
        short BaseHigh;
    } Gdtr_t, PGdtr_t;

    ULONG MiniMmGetPhysicalAddress(ULONG virtualaddress)
    {
        if(virtualaddress<0x80000000||virtualaddress>=0xA0000000)
           return 0;
        return virtualaddress&0x1FFFF000;
    }

    BOOL ExecRing0Proc(ULONG Entry,ULONG seglen)
    {
       Gdtr_t gdt;
       __asm sgdt gdt;
     
       ULONG mapAddr=MiniMmGetPhysicalAddress(gdt.BaseHigh<<16U|gdt.BaseLow);
       if(!mapAddr) return 0;

       HANDLE   hSection=NULL;
       NTSTATUS status;
       OBJECT_ATTRIBUTES        objectAttributes;
       UNICODE_STRING objName;
       CALLGATE_DESCRIPTOR cg;

       status = STATUS_SUCCESS;
   
       RtlInitUnicodeString(&objName,L”\Device\PhysicalMemory”);

       InitializeObjectAttributes(&objectAttributes,
                                  &objName,
                                  OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                  NULL,
                                 (PSECURITY_DESCRIPTOR) NULL);

       status = ZwOpenSection(&hSection,SECTION_MAP_READ|SECTION_MAP_WRITE,&objectAttributes);

       if(status == STATUS_ACCESS_DENIED){
          status = ZwOpenSection(&hSection,READ_CONTROL|WRITE_DAC,&objectAttributes);
          SetPhyscialMemorySectionCanBeWrited(hSection);
          ZwClose(hSection);
          status =ZwOpenSection(&hSection,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&objectAttributes);
       }

       if(status != STATUS_SUCCESS)
         {
            printf(“Error Open PhysicalMemory Section Object,Status:%08X\n”,status);
            return 0;
         }
      
       PVOID BaseAddress;

       BaseAddress=MapViewOfFile(hSection,
                     FILE_MAP_READ|FILE_MAP_WRITE,
                     0,
                     mapAddr,    //low part
                     (gdt.Limit+1));

       if(!BaseAddress)
          {
             printf(“Error MapViewOfFile:”);
             PrintWin32Error(GetLastError());
             return 0;
          }

       BOOL setcg=FALSE;

       for(cg=(CALLGATE_DESCRIPTOR 
)((ULONG)BaseAddress+(gdt.Limit&0xFFF8));(ULONG)cg>(ULONG)BaseAddress;cg–)
           if(cg->type == 0){
             cg->offset_0_15 = LOWORD(Entry);
             cg->selector = 8;
             cg->param_count = 0;
             cg->some_bits = 0;
             cg->type = 0xC;          // 386 call gate
             cg->app_system = 0;      // A system descriptor
             cg->dpl = 3;             // Ring 3 code can call
             cg->present = 1;
             cg->offset_16_31 = HIWORD(Entry);
             setcg=TRUE;
             break;
          }

       if(!setcg){
            ZwClose(hSection);
            return 0;
       }

       short farcall[3];

       farcall[2]=((short)((ULONG)cg-(ULONG)BaseAddress))|3;  //Ring 3 callgate;

       if(!VirtualLock((PVOID)Entry,seglen))
          {
             printf(“Error VirtualLock:”);
             PrintWin32Error(GetLastError());
             return 0;
          }

       SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);

       Sleep(0);

       _asm call fword ptr [farcall]

       SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL);

       VirtualUnlock((PVOID)Entry,seglen);

       //Clear callgate
       
(ULONG )cg=0;
       
((ULONG *)cg+1)=0;

       ZwClose(hSection);
       return TRUE;

    }

    我在提供的代码中演示了对Control Register与I/O端口的操作。CIH病毒在Windows 9X中就是因为获得Ring 0权限才有了一定的危害,但Windows NT/2000毕竟不是Windows 9X,她已经有了比较多的安全审核机制,本文提供的代码也要求具有Administrator权限,但如果系统存在某种漏洞,如缓冲区溢出等等,还是有可能获得这种权限的,所以我不对本文提供的方法负有任何的责任,所有讨论只是一个技术热爱者在讨论技术而已。谢谢! 

    参考资料:
      1.Intel Corp<<Intel Architecture Software Developer’s Manual,Volume 3>>  


代码下载:http://www.cnblogs.com/Files/flying_bat/ntring0.zip

本篇是D3D中的Alpha颜色混合(2)的后续篇。

另一种实现实现背景透明显示的简便方法是直接应用渲染管道流水线的Alpha测试功能进行,D3D中的Alpha颜色混合(2)介绍的接口方法实际就是对Alpha测试的一个包装。Alpha测试是对需要写入绘图表面的像素颜色Alpha值进行测试,判断该Alpha值是否满足预先设定的条件,如果满足条件,则将该像素颜色值写入绘图表面,否则不写入。



如上图所示,瞄准器的背景色为标准绿色,为了使瞄准镜可以背景透明地显示在其他图象的上面,需要把瞄准镜的绿色部分镂空。为此,可调用 D3DXCreateTextureFromFileEx函数,相应的创建一个背景为黑色的纹理对象,这个黑色的Alpha值为 0,它的RGBA颜色值则为(0, 0, 0, 0)。然后,开启渲染管道流水线的Alpha测试,并使Alpha测试仅对Alpha值大于或等于某个值的像素颜色值进行写入,以使Alpha值为0的瞄准镜背景黑色不能写入绘图表面,从而使得瞄准镜图象贴在老虎背景画面上时,背景可透明地显示出来。

来看看 D3DXCreateTextureFromFileEx的具体使用说明:

Creates a texture from a file. This is a more advanced function than D3DXCreateTextureFromFile.

HRESULT D3DXCreateTextureFromFileEx(
LPDIRECT3DDEVICE9 pDevice,
LPCTSTR pSrcFile,
UINT Width,
UINT Height,
UINT MipLevels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
DWORD Filter,
DWORD MipFilter,
D3DCOLOR ColorKey,
D3DXIMAGE_INFO * pSrcInfo,
PALETTEENTRY * pPalette,
LPDIRECT3DTEXTURE9 * ppTexture
);

Parameters

pDevice
[in] Pointer to an IDirect3DDevice9 interface, representing the device to be associated with the texture.
pSrcFile
[in] Pointer to a string that specifies the filename. If the compiler settings require Unicode, the data type LPCTSTR resolves to LPCWSTR. Otherwise, the string data type resolves to LPCSTR. See Remarks.
Width
[in] Width in pixels. If this value is zero or D3DX_DEFAULT, the dimensions are taken from the file and rounded up to a power of two. If the device supports non-power of 2 textures and D3DX_DEFAULT_NONPOW2 is specified, the size will not be rounded.
Height
[in] Height, in pixels. If this value is zero or D3DX_DEFAULT, the dimensions are taken from the file and rounded up to a power of two. If the device supports non-power of 2 textures and D3DX_DEFAULT_NONPOW2 is sepcified, the size will not be rounded.
MipLevels
[in] Number of mip levels requested. If this value is zero or D3DX_DEFAULT, a complete mipmap chain is created. If D3DX_FROM_FILE, the size will be taken exactly as it is in the file, and the call will fail if this violates device capabilities.
Usage
[in] 0, D3DUSAGE_RENDERTARGET, or D3DUSAGE_DYNAMIC. Setting this flag to D3DUSAGE_RENDERTARGET indicates that the surface is to be used as a render target. The resource can then be passed to the pNewRenderTarget parameter of the IDirect3DDevice9::SetRenderTarget method. If either D3DUSAGE_RENDERTARGET or D3DUSAGE_DYNAMIC is specified, Pool must be set to D3DPOOL_DEFAULT, and the application should check that the device supports this operation by calling IDirect3D9::CheckDeviceFormat. D3DUSAGE_DYNAMIC indicates that the surface should be handled dynamically. See Using Dynamic Textures.
Format
[in] Member of the D3DFORMAT enumerated type, describing the requested pixel format for the texture. The returned texture might have a different format from that specified by Format. Applications should check the format of the returned texture. If D3DFMT_UNKNOWN, the format is taken from the file. If D3DFMT_FROM_FILE, the format is taken exactly as it is in the file, and the call will fail if this violates device capabilities.
Pool
[in] Member of the D3DPOOL enumerated type, describing the memory class into which the texture should be placed.
Filter
[in] A combination of one or more D3DX_FILTER constants controlling how the image is filtered. Specifying D3DX_DEFAULT for this parameter is the equivalent of specifying D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER.
MipFilter
[in] A combination of one or more D3DX_FILTER constants controlling how the image is filtered. Specifying D3DX_DEFAULT for this parameter is the equivalent of specifying D3DX_FILTER_BOX. In addition, use bits 27-31 to specify the number of mip levels to be skipped (from the top of the mipmap chain) when a .dds texture is loaded into memory; this allows you to skip up to 32 levels.
ColorKey
[in] D3DCOLOR value to replace with transparent black, or 0 to disable the color key. This is always a 32-bit ARGB color, independent of the source image format. Alpha is significant and should usually be set to FF for opaque color keys. Thus, for opaque black, the value would be equal to 0xFF000000.
pSrcInfo
[in, out] Pointer to a D3DXIMAGE_INFO structure to be filled in with a description of the data in the source image file, or NULL.
pPalette
[out] Pointer to a PALETTEENTRY structure, representing a 256-color palette to fill in, or NULL.
ppTexture
[out] Address of a pointer to an IDirect3DTexture9 interface, representing the created texture object.

Return Values

If the function succeeds, the return value is D3D_OK. If the function fails, the return value can be one of the following: D3DERR_INVALIDCALL.

D3DERR_NOTAVAILABLED3DERR_OUTOFVIDEOMEMORYD3DXERR_INVALIDDATAE_OUTOFMEMORY

Remarks

The compiler setting also determines the function version. If Unicode is defined, the function call resolves to D3DXCreateTextureFromFileExW. Otherwise, the function call resolves to D3DXCreateTextureFromFileExA because ANSI strings are being used.

Use D3DXCheckTextureRequirements to determine if your device can support the texture given the current state.

This function supports the following file formats: .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga. See D3DXIMAGE_FILEFORMAT.

Mipmapped textures automatically have each level filled with the loaded texture. When loading images into mipmapped textures, some devices are unable to go to a 1x1 image and this function will fail. If this happens, then the images need to be loaded manually.

For the best performance when using D3DXCreateTextureFromFileEx:

  1. Doing image scaling and format conversion at load time can be slow. Store images in the format and resolution they will be used. If the target hardware requires power of 2 dimensions, then create and store images using power of 2 dimensions.
  2. For mipmap image creation at load time, filter using D3DX_FILTER_BOX. A box filter is much faster than other filter types such as D3DX_FILTER_TRIANGLE.
  3. Consider using DDS files. Since DDS files can be used to represent any Direct3D 9 texture format, they are very easy for D3DX to read. Also, they can store mipmaps, so any mipmap-generation algorithms can be used to author the images.

When skipping mipmap levels while loading a .dds file, use the D3DX_SKIP_DDS_MIP_LEVELS macro to generate the MipFilter value. This macro takes the number of levels to skip and the filter type and returns the filter value, which would then be passed into the MipFilter parameter.


再来看看参数Usage可以使用的枚举类型D3DUSAGE_RENDERTARGET的详细说明:

Usage options that identify how resources are to be used.

Usages

The following table summarizes the available usage options.

#define Description
D3DUSAGE_AUTOGENMIPMAP The resource will automatically generate mipmaps. See Automatic Generation of Mipmaps (Direct3D 9). Automatic generation of mipmaps is not supported for volume textures and depth stencil surfaces/textures. This usage is not valid for a resource in system memory (D3DPOOL_SYSTEMMEM).
D3DUSAGE_DEPTHSTENCIL The resource will be a depth stencil buffer. D3DUSAGE_DEPTHSTENCIL can only be used with D3DPOOL_DEFAULT.
D3DUSAGE_DMAP The resource will be a displacement map.
D3DUSAGE_DONOTCLIP Set to indicate that the vertex buffer content will never require clipping. When rendering with buffers that have this flag set, the D3DRS_CLIPPING render state must be set to false.
D3DUSAGE_DYNAMIC Set to indicate that the vertex buffer requires dynamic memory use. This is useful for drivers because it enables them to decide where to place the buffer. In general, static vertex buffers are placed in video memory and dynamic vertex buffers are placed in AGP memory. Note that there is no separate static use. If you do not specify D3DUSAGE_DYNAMIC, the vertex buffer is made static. D3DUSAGE_DYNAMIC is strictly enforced through the D3DLOCK_DISCARD and D3DLOCK_NOOVERWRITE locking flags. As a result, D3DLOCK_DISCARD and D3DLOCK_NOOVERWRITE are valid only on vertex buffers created with D3DUSAGE_DYNAMIC. They are not valid flags on static vertex buffers. For more information, see Managing Resources (Direct3D 9).

For more information about using dynamic vertex buffers, see Performance Optimizations (Direct3D 9).

D3DUSAGE_DYNAMIC and D3DPOOL_MANAGED are incompatible and should not be used together. See D3DPOOL.

Textures can specify D3DUSAGE_DYNAMIC. However, managed textures cannot use D3DUSAGE_DYNAMIC. For more information about dynamic textures, see Using Dynamic Textures.

D3DUSAGE_NPATCHES Set to indicate that the vertex buffer is to be used for drawing N-patches.
D3DUSAGE_POINTS Set to indicate that the vertex or index buffer will be used for drawing point sprites. The buffer will be loaded in system memory if software vertex processing is needed to emulate point sprites.
D3DUSAGE_RENDERTARGET The resource will be a render target. D3DUSAGE_RENDERTARGET can only be used with D3DPOOL_DEFAULT.
D3DUSAGE_RTPATCHES Set to indicate that the vertex buffer is to be used for drawing high-order primitives.
D3DUSAGE_SOFTWAREPROCESSING If this flag is used, vertex processing is done in software. If this flag is not used, vertex processing is done in hardware.

The D3DUSAGE_SOFTWAREPROCESSING flag can be set when mixed-mode or software vertex processing (D3DCREATE_MIXED_VERTEXPROCESSING / D3DCREATE_SOFTWARE_VERTEXPROCESSING) is enabled for that device. D3DUSAGE_SOFTWAREPROCESSING must be set for buffers to be used with software vertex processing in mixed mode, but it should not be set for the best possible performance when using hardware index processing in mixed mode (D3DCREATE_HARDWARE_VERTEXPROCESSING). However, setting D3DUSAGE_SOFTWAREPROCESSING is the only option when a single buffer is used with both hardware and software vertex processing. D3DUSAGE_SOFTWAREPROCESSING is allowed for mixed and software devices.

D3DUSAGE_SOFTWAREPROCESSING is used with IDirect3D9::CheckDeviceFormat to find out if a particular texture format can be used as a vertex texture during software vertex processing. If it can, the texture must be created in D3DPOOL_SCRATCH.

D3DUSAGE_WRITEONLY Informs the system that the application writes only to the vertex buffer. Using this flag enables the driver to choose the best memory location for efficient write operations and rendering. Attempts to read from a vertex buffer that is created with this capability will fail. Buffers created with D3DPOOL_DEFAULT that do not specify D3DUSAGE_WRITEONLY might suffer a severe performance penalty.

Usage and Resource Combinations

Usages are either specified when a resource is created, or specified with IDirect3D9::CheckDeviceType to test the capability of an existing resource. The following table identifies which usages can be applied to which resource types.

Usage Vertex buffer create Index buffer create Texture create Cube texture create Volume texture create Surface create Check device format
D3DUSAGE_AUTOGENMIPMAP     x x     x
D3DUSAGE_DEPTHSTENCIL     x x   x x
D3DUSAGE_DMAP     x       x
D3DUSAGE_DONOTCLIP x x          
D3DUSAGE_DYNAMIC x x x x x   x
D3DUSAGE_NPATCHES x x          
D3DUSAGE_POINTS x x          
D3DUSAGE_RTPATCHES x x          
D3DUSAGE_RENDERTARGET     x x   x x
D3DUSAGE_SOFTWAREPROCESSING x x x x x   x
D3DUSAGE_WRITEONLY x x          

Use IDirect3D9::CheckDeviceFormat to check hardware support for these usages.

Each of the resource creation methods is listed here.

  • IDirect3DDevice9::CreateCubeTexture
  • IDirect3DDevice9::CreateDepthStencilSurface
  • IDirect3DDevice9::CreateIndexBuffer
  • IDirect3DDevice9::CreateOffscreenPlainSurface
  • IDirect3DDevice9::CreateRenderTarget
  • IDirect3DDevice9::CreateTexture
  • IDirect3DDevice9::CreateVertexBuffer
  • IDirect3DDevice9::CreateVolumeTexture

The D3DXCreatexxx texturing functions also use some of these constant values for resource creation.

For more information about pool types and their restrictions with certain usages, see D3DPOOL.


再来看看参数Pool的D3DPOOL枚举类型的详细信息:

Defines the memory class that holds the buffers for a resource.

typedef enum D3DPOOL
{
D3DPOOL_DEFAULT = 0,
D3DPOOL_MANAGED = 1,
D3DPOOL_SYSTEMMEM = 2,
D3DPOOL_SCRATCH = 3,
D3DPOOL_FORCE_DWORD = 0x7fffffff,
} D3DPOOL, *LPD3DPOOL;

Constants

D3DPOOL_DEFAULT
Resources are placed in the memory pool most appropriate for the set of usages requested for the given resource. This is usually video memory, including both local video memory and AGP memory. The D3DPOOL_DEFAULT pool is separate from D3DPOOL_MANAGED and D3DPOOL_SYSTEMMEM, and it specifies that the resource is placed in the preferred memory for device access. Note that D3DPOOL_DEFAULT never indicates that either D3DPOOL_MANAGED or D3DPOOL_SYSTEMMEM should be chosen as the memory pool type for this resource. Textures placed in the D3DPOOL_DEFAULT pool cannot be locked unless they are dynamic textures or they are private, FOURCC, driver formats. To access unlockable textures, you must use functions such as IDirect3DDevice9::UpdateSurface, IDirect3DDevice9::UpdateTexture, IDirect3DDevice9::GetFrontBufferData, and IDirect3DDevice9::GetRenderTargetData. D3DPOOL_MANAGED is probably a better choice than D3DPOOL_DEFAULT for most applications. Note that some textures created in driver-proprietary pixel formats, unknown to the Direct3D runtime, can be locked. Also note that - unlike textures - swap chain back buffers, render targets, vertex buffers, and index buffers can be locked. When a device is lost, resources created using D3DPOOL_DEFAULT must be released before calling IDirect3DDevice9::Reset. For more information, see Lost Devices (Direct3D 9).

When creating resources with D3DPOOL_DEFAULT, if video card memory is already committed, managed resources will be evicted to free enough memory to satisfy the request.

D3DPOOL_MANAGED
Resources are copied automatically to device-accessible memory as needed. Managed resources are backed by system memory and do not need to be recreated when a device is lost. See Managing Resources (Direct3D 9) for more information. Managed resources can be locked. Only the system-memory copy is directly modified. Direct3D copies your changes to driver-accessible memory as needed.
D3DPOOL_SYSTEMMEM
Resources are placed in memory that is not typically accessible by the Direct3D device. This memory allocation consumes system RAM but does not reduce pageable RAM. These resources do not need to be recreated when a device is lost. Resources in this pool can be locked and can be used as the source for a IDirect3DDevice9::UpdateSurface or IDirect3DDevice9::UpdateTexture operation to a memory resource created with D3DPOOL_DEFAULT.
D3DPOOL_SCRATCH
Resources are placed in system RAM and do not need to be recreated when a device is lost. These resources are not bound by device size or format restrictions. Because of this, these resources cannot be accessed by the Direct3D device nor set as textures or render targets. However, these resources can always be created, locked, and copied.
D3DPOOL_FORCE_DWORD
Forces this enumeration to compile to 32 bits in size. Without this value, some compilers would allow this enumeration to compile to a size other than 32 bits. This value is not used.

Remarks

All pool types are valid with all resources. This includes: vertex buffers, index buffers, textures, and surfaces.

The following tables indicate restrictions on pool types for render targets, depth stencils, and dynamic and mipmap usages. An x indicates a compatible combination; lack of an x indicates incompatibility.

Pool D3DUSAGE_RENDERTARGET D3DUSAGE_DEPTHSTENCIL
D3DPOOL_DEFAULT x x
D3DPOOL_MANAGED    
D3DPOOL_SCRATCH    
D3DPOOL_SYSTEMMEM    

 

Pool D3DUSAGE_DYNAMIC D3DUSAGE_AUTOGENMIPMAP
D3DPOOL_DEFAULT x x
D3DPOOL_MANAGED   x
D3DPOOL_SCRATCH    
D3DPOOL_SYSTEMMEM x  

For more information about usage types, see D3DUSAGE.

Pools cannot be mixed for different objects contained within one resource (mip levels in a mipmap) and, when a pool is chosen, it cannot be changed.

Applications should use D3DPOOL_MANAGED for most static resources because this saves the application from having to deal with lost devices. (Managed resources are restored by the runtime.) This is especially beneficial for unified memory architecture (UMA) systems. Other dynamic resources are not a good match for D3DPOOL_MANAGED. In fact, index buffers and vertex buffers cannot be created using D3DPOOL_MANAGED together with D3DUSAGE_DYNAMIC.

For dynamic textures, it is sometimes desirable to use a pair of video memory and system memory textures, allocating the video memory using D3DPOOL_DEFAULT and the system memory using D3DPOOL_SYSTEMMEM. You can lock and modify the bits of the system memory texture using a locking method. Then you can update the video memory texture using IDirect3DDevice9::UpdateTexture.


再来看看参数Filter与MipFilter可以使用的枚举类型D3DX_FILTER的详细说明:

The following flags are used to specify which channels in a texture to operate on.

#define Description
D3DX_FILTER_NONE No scaling or filtering will take place. Pixels outside the bounds of the source image are assumed to be transparent black.
D3DX_FILTER_POINT Each destination pixel is computed by sampling the nearest pixel from the source image.
D3DX_FILTER_LINEAR Each destination pixel is computed by sampling the four nearest pixels from the source image. This filter works best when the scale on both axes is less than two.
D3DX_FILTER_TRIANGLE Every pixel in the source image contributes equally to the destination image. This is the slowest of the filters.
D3DX_FILTER_BOX Each pixel is computed by averaging a 2x2(x2) box of pixels from the source image. This filter works only when the dimensions of the destination are half those of the source, as is the case with mipmaps.
D3DX_FILTER_MIRROR_U Pixels off the edge of the texture on the u-axis should be mirrored, not wrapped.
D3DX_FILTER_MIRROR_V Pixels off the edge of the texture on the v-axis should be mirrored, not wrapped.
D3DX_FILTER_MIRROR_W Pixels off the edge of the texture on the w-axis should be mirrored, not wrapped.
D3DX_FILTER_MIRROR Specifying this flag is the same as specifying the D3DX_FILTER_MIRROR_U, D3DX_FILTER_MIRROR_V, and D3DX_FILTER_MIRROR_W flags.
D3DX_FILTER_DITHER The resulting image must be dithered using a 4x4 ordered dither algorithm.
D3DX_FILTER_SRGB_IN Input data is in sRGB (gamma 2.2) color space.
D3DX_FILTER_SRGB_OUT The output data is in sRGB (gamma 2.2) color space.
D3DX_FILTER_SRGB Same as specifying D3DX_FILTER_SRGB_IN | D3DX_FILTER_SRGB_OUT.

Each valid filter must contain exactly one of the following flags: D3DX_FILTER_NONE, D3DX_FILTER_POINT, D3DX_FILTER_LINEAR, D3DX_FILTER_TRIANGLE, or D3DX_FILTER_BOX. In addition, you can use the OR operator to specify zero or more of the following optional flags with a valid filter: D3DX_FILTER_MIRROR_U, D3DX_FILTER_MIRROR_V, D3DX_FILTER_MIRROR_W, D3DX_FILTER_MIRROR, D3DX_FILTER_DITHER, D3DX_FILTER_SRGB_IN, D3DX_FILTER_SRGB_OUT or D3DX_FILTER_SRGB.

Specifying D3DX_DEFAULT for this parameter is usually the equivalent of specifying D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER. However, D3DX_DEFAULT can have different meanings, depending on which method uses the filter. For example:

  • When using D3DXCreateTextureFromFileEx, D3DX_DEFAULT maps to D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER.
  • When using D3DXFilterTexture, D3DX_DEFAULT maps to D3DX_FILTER_BOX if the texture size is a power of two, and D3DX_FILTER_BOX | D3DX_FILTER_DITHER otherwise.

Reference each method to check for information about how D3DX_DEFAULT filter is mapped.


参数pSrcInfo的类型D3DXIMAGE_INFO详细说明:

Returns a description of the original contents of an image file.

typedef struct D3DXIMAGE_INFO {
UINT Width;
UINT Height;
UINT Depth;
UINT MipLevels;
D3DFORMAT Format;
D3DRESOURCETYPE ResourceType;
D3DXIMAGE_FILEFORMAT ImageFileFormat;
} D3DXIMAGE_INFO, *LPD3DXIMAGE_INFO;

Members

Width
Width of original image in pixels.
Height
Height of original image in pixels.
Depth
Depth of original image in pixels.
MipLevels
Number of mip levels in original image.
Format
A value from the D3DFORMAT enumerated type that most closely describes the data in the original image.
ResourceType
Represents the type of the texture stored in the file. It is either D3DRTYPE_TEXTURE, D3DRTYPE_VOLUMETEXTURE, or D3DRTYPE_CubeTexture.
ImageFileFormat
Represents the format of the image file.

参数pPalette的类型PALETTEENTRY的详细说明:

Specifies the color and usage of an entry in a logical palette.

typedef struct PALETTEENTRY {
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY, *LPPALETTEENTRY;

Members

peRed
The red intensity value for the palette entry.
peGreen
The green intensity value for the palette entry.
peBlue
The blue intensity value for the palette entry.
peFlags
The alpha intensity value for the palette entry. Note that as of DirectX 8, this member is treated differently than documented in the Platform SDK.

当背景色为透明黑色的纹理创建以后,就可以将该纹理对象所表示的图象透明地显示在其他图象的上面。

此时,需要设置渲染管道流水线的D3DRS_ALPHATESTENABLE状态值为TRUE,以开启Alpha测试。
 
D3DRS_ALPHATESTENABLE

TRUE to enable per pixel alpha testing. If the test passes, the pixel is processed by the frame buffer. Otherwise, all frame-buffer processing is skipped for the pixel.
The test is done by comparing the incoming alpha value with the reference alpha value, using the comparison function provided by the D3DRS_ALPHAFUNC render state. The reference alpha value is determined by the value set for D3DRS_ALPHAREF. For more information, see Alpha Testing State (Direct3D 9).

The default value of this parameter is FALSE.
 
// enable per pixel alpha testing
_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);

接着设置D3DRS_ALPHAREF的状态为一个0~255范围内的整数值。
 
// specifies a reference alpha value against which pixels are tested
 _d3d_device->SetRenderState(D3DRS_ALPHAREF, 0x01);

再接着设置3DRS_ALPHAFUNC状态为D3DCMPFUNC的枚举值,决定测试将使用比较方式。

Defines the supported compare functions.

typedef enum D3DCMPFUNC
{
D3DCMP_NEVER = 1,
D3DCMP_LESS = 2,
D3DCMP_EQUAL = 3,
D3DCMP_LESSEQUAL = 4,
D3DCMP_GREATER = 5,
D3DCMP_NOTEQUAL = 6,
D3DCMP_GREATEREQUAL = 7,
D3DCMP_ALWAYS = 8,
D3DCMP_FORCE_DWORD = 0x7fffffff,
} D3DCMPFUNC, *LPD3DCMPFUNC;

Constants

D3DCMP_NEVER
Always fail the test.
D3DCMP_LESS
Accept the new pixel if its value is less than the value of the current pixel.
D3DCMP_EQUAL
Accept the new pixel if its value equals the value of the current pixel.
D3DCMP_LESSEQUAL
Accept the new pixel if its value is less than or equal to the value of the current pixel.
D3DCMP_GREATER
Accept the new pixel if its value is greater than the value of the current pixel.
D3DCMP_NOTEQUAL
Accept the new pixel if its value does not equal the value of the current pixel.
D3DCMP_GREATEREQUAL
Accept the new pixel if its value is greater than or equal to the value of the current pixel.
D3DCMP_ALWAYS
Always pass the test.
D3DCMP_FORCE_DWORD
Forces this enumeration to compile to 32 bits in size. Without this value, some compilers would allow this enumeration to compile to a size other than 32 bits. This value is not used.

Remarks

The values in this enumerated type define the supported compare functions for the D3DRS_ZFUNC, D3DRS_ALPHAFUNC, and D3DRS_STENCILFUNC render states.

// Accept the new pixel if its value is greater than the value of the current pixel
_d3d_device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);    

 


好了,该死的DirectX API 使用说明介绍终于可以结束了,来看一个具体的例子。

需要在工程中设置链接d3dx9.lib d3d9.lib dinput8.lib dxguid.lib。
由于文件中用到了GE_APP和GE_INPUT这两个类,它的具体使用说明请参阅 主窗口和DirectInput的封装。


若发现代码中存在错误,敬请指出。

源码及素材下载

来看看头文件TransparentBlend.h的定义:
 

/*************************************************************************************
 [Include File]

 PURPOSE: 
    Defines for Transparent blend.
*************************************************************************************/


#ifndef TRANSPARENT_BLEND_H
#define TRANSPARENT_BLEND_H

struct CUSTOM_VERTEX
{
    
float x, y, z, rhw;
    
float u, v;
};

#define CUSTOM_VERTEX_FVF   (D3DFVF_XYZRHW | D3DFVF_TEX1)

class TRANSPARENT_BLEND
{
private:
    IDirect3D9* _d3d;
    IDirect3DDevice9* _d3d_device;
    IDirect3DVertexBuffer9* _tiger_vertex_buffer;
    IDirect3DVertexBuffer9* _gun_vertex_buffer;
    IDirect3DTexture9* _tiger_texture;
    IDirect3DTexture9* _gun_texture;

public:
    
float m_gun_pos_x, m_gun_pos_y;

public:
    TRANSPARENT_BLEND();
    ~TRANSPARENT_BLEND();

    
bool Create_D3D_Device(HWND hwnd, bool full_screen = true);
    
bool Init_Tiger_Vertex_Buffer();
    
bool Init_Gun_Vertex_Buffer();
    
bool Create_All_Texture();
    
void Render();
    
void Release_COM_Object();
};

#endif

再来看看TransparentBlend.cpp的定义:

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Defines for Transparent blend.
*************************************************************************************/


#include "GE_COMMON.h"
#include "TransparentBlend.h"

//------------------------------------------------------------------------------------
// Constructor, initialize data.
//------------------------------------------------------------------------------------
TRANSPARENT_BLEND::TRANSPARENT_BLEND()
{
    _d3d                 = NULL;
    _d3d_device          = NULL;
    _tiger_vertex_buffer = NULL;
    _gun_vertex_buffer   = NULL;
    _tiger_texture       = NULL;
    _gun_texture         = NULL;

    m_gun_pos_x  = 500.0f;
    m_gun_pos_y  = 180.0f;
}

//------------------------------------------------------------------------------------
// Destructor, release COM object.
//------------------------------------------------------------------------------------
TRANSPARENT_BLEND::~TRANSPARENT_BLEND()
{
    Release_COM_Object();
}

//------------------------------------------------------------------------------------
// Create direct3D interface and direct3D device.
//------------------------------------------------------------------------------------
bool TRANSPARENT_BLEND::Create_D3D_Device(HWND hwnd, bool full_screen)
{
    
// Create a IDirect3D9 object and returns an interace to it.
    _d3d = Direct3DCreate9(D3D_SDK_VERSION);
    
if(_d3d == NULL)
        
return false;

    
// retrieve adapter capability
    D3DCAPS9 d3d_caps;    
    _d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3d_caps);
    
    
bool hardware_process_enable = (d3d_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ? true : false);

    
// Retrieves the current display mode of the adapter.
    D3DDISPLAYMODE display_mode;
    
if(FAILED(_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return false;

    
// set present parameter for direct3D device
    D3DPRESENT_PARAMETERS present_param;

    ZeroMemory(&present_param, 
sizeof(present_param));

    present_param.BackBufferWidth      = WINDOW_WIDTH;
    present_param.BackBufferHeight     = WINDOW_HEIGHT;
    present_param.BackBufferFormat     = display_mode.Format;
    present_param.BackBufferCount      = 1;
    present_param.hDeviceWindow        = hwnd;
    present_param.Windowed             = !full_screen;
    present_param.SwapEffect           = D3DSWAPEFFECT_FLIP;
    present_param.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

    
// Creates a device to represent the display adapter.
    DWORD behavior_flags;

    behavior_flags = hardware_process_enable ?
 D3DCREATE_HARDWARE_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING;

    
if(FAILED(_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, behavior_flags, 
                                 &present_param, &_d3d_device)))
    {
        
return false;
    }
    
    
// create successfully
    return true;
}

//------------------------------------------------------------------------------------
// Initialize vertex buffer for tiger image. 
//------------------------------------------------------------------------------------
bool TRANSPARENT_BLEND::Init_Tiger_Vertex_Buffer()
{
    CUSTOM_VERTEX tiger_vertex[] =
    {
        {0.0f,   0.0f,   0.0f, 1.0f, 0.0f, 0.0f},
        {800.0f, 0.0f,   0.0f, 1.0f, 1.0f, 0.0f},
        {0.0f,   600.0f, 0.0f, 1.0f, 0.0f, 1.0f},
        {800.0f, 600.0f, 0.0f, 1.0f, 1.0f, 1.0f}            
    };

    BYTE* vertex_data;

    
// create vertex buffer
    if(FAILED(_d3d_device->CreateVertexBuffer(4 * sizeof(CUSTOM_VERTEX), 0, CUSTOM_VERTEX_FVF,
                            D3DPOOL_MANAGED, &_tiger_vertex_buffer, NULL)))
    {
        
return false;
    }

    
// get data pointer to vertex buffer
    if(FAILED(_tiger_vertex_buffer->Lock(0, 0, (void **) &vertex_data, 0)))
        
return false;

    
// copy custom vertex data into vertex buffer
    memcpy(vertex_data, tiger_vertex, sizeof(tiger_vertex));

    
// unlock vertex buffer
    _tiger_vertex_buffer->Unlock();

    
return true;
}

//------------------------------------------------------------------------------------
// Initialize vertex buffer for gun image. 
//------------------------------------------------------------------------------------
bool TRANSPARENT_BLEND::Init_Gun_Vertex_Buffer()
{
    CUSTOM_VERTEX gun_vertex[] =
    {
        {m_gun_pos_x,        m_gun_pos_y,         0.0f,   1.0f,   0.0f,   0.0f},
        {m_gun_pos_x+130.0f, m_gun_pos_y,         0.0f,   1.0f,   1.0f,   0.0f},
        {m_gun_pos_x,        m_gun_pos_y+130.0f,  0.0f,   1.0f,   0.0f,   1.0f},
        {m_gun_pos_x+130.0f, m_gun_pos_y+130.0f,  0.0f,   1.0f,   1.0f,   1.0f}        
    };

    BYTE* vertex_data;

    
// create vertex buffer
    if(FAILED(_d3d_device->CreateVertexBuffer(4 * sizeof(CUSTOM_VERTEX), 0, CUSTOM_VERTEX_FVF,
                            D3DPOOL_MANAGED, &_gun_vertex_buffer, NULL)))
    {
        
return false;
    }

    
// get data pointer to vertex buffer
    if(FAILED(_gun_vertex_buffer->Lock(0, 0, (void **) &vertex_data, 0)))
        
return false;

    
// copy custom vertex data into vertex buffer
    memcpy(vertex_data, gun_vertex, sizeof(gun_vertex));

    
// unlock vertex buffer
    _gun_vertex_buffer->Unlock();

    
return true;

}

//------------------------------------------------------------------------------------
// Create all texture interfaces from file.
//------------------------------------------------------------------------------------
bool TRANSPARENT_BLEND::Create_All_Texture()
{
    
// Creates tiger texture from file
    if(FAILED(D3DXCreateTextureFromFile(_d3d_device, "tiger.jpg", &_tiger_texture)))
    {
        MessageBox(NULL, "Create texture interface for image tiger failed.", "ERROR", MB_OK);
        
return false;
    }

    
// Creates gun texture from file
    if(FAILED(D3DXCreateTextureFromFileEx(
        _d3d_device,        
// Pointer to an IDirect3DDevice9 interface
        "gun.bmp",          // Pointer to a string that specifies the filename
        D3DX_DEFAULT,       // Width in pixels
        D3DX_DEFAULT,       // Height in pixels.
        D3DX_DEFAULT,       // Number of mip levels requested, D3DX_DEFAULT means a complete mipmap chain is created.
        0,                  // Usage
        D3DFMT_A8R8G8B8,    // describing the requested pixel format for the texture
        D3DPOOL_MANAGED,    // describing the memory class into which the texture should be placed
        D3DX_FILTER_TRIANGLE,           // how the image is filtered (Filter)
        D3DX_FILTER_TRIANGLE,           // how the image is filtered (MipFilter)
        D3DCOLOR_RGBA(0, 255, 0, 255),  // D3DCOLOR value to replace with transparent black
        NULL,               // Pointer to a D3DXIMAGE_INFO structure to be filled in with a description of the data 
                            // in the source image file
        NULL,               // Pointer to a PALETTEENTRY structure, representing a 256-color palette to fill in
        &_gun_texture)))    // Address of a pointer to an IDirect3DTexture9 interface
    {
        MessageBox(NULL, "Create texture interface for image gun failed.", "ERROR", MB_OK);
        
return false;
    }        

    
return true;
}

//------------------------------------------------------------------------------------
// Render image tiger and gun.
//------------------------------------------------------------------------------------
void TRANSPARENT_BLEND::Render()
{
    
if(_d3d_device == NULL)
        
return;

    
// clear back surface with color black
    _d3d_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0, 0);

    
// indicate d3d device begin draw scene
    _d3d_device->BeginScene();

    
// 1) draw for image tiger

    // Binds a vertex buffer to a device data stream.
    _d3d_device->SetStreamSource(0, _tiger_vertex_buffer, 0, sizeof(CUSTOM_VERTEX));

    
// Sets the current vertex stream declaration.
    _d3d_device->SetFVF(CUSTOM_VERTEX_FVF);

    
// Assigns a texture to a stage for a device.
    _d3d_device->SetTexture(0, _tiger_texture);
    
    
// draw tiger image
    _d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    
// 2) draw for image gun

    // Binds a vertex buffer to a device data stream
    _d3d_device->SetStreamSource(0, _gun_vertex_buffer, 0, sizeof(CUSTOM_VERTEX));

    
// Assigns a texture to a stage for a device
    _d3d_device->SetTexture(0, _gun_texture);
    
    
// enable per pixel alpha testing
    _d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);

    
// specifies a reference alpha value against which pixels are tested
    _d3d_device->SetRenderState(D3DRS_ALPHAREF, 0x01);

    
// Accept the new pixel if its value is greater than the value of the current pixel
    _d3d_device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);    
    
    
// draw gun image
    _d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    
// disable per pixel alpha testing now
    _d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

    
// indicate d3d device to end scene
    _d3d_device->EndScene();

    
// Presents the contents of the next buffer in the sequence of back buffers owned by the device.
    _d3d_device->Present(NULL, NULL, NULL, NULL);
}

//------------------------------------------------------------------------------------
// Release all COM object.
//------------------------------------------------------------------------------------
void TRANSPARENT_BLEND::Release_COM_Object()
{
    Safe_Release(_tiger_texture);
    Safe_Release(_gun_texture);
    Safe_Release(_tiger_vertex_buffer);
    Safe_Release(_gun_vertex_buffer);
    Safe_Release(_d3d_device);
    Safe_Release(_d3d);
}

注释很详尽,看懂应该没问题了。

来看看测试文件main.cpp的定义:

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Test for transparent blending.
*************************************************************************************/


#define DIRECTINPUT_VERSION 0x0800

#include "GE_COMMON.h"
#include "GE_APP.h"
#include "GE_INPUT.h"
#include "TransparentBlend.h"

#pragma warning(disable : 4305 4996)

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    GE_APP ge_app;
    GE_INPUT ge_input;
    TRANSPARENT_BLEND tb;

    MSG msg = {0};

    
// create window
    if(! ge_app.Create_Window("Transparent blending test", instance, cmd_show))
        
return false;

    HWND hwnd = ge_app.Get_Window_Handle();    

    SetWindowPos(hwnd, 0, 0,0,0,0, SWP_NOSIZE);
    SetCursorPos(0, 0);    

    
// create direct input
    ge_input.Create_Input(instance, hwnd);    
    
    
// Create direct3D interface and direct3D device.
    if(! tb.Create_D3D_Device(hwnd, false))
        
return false;
  
    
// initialize vertex buffer for tiger
    if(! tb.Init_Tiger_Vertex_Buffer())
        
return false;

    
// initialize vertex buffer for gun
    if(! tb.Init_Gun_Vertex_Buffer())
        
return false;

    
// Create all texture interfaces from file.
    tb.Create_All_Texture();
    
    
// draw tiger and gun
    tb.Render();    

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 0,0 , PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
else
        {
            
// get keyboard input
            if(ge_input.Read_Keyboard())
            {
                
if(ge_input.Is_Key_Pressed(DIK_ESCAPE))
                    PostQuitMessage(0);

                
bool key_right_pressed  = ge_input.Is_Key_Pressed(DIK_RIGHT);
                
bool key_left_pressed   = ge_input.Is_Key_Pressed(DIK_LEFT);
                
bool key_up_pressed     = ge_input.Is_Key_Pressed(DIK_UP);
                
bool key_down_pressed   = ge_input.Is_Key_Pressed(DIK_DOWN);

                
if(key_right_pressed || key_left_pressed || key_up_pressed || key_down_pressed)
                {                    
                    
if(key_right_pressed)       // press key "→" 
                        tb.m_gun_pos_x += 5.5f;    

                    
if(key_left_pressed)        // press key "←" 
                        tb.m_gun_pos_x -= 5.5f;  

                    
if(key_up_pressed)          // press key "↑" 
                        tb.m_gun_pos_y -= 5.5f; 

                    
if(key_down_pressed)        // press key "↓" 
                        tb.m_gun_pos_y += 5.5f;  

                    
// reset vertex buffer for gun
                    tb.Init_Gun_Vertex_Buffer();

                    
// render tiger and gun
                    tb.Render();
                }
            }
        }
    }    

    UnregisterClass(WINDOW_CLASS_NAME, instance);

    
return true;
}

运行效果:


本篇是D3D中的Alpha颜色混合(1) 的后续篇,主要讲利用ID3DXSprite来实现图片间的颜色透明效果。

在一幅图象上透明的显示另一幅图象,是Alpha颜色混合的一个典型应用,如下图所示,瞄准镜图象背景透明地显示在老虎背景图象上。



实现瞄准镜的背景透明显示,首先需要准备如下两张图:

                                               
  瞄准镜源图  (图1)                                    瞄准镜Alpha通道(屏蔽图)(图2)

Alpha通道图(屏蔽图)的黑色像素对应的源图像素不被显示出来,Alpha通道图(屏蔽图)的白色像素对应的源图像素会显示出来。

Alpha 通道图(屏蔽图)的像素颜色值是作为DirectX颜色结构体的alpha分量值来使用的。图2所示的黑色像素将产生一个为0的Alpha值,而白色像素将产生一个为1的Alpha值。如果此时将渲染管道流水线的D3DRS_SRCBLEND源混合因子参数设置为D3DBLEND_SRCALPHA,目标混合因子参数设置为D3DBLEND_INVSRCALPHA,然后先绘制图1所示的纹理图,再绘制图2所示的纹理图,那么混合后的结果将是图1所示的白色背景不被显示出来,仅显示一个圆和一个十字形,正因为Alpha通道图具有以上的屏蔽效果,因此图2所示的Alpha通道图也称为屏蔽图。

如果直接采用上面的方法实现图形间的透明效果,那么需要进行3次纹理贴图,并设置好相应的混合参数。第一次先绘制出老虎背景图,接着绘制瞄准镜源图,最后绘制瞄准镜Alpha通道图。这样做显然是比较繁琐的,因此可用DirectX提供的ID3DXSprite接口进行绘制。

首先,在DirectX安装目录下的Utilities目录下执行DxTex.exe,这是DirectX提供的用于生成纹理图象的Alpha通道图的工具。

(注:如果你执行DxTex.exe时系统提示如下信息:

Unable to create Direct3D Device. Please make sure your desktop color depth is 16 or 32 bit, and that d3dref.dll is installed.

请参考解决方案 运行DxTex.exe碰到的问题

首先执行"File - Open"菜单,将图1所示的瞄准镜源图打开,然后执行"Change Surface Format ..." 菜单,重新设置bmp图象的像素颜色格式为A8R8G8B8,即添加一个Alpha颜色值。

              

执行新增加的"File - Open onto alpha channel of this texture..."菜单,弹出一个文件打开对话框,选择图2所示的瞄准镜Alpha通道图并打开,DxTex将自动把Alpha通道图的屏蔽信息插入图象中。如下图所示,DxTex默认的淡蓝色背景色表示该像素颜色已为透明色。最后,执行"File - Save"菜单,将同时具有源图信息和Alpha通道信息的图象保存为DDS格式的gun.dds。



生成瞄准镜的DDS文件后,就可以利用d3dx9.lib库的ID3DXSprite提供的方法,将瞄准镜背景透明地显示出来。ID3DXSprite接口对象是通过D3DXCreateSprite函数进行创建的,来看看该函数的具体使用信息:

Creates a sprite object which is associated with a particular device. Sprite objects are used to draw 2D images to the screen.

HRESULT D3DXCreateSprite(
LPDIRECT3DDEVICE9 pDevice,
LPD3DXSPRITE * ppSprite
);

Parameters

pDevice
[in] Pointer to an IDirect3DDevice9 interface, the device to be associated with the sprite.
ppSprite
[out] Address of a pointer to an ID3DXSprite interface. This interface allows the user to access sprite functions.

Return Values

If the function succeeds, the return value is S_OK. If the function fails, the return value can be one of the following: D3DERR_INVALIDCALL, E_OUTOFMEMORY.

Remarks

This interface can be used to draw two dimensional images in screen space of the associated device.


再来看看SDK文档提供的有关ID3DXSprite的使用说明:

The ID3DXSprite interface provides a set of methods that simplify the process of drawing sprites using Microsoft Direct3D.

ID3DXSprite Members

Method Description
ID3DXSprite::Begin Prepares a device for drawing sprites.
ID3DXSprite::Draw Adds a sprite to the list of batched sprites.
ID3DXSprite::End Calls ID3DXSprite::Flush and restores the device state to how it was before ID3DXSprite::Begin was called.
ID3DXSprite::Flush Forces all batched sprites to be submitted to the device. Device states remain as they were after the last call to ID3DXSprite::Begin. The list of batched sprites is then cleared.
ID3DXSprite::GetDevice Retrieves the device associated with the sprite object.
ID3DXSprite::GetTransform Gets the sprite transform.
ID3DXSprite::OnLostDevice Use this method to release all references to video memory resources and delete all stateblocks. This method should be called whenever a device is lost or before resetting a device.
ID3DXSprite::OnResetDevice Use this method to re-acquire resources and save initial state.
ID3DXSprite::SetTransform Sets the sprite transform.
ID3DXSprite::SetWorldViewLH Sets the left-handed world-view transform for a sprite. A call to this method is required before billboarding or sorting sprites.
ID3DXSprite::SetWorldViewRH Sets the right-handed world-view transform for a sprite. A call to this method is required before billboarding or sorting sprites.

Remarks

The ID3DXSprite interface is obtained by calling the D3DXCreateSprite function.

The application typically first calls ID3DXSprite::Begin, which allows control over the device render state, alpha blending, and sprite transformation and sorting. Then for each sprite to be displayed, call ID3DXSprite::Draw. ID3DXSprite::Draw can be called repeatedly to store any number of sprites. To display the batched sprites to the device, call ID3DXSprite::End or ID3DXSprite::Flush.

The LPD3DXSPRITE type is defined as a pointer to the ID3DXSprite interface.

typedef interface ID3DXSprite ID3DXSprite;
typedef interface ID3DXSprite *LPD3DXSPRITE;

Begin方法和End方法表明绘制精灵图象的开始和结束,这是一对配套函数,必须用在IDirect3DDevice9::BeginScene和IDirect3DDevice9::EndScene的调用之间。

接着来看看Begin方法的使用说明:

Prepares a device for drawing sprites.

HRESULT Begin(
DWORD Flags
);

Parameters

Flags
[in] Combination of zero or more flags that describe sprite rendering options. For this method, the valid flags are:
  • D3DXSPRITE_ALPHABLEND
  • D3DXSprite__BILLBOARD
  • D3DXSPRITE_DONOTMODIFY_RENDERSTATE
  • D3DXSPRITE_DONOTSAVESTATE
  • D3DXSPRITE_OBJECTSPACE
  • D3DXSprite__SORT_DEPTH_BACKTOFRONT
  • D3DXSprite__SORT_DEPTH_FRONTTOBACK
  • D3DXSprite__SORT_TEXTURE
For a description of the flags and for information on how to control device state capture and device view transforms, see D3DXSPRITE.

Return Values

If the method succeeds, the return value is S_OK. If the method fails, the return value can be one of the following: D3DERR_INVALIDCALL, D3DERR_OUTOFVIDEOMEMORY, D3DXERR_INVALIDDATA, E_OUTOFMEMORY.

Remarks

This method must be called from inside a IDirect3DDevice9::BeginScene . . . IDirect3DDevice9::EndScene sequence. ID3DXSprite::Begin cannot be used as a substitute for either IDirect3DDevice9::BeginScene or ID3DXRenderToSurface::BeginScene.

This method will set the following states on the device.

Render States:

Type (D3DRENDERSTATETYPE) Value
D3DRS_ALPHABLENDENABLE TRUE
D3DRS_ALPHAFUNC D3DCMP_GREATER
D3DRS_ALPHAREF 0x00
D3DRS_ALPHATESTENABLE AlphaCmpCaps
D3DRS_BLENDOP D3DBLENDOP_ADD
D3DRS_CLIPPING TRUE
D3DRS_CLIPPLANEENABLE FALSE
D3DRS_COLORWRITEENABLE D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED
D3DRS_CULLMODE D3DCULL_NONE
D3DRS_DESTBLEND D3DBLEND_INVSRCALPHA
D3DRS_DIFFUSEMATERIALSOURCE D3DMCS_COLOR1
D3DRS_ENABLEADAPTIVETESSELLATION FALSE
D3DRS_FILLMODE D3DFILL_SOLID
D3DRS_FOGENABLE FALSE
D3DRS_INDEXEDVERTEXBLENDENABLE FALSE
D3DRS_LIGHTING FALSE
D3DRS_RANGEFOGENABLE FALSE
D3DRS_SEPARATEALPHABLENDENABLE FALSE
D3DRS_SHADEMODE D3DSHADE_GOURAUD
D3DRS_SPECULARENABLE FALSE
D3DRS_SRCBLEND D3DBLEND_SRCALPHA
D3DRS_SRGBWRITEENABLE FALSE
D3DRS_STENCILENABLE FALSE
D3DRS_VERTEXBLEND FALSE
D3DRS_WRAP0 0

Texture Stage States:

Stage Identifier Type (D3DTEXTURESTAGESTATETYPE) Value
0 D3DTSS_ALPHAARG1 D3DTA_TEXTURE
0 D3DTSS_ALPHAARG2 D3DTA_DIFFUSE
0 D3DTSS_ALPHAOP D3DTOP_MODULATE
0 D3DTSS_COLORARG1 D3DTA_TEXTURE
0 D3DTSS_COLORARG2 D3DTA_DIFFUSE
0 D3DTSS_COLOROP D3DTOP_MODULATE
0 D3DTSS_TEXCOORDINDEX 0
0 D3DTSS_TEXTURETRANSFORMFLAGS D3DTTFF_DISABLE
1 D3DTSS_ALPHAOP D3DTOP_DISABLE
1 D3DTSS_COLOROP D3DTOP_DISABLE

Sampler States:

Sampler Stage Index Type (D3DSAMPLERSTATETYPE) Value
0 D3DSAMP_ADDRESSU D3DTADDRESS_CLAMP
0 D3DSAMP_ADDRESSV D3DTADDRESS_CLAMP
0 D3DSAMP_MAGFILTER D3DTEXF_ANISOTROPIC if TextureFilterCaps includes D3DPTFILTERCAPS_MAGFANISOTROPIC; otherwise D3DTEXF_LINEAR
0 D3DSAMP_MAXMIPLEVEL 0
0 D3DSAMP_MAXANISOTROPY MaxAnisotropy
0 D3DSAMP_MINFILTER D3DTEXF_ANISOTROPIC if TextureFilterCaps includes D3DPTFILTERCAPS_MINFANISOTROPIC; otherwise D3DTEXF_LINEAR
0 D3DSAMP_MIPFILTER D3DTEXF_LINEAR if TextureFilterCaps includes D3DPTFILTERCAPS_MIPFLINEAR; otherwise D3DTEXF_POINT
0 D3DSAMP_MIPMAPLODBIAS 0
0 D3DSAMP_SRGBTEXTURE 0

Note    This method disables N-patches.


参数Flags允许设置的D3DXSPRITE模式如下所示:

The following flags are used to specify sprite rendering options to the flags parameter in the ID3DXSprite::Begin method:

#define Description
D3DXSPRITE_DONOTSAVESTATE The device state is not to be saved or restored when ID3DXSprite::Begin or ID3DXSprite::End is called.
D3DXSPRITE_DONOTMODIFY_RENDERSTATE The device render state is not to be changed when ID3DXSprite::Begin is called. The device is assumed to be in a valid state to draw vertices containing UsageIndex = 0 in the D3DDECLUSAGE_POSITION, D3DDECLUSAGE_TEXCOORD, and D3DDECLUSAGE_COLOR data.
D3DXSPRITE_OBJECTSPACE The world, view, and projection transforms are not modified. The transforms currently set to the device are used to transform the sprites when the batched sprites are drawn (when ID3DXSprite::Flush or ID3DXSprite::End is called). If this flag is not specified, then world, view, and projection transforms are modified so that sprites are drawn in screen-space coordinates.
D3DXSPRITE_BILLBOARD Each sprite will be rotated about its center so that it is facing the viewer. ID3DXSprite::SetWorldViewLH or ID3DXSprite::SetWorldViewRH must be called first.
D3DXSPRITE_ALPHABLEND Enables alpha blending with D3DRS_ALPHATESTENABLE set to TRUE (for nonzero alpha). D3DBLEND_SRCALPHA will be the source blend state, and D3DBLEND_INVSRCALPHA will be the destination blend state in calls to IDirect3DDevice9::SetRenderState. See Alpha Blending State (Direct3D 9). ID3DXFont expects this flag to be set when drawing text.
D3DXSPRITE_SORT_TEXTURE Sort sprites by texture prior to drawing. This can improve performance when drawing non-overlapping sprites of uniform depth.

You may also combine D3DXSPRITE_SORT_TEXTURE with either D3DXSPRITE_SORT_DEPTH_FRONTTOBACK or D3DXSPRITE_SORT_DEPTH_BACKTOFRONT. This will sort the list of sprites by depth first and texture second.

D3DXSPRITE_SORT_DEPTH_FRONTTOBACK Sprites are sorted by depth in front-to-back order prior to drawing. This procedure is recommended when drawing opaque sprites of varying depths.

You may combine D3DXSPRITE_SORT_DEPTH_FRONTTOBACK with D3DXSPRITE_SORT_TEXTURE to sort first by depth, and second by texture.

D3DXSPRITE_SORT_DEPTH_BACKTOFRONT Sprites are sorted by depth in back-to-front order prior to drawing. This procedure is recommended when drawing transparent sprites of varying depths.

You may combine D3DXSPRITE_SORT_DEPTH_BACKTOFRONT with D3DXSPRITE_SORT_TEXTURE to sort first by depth, and second by texture.

D3DXSPRITE_DO_NOT_ADDREF_TEXTURE Disables calling AddRef() on every draw, and Release() on Flush() for better performance.

再来看看End方法的使用信息:

Calls ID3DXSprite::Flush and restores the device state to how it was before ID3DXSprite::Begin was called.

HRESULT End();

Parameters

None.

Return Values

If the method succeeds, the return value is S_OK. If the method fails, the following value will be returned.

D3DERR_INVALIDCALL

Remarks

ID3DXSprite::End cannot be used as a substitute for either IDirect3DDevice9::EndScene or ID3DXRenderToSurface::EndScene.


Draw方法用来在指定位置绘制精灵,来看看它的使用说明:

Adds a sprite to the list of batched sprites.

HRESULT Draw(
LPDIRECT3DTEXTURE9 pTexture,
CONST RECT * pSrcRect,
CONST D3DXVECTOR3 * pCenter,
CONST D3DXVECTOR3 * pPosition,
D3DCOLOR Color
);

Parameters

pTexture
[in] Pointer to an IDirect3DTexture9 interface that represents the sprite texture.
pSrcRect
[in] Pointer to a RECT structure that indicates the portion of the source texture to use for the sprite. If this parameter is NULL, then the entire source image is used for the sprite.
pCenter
[in] Pointer to a D3DXVECTOR3 vector that identifies the center of the sprite. If this argument is NULL, the point (0,0,0) is used, which is the upper-left corner.
pPosition
[in] Pointer to a D3DXVECTOR3 vector that identifies the position of the sprite. If this argument is NULL, the point (0,0,0) is used, which is the upper-left corner.
Color
[in] D3DCOLOR type. The color and alpha channels are modulated by this value. A value of 0xFFFFFFFF maintains the original source color and alpha data. Use the D3DCOLOR_RGBA macro to help generate this color.

Return Values

If the method succeeds, the return value is S_OK. If the method fails, the return value can be one of the following: D3DERR_INVALIDCALL, D3DXERR_INVALIDDATA.

Remarks

To scale, rotate, or translate a sprite, call ID3DXSprite::SetTransform with a matrix that contains the scale, rotate, and translate (SRT) values, before calling ID3DXSprite::Draw. For information about setting SRT values in a matrix, see Matrix Transforms.

 


好了,现在来看一个例子:

需要在工程中设置链接d3dx9.lib d3d9.lib dinput8.lib dxguid.lib。
由于文件中用到了GE_APP和GE_INPUT这两个类,它的具体使用说明请参阅主窗口和DirectInput的封装。


若发现代码中存在错误,敬请指出。

源码及素材下载

来看看shoot.h的定义:
 

/*************************************************************************************
 [Include File]

 PURPOSE: 
    Define for alpha blending test, use ID3DXSprit interface.
*************************************************************************************/


#ifndef SHOOT_H
#define SHOOT_H

class SHOOT
{
private:
    IDirect3D9* _d3d;
    IDirect3DDevice9* _d3d_device;
    IDirect3DTexture9* _texture_tiger;
    IDirect3DTexture9* _texture_gun;
    ID3DXSprite* _sprite_tiger;
    ID3DXSprite* _sprite_gun;

public:
    float m_gun_pos_x, m_gun_pos_y;

public:
    SHOOT();
    ~SHOOT();
    
    bool Create_D3D_Device(HWND hwnd, bool full_screen = true);
    bool Create_Sprite();
    bool Create_Sprite_Texture(IDirect3DTexture9** texture, const char* image_file);
    void Render();
    void Release_COM_Object();
};

#endif

再来看看shoot.cpp的定义:

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Define for alpha blending test, use ID3DXSprit interface.
*************************************************************************************/


#include "GE_COMMON.h"
#include "GE_INPUT.h"
#include "shoot.h"

//------------------------------------------------------------------------------------
// Constructor, initialize data.
//------------------------------------------------------------------------------------
SHOOT::SHOOT()
{
    _d3d            = NULL;
    _d3d_device     = NULL;
    _texture_tiger  = NULL;
    _texture_gun    = NULL;
    _sprite_tiger   = NULL;
    _sprite_gun     = NULL;

    m_gun_pos_x     = 500.0f;
    m_gun_pos_y     = 180.0f;
}

//------------------------------------------------------------------------------------
// Destructor, release COM object.
//------------------------------------------------------------------------------------
SHOOT::~SHOOT()
{
    Release_COM_Object();
}

//------------------------------------------------------------------------------------
// Create direct3D interface and direct3D device.
//------------------------------------------------------------------------------------
bool SHOOT::Create_D3D_Device(HWND hwnd, bool full_screen)
{
    
// Create a IDirect3D9 object and returns an interace to it.
    _d3d = Direct3DCreate9(D3D_SDK_VERSION);
    
if(_d3d == NULL)
        
return false;

    
// retrieve adapter capability
    D3DCAPS9 d3d_caps;    
    _d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3d_caps);
    
    
bool hardware_process_enable = (d3d_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ? true : false);

    
// Retrieves the current display mode of the adapter.
    D3DDISPLAYMODE display_mode;
    
if(FAILED(_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        
return false;

    
// set present parameter for direct3D device
    D3DPRESENT_PARAMETERS present_param;

    ZeroMemory(&present_param, 
sizeof(present_param));

    present_param.BackBufferWidth      = WINDOW_WIDTH;
    present_param.BackBufferHeight     = WINDOW_HEIGHT;
    present_param.BackBufferFormat     = display_mode.Format;
    present_param.BackBufferCount      = 1;
    present_param.hDeviceWindow        = hwnd;
    present_param.Windowed             = !full_screen;
    present_param.SwapEffect           = D3DSWAPEFFECT_FLIP;
    present_param.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

    
// Creates a device to represent the display adapter.
    DWORD behavior_flags;

    behavior_flags = hardware_process_enable ?
 D3DCREATE_HARDWARE_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING;

    
if(FAILED(_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, behavior_flags, 
                                 &present_param, &_d3d_device)))
    {
        
return false;
    }
    
    
// create successfully
    return true;
}

//------------------------------------------------------------------------------------
// Creates two sprite objects which is associated with a particular device. 
// Sprite objects are used to draw 2D images to the screen.
//------------------------------------------------------------------------------------
bool SHOOT::Create_Sprite()
{
    
// create sprite for image tiger
    if(FAILED(D3DXCreateSprite(_d3d_device, &_sprite_tiger)))
    {
        MessageBox(NULL, "Create sprite tiger failed!", "ERROR", MB_OK);
        
return false;
    }

    
// create sprite for image gun
    if(FAILED(D3DXCreateSprite(_d3d_device, &_sprite_gun)))
    {
        MessageBox(NULL, "Create sprite gun failed!", "ERROR", MB_OK);
        
return false;
    }

    
// create texture for tiger sprite
    if(! Create_Sprite_Texture(&_texture_tiger, "tiger.jpg"))
    {
        MessageBox(NULL, "Create texture interface for sprite tiger failed.", "ERROR", MB_OK);
        
return false;
    }

    
// create texture for gun sprite
    if(! Create_Sprite_Texture(&_texture_gun, "gun.dds"))
    {
        MessageBox(NULL, "Create texture interface for sprite gun failed.", "ERROR", MB_OK);
        
return false;
    }

    
return true;
}

//------------------------------------------------------------------------------------
// Creates a texture from image file.
//------------------------------------------------------------------------------------
bool SHOOT::Create_Sprite_Texture(IDirect3DTexture9** texture, const char* image_file)
{
    
if(FAILED(D3DXCreateTextureFromFile(_d3d_device, image_file, texture)))
        
return false;

    
return true;
}

//------------------------------------------------------------------------------------
// Draw alpha blend image.
//------------------------------------------------------------------------------------
void SHOOT::Render()
{
    
if(_d3d_device == NULL)
        
return;

    
// clear surface with black
    _d3d_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0, 0);

    
// 1) draw tiger

    // begin scene
    _d3d_device->BeginScene();    

    
// Prepares a device for drawing sprites.
    _sprite_tiger->Begin(D3DXSPRITE_ALPHABLEND);
    
    
// Adds a sprite to the list of batched sprites.
    if(FAILED(_sprite_tiger->Draw(_texture_tiger, NULL, NULL, NULL, 0xFFFFFFFF)))
    {
        MessageBox(NULL, "Draw image tiger failed.", "ERROR", MB_OK);
        
return;
    }

    
// restores the device state to how it was before ID3DXSprite::Begin was called.
    _sprite_tiger->End();

    
// 2) draw gun

    // prepare for begin draw 
    _sprite_gun->Begin(D3DXSPRITE_ALPHABLEND);

    D3DXVECTOR3 pos(m_gun_pos_x, m_gun_pos_y, 0);

    
// draw gun now
    if(FAILED(_sprite_gun->Draw(_texture_gun, NULL, NULL, &pos, 0xFFFFFFFF)))
    {
        MessageBox(NULL, "Draw gun failed.", "ERROR", MB_OK);
        
return;
    }

    
// indicate end draw for sprite gun
    _sprite_gun->End();

    
// end scene
    _d3d_device->EndScene();

    
// Presents the contents of the next buffer in the sequence of back buffers owned by the device.
    _d3d_device->Present(NULL, NULL, NULL, NULL);
}

//------------------------------------------------------------------------------------
// Release all COM object.
//------------------------------------------------------------------------------------
void SHOOT::Release_COM_Object()
{
    Safe_Release(_texture_tiger);
    Safe_Release(_texture_gun);
    Safe_Release(_sprite_tiger);
    Safe_Release(_sprite_gun);    
    Safe_Release(_d3d_device);
    Safe_Release(_d3d);
}

Create_Sprite方法用来创建背景图和瞄准镜的ID3DXSprite精灵接口对象和纹理接口对象, Create_Sprite_Texture方法根据图象文件创建精灵的纹理对象,这里使用DDS格式的文件创建出一个背景透明的瞄准器精灵对象。 Render方法调用ID3DXSprite接口的Begin,Draw,End方法绘制出背景图和背景透明的瞄准镜图象。

再来看看测试代码main.cpp:

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Test for sprite draw with alpha blending.
*************************************************************************************/


#define DIRECTINPUT_VERSION 0x0800

#include "GE_COMMON.h"
#include "GE_APP.h"
#include "GE_INPUT.h"
#include "shoot.h"

#pragma warning(disable : 4305 4996)

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    GE_APP ge_app;
    GE_INPUT ge_input;
    SHOOT shoot;

    MSG msg = {0};

    
// create window
    if(! ge_app.Create_Window("Sprite draw test with Alpha blending", instance, cmd_show))
        
return false;

    HWND hwnd = ge_app.Get_Window_Handle();    

    SetWindowPos(hwnd, 0, 0,0,0,0, SWP_NOSIZE);
    SetCursorPos(0, 0);    

    
// create direct input
    ge_input.Create_Input(instance, hwnd);    
    
    
// Create direct3D interface and direct3D device.
    if(! shoot.Create_D3D_Device(hwnd, false))
        
return false;

    
// create all sprites
    if(! shoot.Create_Sprite())
        
return false;

    
// Draw all cones
    shoot.Render();

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 0,0 , PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
else
        {
            
// get keyboard input
            if(ge_input.Read_Keyboard())
            {
                
if(ge_input.Is_Key_Pressed(DIK_ESCAPE))
                    PostQuitMessage(0);

                
if(ge_input.Is_Key_Pressed(DIK_RIGHT))
                {
                    shoot.m_gun_pos_x += 5.5;
                    shoot.Render();
                }

                
if(ge_input.Is_Key_Pressed(DIK_LEFT))
                {
                    shoot.m_gun_pos_x -= 5.5;
                    shoot.Render();
                }

                
if(ge_input.Is_Key_Pressed(DIK_UP))
                {
                    shoot.m_gun_pos_y -= 5.5;
                    shoot.Render();
                }

                
if(ge_input.Is_Key_Pressed(DIK_DOWN))
                {
                    shoot.m_gun_pos_y += 5.5;
                    shoot.Render();
                }
            }
        }
    }    

    UnregisterClass(WINDOW_CLASS_NAME, instance);

    
return true;
}
 

提示:

阅读本文需要一定的3D图形学和DirectX9基础,如果你发现阅读困难,请参阅D3D中的材质和光照处理
本文用到的坐标系统变换函数请参阅DirectX 9的坐标系统变换



渲染管道流水线通常需要将来自顶点的颜色,纹理像素的颜色,光照颜色以及物体表面材质反射光颜色进行混合,生成计算机屏幕的像素颜色。将多种颜色混合在一起,必须考虑各种颜色的成分比例,这个比例由Alpha因子决定。对于游戏开发来说,利用Alpha颜色混合可产生背景透明的渲染效果。

颜色混合原理

一般的,屏幕像素的当前颜色值SrcColor可与目标像素颜色值DestColor进行如下运算,然后将获得的颜色值Color作为该像素的新颜色,以实现像素的目标颜色与源颜色的混合。

Color = SrcColor * SrcBlend + DestColor * DestBlend

这里,SrcBlend和DestBlend为源混合因子和目标混合因子,分别乘以源颜色和目标颜色。SrcColor ,SrcBlend , DestColor ,DestBlend都是一个4维向量,而乘法运算 * 则是一个一个向量点积运算。

假设4维向量SrcColor=(Rs, Gs, Bs, As),SrcBlend=(S1, S2, S3, S4), DestColor=(Rd, Gd, Bd, Ad),DestBlend(D1, D2, D3, D4),则混合颜色Color可用4维向量表示为:

Color = (Rs * S1 + Rd * D1, Gs * S2 + Gd * D2, Bs * S3 + Bd * D3, As * S4 + Ad * D4)

利用Direct3D设备接口提供的SetRenderState函数可将所要使用的混合因子设置给渲染管道流水线。此时,函数的第一个参数必须指定为D3DRS_SRCBLEND或D3DRS_DESTBLEND,分别表示设置源混合因子和目标混合因子,如下所示:
 

// IDirect3DDevice9* _d3d_device;

// set alpha blend for source color
 _d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);

 
// set alpha blend for dest color
  _d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

D3DBLEND_SRCALPHA和D3DBLEND_INVSRCALPHA均为DirectX预定义的混合因子宏,来看看具体定义:

Defines the supported blend mode.

typedef enum D3DBLEND
{
D3DBLEND_ZERO = 1,
D3DBLEND_ONE = 2,
D3DBLEND_SRCCOLOR = 3,
D3DBLEND_INVSRCCOLOR = 4,
D3DBLEND_SRCALPHA = 5,
D3DBLEND_INVSRCALPHA = 6,
D3DBLEND_DESTALPHA = 7,
D3DBLEND_INVDESTALPHA = 8,
D3DBLEND_DESTCOLOR = 9,
D3DBLEND_INVDESTCOLOR = 10,
D3DBLEND_SRCALPHASAT = 11,
D3DBLEND_BOTHSRCALPHA = 12,
D3DBLEND_BOTHINVSRCALPHA = 13,
D3DBLEND_BLENDFACTOR = 14,
D3DBLEND_INVBLENDFACTOR = 15,
D3DBLEND_FORCE_DWORD = 0x7fffffff,
} D3DBLEND, *LPD3DBLEND;

Constants

D3DBLEND_ZERO
Blend factor is (0, 0, 0, 0).
D3DBLEND_ONE
Blend factor is (1, 1, 1, 1).
D3DBLEND_SRCCOLOR
Blend factor is (Rs, Gs, Bs, As).
D3DBLEND_INVSRCCOLOR
Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As).
D3DBLEND_SRCALPHA
Blend factor is (As, As, As, As).
D3DBLEND_INVSRCALPHA
Blend factor is ( 1 - As, 1 - As, 1 - As, 1 - As).
D3DBLEND_DESTALPHA
Blend factor is (Ad Ad Ad Ad).
D3DBLEND_INVDESTALPHA
Blend factor is (1 - Ad 1 - Ad 1 - Ad 1 - Ad).
D3DBLEND_DESTCOLOR
Blend factor is (Rd, Gd, Bd, Ad).
D3DBLEND_INVDESTCOLOR
Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad).
D3DBLEND_SRCALPHASAT
Blend factor is (f, f, f, 1); where f = min(As, 1 - Ad).
D3DBLEND_BOTHSRCALPHA
Obsolete. Starting with DirectX 6, you can achieve the same effect by setting the source and destination blend factors to D3DBLEND_SRCALPHA and D3DBLEND_INVSRCALPHA in separate calls.
D3DBLEND_BOTHINVSRCALPHA
Source blend factor is (1 - As, 1 - As, 1 - As, 1 - As), and destination blend factor is (As, As, As, As); the destination blend selection is overridden. This blend mode is supported only for the D3DRS_SRCBLEND render state.
D3DBLEND_BLENDFACTOR
Constant color blending factor used by the frame-buffer blender. This blend mode is supported only if D3DPBLENDCAPS_BLENDFACTOR is set in the SrcBlendCaps or DestBlendCaps members of D3DCAPS9.
D3DBLEND_INVBLENDFACTOR
Inverted constant color-blending factor used by the frame-buffer blender. This blend mode is supported only if the D3DPBLENDCAPS_BLENDFACTOR bit is set in the SrcBlendCaps or DestBlendCaps members of D3DCAPS9.
D3DBLEND_FORCE_DWORD
Forces this enumeration to compile to 32 bits in size. Without this value, some compilers would allow this enumeration to compile to a size other than 32 bits. This value is not used.

Remarks

In the preceding member descriptions, the RGBA values of the source and destination are indicated by the s and d subscripts.

The values in this enumerated type are used by the following render states:

  • D3DRS_DESTBLEND
  • D3DRS_SRCBLEND
  • D3DRS_DESTBLENDALPHA
  • D3DRS_SRCBLENDALPHA

由于渲染管道流水线的默认Alpha颜色混合功能是禁用的,因此必须调用SetRenderState函数设置D3DRS_ALPHABLENDENABLE为true.
 
// enable alpha-blended transparency
_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);

 


来看一个具体的例子:

需要在工程中设置链接d3dx9.lib d3d9.lib。
由于文件中用到了GE_APP这个类,它的具体使用说明请参阅 主窗口和DirectInput的封装。


若发现代码中存在错误,敬请指出。

源码下载

来看看AlphaBlend.h的定义:

 

/*************************************************************************************
 [Include File]

 PURPOSE: 
    Define for alpha blend.
*************************************************************************************/


#ifndef ALPHA_BLEND_H
#define ALPHA_BLEND_H

struct CUSTOM_VERTEX
{
    float x, y, z;
    float nx, ny, nz;
};

#define CUSTOM_VERTEX_FVF   (D3DFVF_XYZ | D3DFVF_NORMAL)

class ALPHA_BLEND
{
private:
    IDirect3D9* _d3d;
    IDirect3DDevice9* _d3d_device;
    IDirect3DVertexBuffer9* _vertex_buffer1;
    IDirect3DVertexBuffer9* _vertex_buffer2;

public:
    ALPHA_BLEND();
    ~ALPHA_BLEND();

    bool Create_D3D_Device(HWND hwnd, bool full_screen = true);
    bool Init_Vertex_Buffer1();
    bool Init_Vertex_Buffer2();
    void Compute_Triangle_Normal(D3DXVECTOR3& v1, D3DXVECTOR3& v2, D3DXVECTOR3& v3, D3DVECTOR& normal);
    void Set_Camera();
    void Set_Point_Light();
    void Set_Object_Material(D3DCOLORVALUE& dif, D3DCOLORVALUE& amb, D3DCOLORVALUE& spe, 
                             D3DCOLORVALUE& emi, float power);
    void Render();
    void Release_COM_Object();
};

#endif

以上的头文件定义了两个三棱锥的顶点格式和顶点结构体,函数Init_Vertex_Buffer1个Init_Vertex_Buffer2分别用来装入这两个三棱锥的顶点数据,Render函数则设置了渲染管道流水线的 Alpha颜色混合状态值。

再来看看AlphaBlend.cpp的定义:

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Define for alpha blend.
*************************************************************************************/


#include "GE_COMMON.h"
#include "AlphaBlend.h"

//------------------------------------------------------------------------------------
// Constructor, initialize all pointer with NULL.
//------------------------------------------------------------------------------------
ALPHA_BLEND::ALPHA_BLEND()
{
    _d3d            = NULL;
    _d3d_device     = NULL;
    _vertex_buffer1 = NULL;
    _vertex_buffer2 = NULL;
}

//------------------------------------------------------------------------------------
// Destructor, release all COM object.
//------------------------------------------------------------------------------------
ALPHA_BLEND::~ALPHA_BLEND()
{
    Release_COM_Object();
}

//------------------------------------------------------------------------------------
// Create direct3D interface and direct3D device.
//------------------------------------------------------------------------------------
bool ALPHA_BLEND::Create_D3D_Device(HWND hwnd, bool full_screen)
{
    // Create a IDirect3D9 object and returns an interace to it.
    _d3d = Direct3DCreate9(D3D_SDK_VERSION);
    if(_d3d == NULL)
        return false;

    // retrieve adapter capability
    D3DCAPS9 d3d_caps;    
    _d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3d_caps);
    
    bool hardware_process_enable = (d3d_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ? true : false);

    // Retrieves the current display mode of the adapter.
    D3DDISPLAYMODE display_mode;
    if(FAILED(_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
        return false;

    // set present parameter for direct3D device
    D3DPRESENT_PARAMETERS present_param;

    ZeroMemory(&present_param, sizeof(present_param));

    present_param.BackBufferWidth      = WINDOW_WIDTH;
    present_param.BackBufferHeight     = WINDOW_HEIGHT;
    present_param.BackBufferFormat     = display_mode.Format;
    present_param.BackBufferCount      = 1;
    present_param.hDeviceWindow        = hwnd;
    present_param.Windowed             = !full_screen;
    present_param.SwapEffect           = D3DSWAPEFFECT_FLIP;
    present_param.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

    // Creates a device to represent the display adapter.
    DWORD behavior_flags;

    behavior_flags = hardware_process_enable ?
 D3DCREATE_HARDWARE_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING;

    if(FAILED(_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, behavior_flags, 
                                 &present_param, &_d3d_device)))
    {
        return false;
    }
    
    // create successfully
    return true;
}

//------------------------------------------------------------------------------------
// Initialize vertex buffer for cone.
//------------------------------------------------------------------------------------
bool ALPHA_BLEND::Init_Vertex_Buffer1()
{
    CUSTOM_VERTEX custom_vertex[12];
    
    D3DXVECTOR3 v[] = 
    {
        D3DXVECTOR3(5.0f, 6.0f, 5.0f),    // left triangle
        D3DXVECTOR3(6.0f, 0.0f, 3.0f),
        D3DXVECTOR3(1.0f, 0.0f, 7.0f),  
        D3DXVECTOR3(5.0f, 6.0f, 5.0f),    // right triangle
        D3DXVECTOR3(10.0f, 0.0f, 8.0f),
        D3DXVECTOR3(6.0f, 0.0f, 3.0f), 
        D3DXVECTOR3(5.0f, 6.0f, 5.0f),    // back triangle
        D3DXVECTOR3(1.0f, 0.0f, 7.0f),
        D3DXVECTOR3(10.0f, 0.0f, 8.0f),
        D3DXVECTOR3(1.0f, 0.0f, 7.0f),    // bottom triangle
        D3DXVECTOR3(6.0f, 0.0f, 3.0f),
        D3DXVECTOR3(10.0f, 0.0f, 8.0f)      
    };

    D3DVECTOR normal;

    // compute all triangle normal
    for(int i = 0; i < 12; i += 3)
    {
        // compute current triangle's normal
        Compute_Triangle_Normal(v[i], v[i+1], v[i+2], normal);

        // assign current vertex coordinate and current triangle normal to custom vertex array
        for(int j = 0; j < 3; j++)
        {
            int k = i + j;

            custom_vertex[k].x  = v[k].x;
            custom_vertex[k].y  = v[k].y;
            custom_vertex[k].z  = v[k].z;
            custom_vertex[k].nx = normal.x;
            custom_vertex[k].ny = normal.y;
            custom_vertex[k].nz = normal.z;
        }
    }

    BYTE* vertex_data;

    // create vertex buffer
    if(FAILED(_d3d_device->CreateVertexBuffer(12 * sizeof(CUSTOM_VERTEX), 0, CUSTOM_VERTEX_FVF,
                            D3DPOOL_DEFAULT, &_vertex_buffer1, NULL)))
    {
        return false;
    }

    // get data pointer to vertex buffer
    if(FAILED(_vertex_buffer1->Lock(0, 0, (void **) &vertex_data, 0)))
        return false;

    // copy custom vertex data into vertex buffer
    memcpy(vertex_data, custom_vertex, sizeof(custom_vertex));

    // unlock vertex buffer
    _vertex_buffer1->Unlock();

    return true;
}

//------------------------------------------------------------------------------------
// Initialize vertex buffer for cone.
//------------------------------------------------------------------------------------
bool ALPHA_BLEND::Init_Vertex_Buffer2()
{
    CUSTOM_VERTEX custom_vertex[12];

    float add = 1.3f;
    
    D3DXVECTOR3 v[] = 
    {
        D3DXVECTOR3(5.0f + add, 6.0f + add, 5.0f + add),    // left triangle
        D3DXVECTOR3(6.0f + add, 0.0f + add, 3.0f + add),
        D3DXVECTOR3(1.0f + add, 0.0f + add, 7.0f + add),  
        D3DXVECTOR3(5.0f + add, 6.0f + add, 5.0f + add),    // right triangle
        D3DXVECTOR3(10.0f + add, 0.0f + add, 8.0f + add),
        D3DXVECTOR3(6.0f + add, 0.0f + add, 3.0f + add), 
        D3DXVECTOR3(5.0f + add, 6.0f + add, 5.0f + add),    // back triangle
        D3DXVECTOR3(1.0f + add, 0.0f + add, 7.0f + add),
        D3DXVECTOR3(10.0f + add, 0.0f + add, 8.0f + add),
        D3DXVECTOR3(1.0f + add, 0.0f + add, 7.0f + add),    // bottom triangle
        D3DXVECTOR3(6.0f + add, 0.0f + add, 3.0f + add),
        D3DXVECTOR3(10.0f + add, 0.0f + add, 8.0f + add)      
    };

    D3DVECTOR normal;

    // compute all triangle normal
    for(int i = 0; i < 12; i += 3)
    {
        // compute current triangle's normal
        Compute_Triangle_Normal(v[i], v[i+1], v[i+2], normal);

        // assign current vertex coordinate and current triangle normal to custom vertex array
        for(int j = 0; j < 3; j++)
        {
            int k = i + j;

            custom_vertex[k].x  = v[k].x;
            custom_vertex[k].y  = v[k].y;
            custom_vertex[k].z  = v[k].z;
            custom_vertex[k].nx = normal.x;
            custom_vertex[k].ny = normal.y;
            custom_vertex[k].nz = normal.z;
        }
    }

    BYTE* vertex_data;

    // create vertex buffer
    if(FAILED(_d3d_device->CreateVertexBuffer(12 * sizeof(CUSTOM_VERTEX), 0, CUSTOM_VERTEX_FVF,
                            D3DPOOL_DEFAULT, &_vertex_buffer2, NULL)))
    {
        return false;
    }

    // get data pointer to vertex buffer
    if(FAILED(_vertex_buffer2->Lock(0, 0, (void **) &vertex_data, 0)))
        return false;

    // copy custom vertex data into vertex buffer
    memcpy(vertex_data, custom_vertex, sizeof(custom_vertex));

    // unlock vertex buffer
    _vertex_buffer2->Unlock();

    return true;
}

//------------------------------------------------------------------------------------
// Set camera position.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Set_Camera()
{
    D3DXVECTOR3 eye(-6.0, 1.5, 10.0);
    D3DXVECTOR3 at(6.0, 2.0, 3.0);
    D3DXVECTOR3 up(0.0, 1.0, 0.0);

    D3DXMATRIX view_matrix;

    // Builds a left-handed, look-at matrix.
    D3DXMatrixLookAtLH(&view_matrix, &eye, &at, &up);

    // Sets d3d device view transformation state.
    _d3d_device->SetTransform(D3DTS_VIEW, &view_matrix);

    D3DXMATRIX proj_matrix;

    // Builds a left-handed perspective projection matrix based on a field of view.
    D3DXMatrixPerspectiveFovLH(&proj_matrix, D3DX_PI/2, WINDOW_WIDTH / WINDOW_HEIGHT, 1.0, 1000.0);
    
    // Sets d3d device projection transformation state.
    _d3d_device->SetTransform(D3DTS_PROJECTION, &proj_matrix);
    // enable automatic normalization of vertex normals
    _d3d_device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
}

//------------------------------------------------------------------------------------
// Set point light.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Set_Point_Light()
{
    D3DLIGHT9 light;

    // clear memory with 0
    ZeroMemory(&light, sizeof(D3DLIGHT9));

    light.Type          = D3DLIGHT_POINT;

    light.Diffuse.r     = 1.0;
    light.Diffuse.g     = 0.0;
    light.Diffuse.b     = 0.0;

    light.Ambient.r     = 0.0;
    light.Ambient.g     = 1.0;
    light.Ambient.b     = 0.0;

    light.Specular.r    = 0.0;
    light.Specular.g    = 0.0;
    light.Specular.b    = 0.0;
    
    light.Position.x    = 5.0;
    light.Position.y    = 6.0;
    light.Position.z    = -20.0;

    light.Attenuation0  = 1.0;
    light.Attenuation1  = 0.0;
    light.Attenuation2  = 0.0;

    light.Range         = 1000.0;

    // Assigns point lighting properties for this device
    _d3d_device->SetLight(0, &light);
    // enable point light
    _d3d_device->LightEnable(0, TRUE);
    // enable light 
    _d3d_device->SetRenderState(D3DRS_LIGHTING, TRUE);
    // add ambient light
    _d3d_device->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));
}

//------------------------------------------------------------------------------------
// Sets the material properties for the device.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Set_Object_Material(D3DCOLORVALUE& dif, D3DCOLORVALUE& amb, D3DCOLORVALUE& spe, 
                                       D3DCOLORVALUE& emi, float power)
{
    D3DMATERIAL9 material;

    material.Diffuse  = dif;
    material.Ambient  = amb;
    material.Specular = spe;
    material.Emissive = emi;
    material.Power    = power;

    // Sets the material properties for the device.
    _d3d_device->SetMaterial(&material);
}

//------------------------------------------------------------------------------------
// Compute triangle normal.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Compute_Triangle_Normal(D3DXVECTOR3& v1, D3DXVECTOR3& v2, D3DXVECTOR3& v3, D3DVECTOR& normal)
{
    D3DXVECTOR3 vec1 = v1 - v2;
    D3DXVECTOR3 vec2 = v1 - v3;
    D3DXVECTOR3 normal_vec;

    D3DXVec3Cross(&normal_vec, &vec1, &vec2);
    D3DXVec3Normalize(&normal_vec, &normal_vec);

    normal = (D3DVECTOR) normal_vec;
}

//------------------------------------------------------------------------------------
// Draw cones.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Render()
{
    if(_d3d_device == NULL)
        return;

    // clear surface with black
    _d3d_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0, 0);

    // begin scene
    _d3d_device->BeginScene();

    // 1) draw cone 1

    // Binds a vertex buffer to a device data stream.
    _d3d_device->SetStreamSource(0, _vertex_buffer1, 0, sizeof(CUSTOM_VERTEX));

    // Sets the current vertex stream declaration.
    _d3d_device->SetFVF(CUSTOM_VERTEX_FVF);

    // Renders a sequence of nonindexed, geometric primitives of the specified type from the current 
    // set of data input streams.
    _d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);

    // enable alpha-blended transparency
    _d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
    
    // set alpha blend for source cone
    _d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    // set alpha blend for dest cone
    _d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

    // 2) draw cone 2

    // Binds a vertex buffer to a device data stream. 
    _d3d_device->SetStreamSource(0, _vertex_buffer2, 0, sizeof(CUSTOM_VERTEX));

    // Sets the current vertex stream declaration.
    _d3d_device->SetFVF(CUSTOM_VERTEX_FVF);

    // draw square
    _d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);

    // disable alpha blend for d3d device
    _d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);

    // end scene
    _d3d_device->EndScene();

    // Presents the contents of the next buffer in the sequence of back buffers owned by the device.
    _d3d_device->Present(NULL, NULL, NULL, NULL);
}

//------------------------------------------------------------------------------------
// Release all COM object.
//------------------------------------------------------------------------------------
void ALPHA_BLEND::Release_COM_Object()
{
    Safe_Release(_vertex_buffer1);
    Safe_Release(_vertex_buffer2);
    Safe_Release(_d3d_device);
    Safe_Release(_d3d);
}

main.cpp的实现很简单,它首先调用类ALPHA_BLEND的函数创建两个三棱锥的顶点缓冲区,然后进行取景并设置材质光源,最后调用Render函数进行混色渲染。

 
/*************************************************************************************
 [Implement File]

 PURPOSE: 
    Test for alpha blending.
*************************************************************************************/


#define DIRECTINPUT_VERSION 0x0800

#include "GE_COMMON.h"
#include "GE_APP.h"
#include "AlphaBlend.h"

#pragma warning(disable : 4305 4996)

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    GE_APP ge_app;
    ALPHA_BLEND alpha_blend;

    MSG msg = {0};

    
// create window
    if(! ge_app.Create_Window("Alpha blending test", instance, cmd_show))
        
return false;

    HWND hwnd = ge_app.Get_Window_Handle();    

    SetWindowPos(hwnd, 0, 0,0,0,0, SWP_NOSIZE);
    SetCursorPos(0, 0);
    
    
// Create direct3D interface and direct3D device.
    if(! alpha_blend.Create_D3D_Device(hwnd, false))
        
return false;

    
// Initialize cone 1 vertex buffer with curstom vertex structure.
    if(! alpha_blend.Init_Vertex_Buffer1())
        
return false;

    
// Initialize cone 2 vertex buffer with curstom vertex structure.
    if(! alpha_blend.Init_Vertex_Buffer2())
        
return false;
    
    
// Set camera position.
    alpha_blend.Set_Camera();

    D3DCOLORVALUE dif = {1.0f, 1.0f, 1.0f, 0.6f};
    D3DCOLORVALUE amb = {1.0f, 1.0f, 1.0f, 0.0f};
    D3DCOLORVALUE spe = {0.0f, 0.0f, 0.0f, 0.0f};
    D3DCOLORVALUE emi = {0.0f, 0.0f, 0.0f, 0.0f};

    
// Sets the material properties for the device.
    alpha_blend.Set_Object_Material(dif, amb, spe, emi, 0);

    
// Set point light.
    alpha_blend.Set_Point_Light();

    
// Draw all cones
    alpha_blend.Render();

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 0,0 , PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }    

    UnregisterClass(WINDOW_CLASS_NAME, instance);

    
return true;
}
 

运行效果: