曾经有几个常捐血的朋友来找我,他们平均在两三年内捐了十次以上的血。从外表看,几乎都出现肺虚的症状,皮肤偏黑而且乾。他们有些是由於血浓度太低了,捐血站不再让他们捐血时,才惊觉身体出了状况。
西医有一种说法,认為捐血能刺激身体的造血机能。我不知道这是真的学理上有这样的证据,还是為了鼓励社会大眾捐血所编造出来的谎言,如果是后者,那就很不道德了。
通常在捐血之后,静脉裡的血红素和血液裡的各种成份的浓度都会降低,但是多数人在第二天这些数据就会回到正常的状况。大概『捐血能刺激造血机能』的说法是从这个现象来的。
身体的血液就像企业的资金一样,当企业的资金不足时,会依照各个部门不同的重要性,适当的调整资金的分配。同样的,当身体的主要血管裡的血液减少时,由於这部份血液直接影响身体的运行,如果血液不足,会出现立即的危险。因此,身体会把本来分配在其他器官裡的血液紧急调到主要血管中。於是第二天主要血管中的血液浓度就恢復了正常。这种快速恢復的现象,并不代表身体增加了新造的血来弥补捐血的损失。只是身体减少的血液是器官裡的血液,不会影响主要血管的浓度,但是身体的总血量确实减少了。
这就像我身上有一万元,银行有九万元,我的总资產有十万元。从身上的钱拿出五千元,捐给慈善机构,身上的钱立刻少了五千元。第二天再到银行领五千元,身上又恢復了一万元,可是银行裡已经少了五千元。我的总资產剩下九万五千元。从能量不灭定律来看,血液是身体的能量,捐出去所减少的能量必需有新的补充,才会恢復。身上的血液,仍然必需经过吸收营养,规律而良好的睡眠,让身体正常的造血,才会產生。『捐血能够刺激造血机能』的说法,违反了能量不灭定律。
捐血和捐钱都是行善的方式,想捐钱的人,一定得先赚钱或存到足够的钱才能捐。同样的,想捐血的人,一定要先明白养血的方法,可以先养了血再去捐,也可以捐了血之后再养血。养血最重要的就是要早睡,同时注意改善身体对营养的吸收。饮食『细嚼慢咽』加上敲胆经,是增加营养吸收很好的方法。每捐一次血,至少得维持十天至半个月在夜间十点前睡,才有机会把捐出去的血补回来。因此,生活不规律的人,是不适合捐血的,捐血的频率也必需视自己的生活作息和整体状况而调整。
捐血就像捐钱一样,都必定有真实的付出,捐出去的钱不会从天上掉下来,捐出去的血也不会凭空长出来。血库裡的血需要大家的捐赠才能维持运行,但愿所有想捐血的善心人士,都能长保健康。

公司的项目都是基于B/S结构的,绝大多数操作界面都是通过网页的形式展现在用户面前的,页面的美观就成了非常重要的问题。记得去年的这个时候公司迎来了它历史上的第一个专职美工。同时到来的就是程序员与美工的合作问题。

  矛盾篇:

  公司以前的系统都是由程序员来编写界面的,美观与否先不必说,单从效率上讲就是一个很大的问题。大部分时间都花在了界面的编写上,严重影响了项目的进展速度。美工到来以后,页面的美观程度和制作速度都有了很大提高,随之而来的程序员与美工的配合问题又成了一个新的问题。其中主要的问题、矛盾有以下几点:

  1. 美工何时参与到项目中来

  2. 程序员不懂如何将页面弄得美观,美工也不懂如何向页面中添加代码(即使是使用了Velocity)

  3. 程序员和美工是两种完全不同的人,他们关心的事情也完全不同,这就导致两种人对页面代码(html)风格的要求大相径庭??程序员要得是简单易懂,美工要得是美观漂亮

  4. 程序员要做的是将数据展现在页面上(使用简单的条件、循环语句),美工要做的是将美丽充满整个屏幕(程序员会叫道:天哪!这么复杂,我怎么用if、else、for来实现)

  解决篇:

  上面的这几点问题和矛盾从关系上来讲是层层递进的,要一个一个依次解决。先来说说美工何时介入到项目中来,在公司做过的这些项目以及我听说过的项目看,大致有以下几种:1)先有美工制作静态页面,完成后程序员直接向页面中添加程序代码;2)程序员随时和美工沟通,向美工描述页面需求,随要随做;3)程序员自己编写测试页面,然后让美工进行美化。

  这3种方式可以说是个有利弊。方式1)对程序员来说绝对是个喜讯,它能使程序员最大限度的远离那些烦人的页面编码,提高程序员工作的含金量。同时,一套完整的页面可以展现全部业务的流程,对程序员开发也起到了规范的作用。但这种方式对美工的要求极高,美工要了解项目的需求,而这一般是达不到的。但可以让了解需求的人为其讲解,或是描绘出希望的页面的样式。这样虽然可以弥补美工对业务了解的不足,但也确实花掉了很多时间(而且是花掉了比较重要的人物的时间,因为了解整体业务的一般都是公司的牛人,他们的时间可是一刻千金呀)。方式2)是一个比较折中的方法,这样做无需太多的准备就可开始编码工作,程序员把握页面内容和样式,向美工详细描述,美工再根据描述设计页面,最后返回给程序员添加代码。这个反馈的过程一般比较迅速,效果也不错,可以达到程序员预期的效果,适用于项目时间要求比较紧的情况。该方式的问题在于没有一个形象化的完整的流程可供程序员参考,一切掌握在程序员手中,容易造成对需求的贪污和系统整体风格的不统一。方式3)一般用于对已有项目的美化上,对美工的要求也很高,她们需要具备在html和其他代码混合体的环境下工作的能力。而且修改的效果一般不是很佳,不到万不得已不推荐使用。

  问题2.3.4.虽然表现出来的问题各不相同,但解决的方法却很相似。首先,美工要养成一些程序员编码时惯有的习惯,比如:文件命名要有意义、html代码要根据层次进行缩进等。其次,页面代码的一些细节也要注意,比如,使用居中或右对齐标签来取代空格,必须使用空格时也要用“ ”,不使用

标签,尽量使用表格等。再次,如果在条件允许的情况下,美工也可以学习一下夹杂在页面中的各种程序代码,了解其语义和工作原理,这将对与程序员的合作起到很大的帮助的。最后,就是程序员要在向页面文件中添加代码前先对页面代码做一下审核工作,在这里并不是看美工的页面是否美观,而是看在原有页面代码的基础上是否能够使用简单的条件、循环语句来显示数据(比如,页面布局过于复杂,不能通过简单的循环来显示所有数据),否则就需要修改页面代码直到能满足要求为止。

  做网站后台的流程一般是这样的:

  一、网站规划阶段

  这个阶段主要是对网站的功能、目标受众、内容、栏目进行规划。这期间会经常性地和有关领导进行沟通。首先,自己一定要对网站的整体规划清清楚楚,然后要吸收别人的建议。吸收别人的建议的过程,可以认认真真地做,也可以走过场,但是有这个过程以后,别人才不会对你的规划说三道四。

  至于领导的意愿,和你的规划靠得上边的,你一定要让领导明白,他们的设想已经在你的规划中被考虑进去了。

  项目的大致进度,要在这个阶段结束的时候确定下来。

  二、后台模块划分和版面设计

  这个阶段,程序员要和美工兵分两路分头行动。

  后台模块划分如果做好了,后面的效率会高一些。这个过程不能省。

  版面设计,美工既要考虑网站整体规划,又要考虑大家的建议,尤其是不能忽视领导们的观点(虽然大多数情况下领导的美术细胞少得可怜)。在这个大前提下,再兼顾美观、合理。一个好的美工,不仅仅能做出漂亮的页面,还要能迎合一下客户或者公司领导的意愿,而且能和程序员进行沟通。

  在这个阶段,程序员和项目经理(项目负责人)要拿出一个可操作的模块划分方案,而美工要确定网站的版面框架、美术风格,做出网站首页和二级页面。

  实际上,在第一个阶段(网站规划阶段),美工就应该开始思考网站的风格了。在第二个阶段,则需要把比较抽象的初级设想变成具体的页面。基本上,首页定了,整个网站的页面就定了一大半了。

  在这个阶段结束的时候,要将项目的进度计划进一步具体化。

  三、数据库设计

  这项工作很重要。但是程序员应该知道怎么去做。而且数据库设计是和一个人的理论水平、实际经验息息相关的,不是几句话能说明白的。大的、复杂的站点,数据库规划可能要用一周左右的时间,小的、简单的站点,数据库设计也需要2到3天。

  在这个阶段,美工最好别闲着,继续完成页面设计。要知道下一个阶段,程序员可就要用到美工的页面了。最好别出现这样的情况:程序员要用到某个页面,而美工还没有把那个页面确定下来。

  四、后台程序编码

  这个阶段,程序员要紧张工作,会比较辛苦的。

  程序员需要遵守的三个原则:

  1、团队合作;

  2、保证进度;

  3、保证质量。

  美工这个时候要辅助程序员做页面。这个阶段美工可能比较闲,但是一定要称职。

  项目经理该和客户或者领导沟通的时候,一定要沟通。

  五、除错、改进、页面美化

  这个阶段,不多说了。项目经理和客户、领导的沟通,仍然是很重要的。

好久不玩ASP,今天没事写个小程序,由于偶安装的Access 2007使用以前的方法就是连接不成功,后来到网上一查原来是连接方法有所改变(在想如果两年前偶能天天上网,今天的水平一定不错,呵呵.)

回正题 Access 2007 在数据格式上有了很大变化,因此数据提供者已经不是 jet db 4.0 了,Microsoft.ACE.OLEDB.12.0 才是 *.accdb 的数据提供者。
一般改成这样就行了:
"Provider = Microsoft.ACE.OLEDB.12.0;Persist Security Info=False;Data Source = " & Server.MapPath(db)
详细的连接字符串如下:
Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=C:\Documents and Settings\chenge\My Documents\数据库1.accdb;Mode=Share Deny Read|Share Deny Write;Extended Properties="";Jet OLEDB:System database=C:\Documents and Settings\chenge\Application Data\Microsoft\Access\System.mdw;Jet OLEDB:Registry Path=Software\Microsoft\Office\12.0\Access\Access Connectivity Engine;Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=6;Jet OLEDB:Database Locking Mode=0;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False;Jet OLEDB:Support Complex Data=True

XP系统坏了,重新安装Windows 2003 SP2系统,安装VS2005,在安装VS2005 SP1时,安装过程真是慢啊,就在大概安装到70%时,系统提示错误,错误号为:1718

解决方案:http://support.microsoft.com/kb/925336,就是下载页面的链接,因为没有注意看,白白浪费10多分钟。

发在首页,提醒大家一下!
解决方法:

<tbody>

    <tr>

        <td class=number>1.</td>

        <td class=text>依次单击&#8220;开始&#8221;和&#8220;运行&#8221;,键入 <span class=userInput>regedit</span>,然后单击&#8220;确定&#8221;。</td>

    </tr>

    <tr>

        <td class=number>2.</td>

        <td class=text>在注册表编辑器中,找到并单击下面的注册表项:

        <div class=indent>HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Safer\CodeIdentifiers<br><br><strong>注意</strong>:在修改此注册表项之前,建议先备份此注册表项。为此,右键单击&#8220;CodeIdentifiers&#8221;,然后单击&#8220;导出&#8221;。将文件保存到可在计算机上找到此文件的位置中。</div>

        </td>

    </tr>

    <tr>

        <td class=number>3.</td>

        <td class=text>更改 PolicyScope 的注册表值。为此,请双击&#8220;PolicyScope&#8221;,然后将设置从 0 更改为 1。</td>

    </tr>

    <tr>

        <td class=number>4.</td>

        <td class=text>关闭注册表编辑器。</td>

    </tr>

    <tr>

        <td class=number>5.</td>

        <td class=text>依次单击&#8220;开始&#8221;、&#8220;运行&#8221;,键入 <span class=userInput>cmd</span>,然后单击&#8220;确定&#8221;以打开命令提示符窗口。</td>

    </tr>

    <tr>

        <td class=number>6.</td>

        <td class=text>在命令提示符下,键入以下命令并按 Enter:

        <div class=indent><span class=userInput>net stop msiserver</span></div>

        如果 Windows Installer 服务当前正在后台运行,则此命令将停止该服务。该服务停止后,请关闭命令提示符窗口,然后转到步骤 7。<br><br><strong>注意</strong>:如果在命令提示符处收到以下消息,请关闭命令提示符窗口,然后转到步骤 7:

        <div class=message>未启动 Windows Installer 服务</div>

        </td>

    </tr>

    <tr>

        <td class=number>7.</td>

        <td class=text>收到&#8220;症状&#8221;部分所述的错误消息后,请安装要尝试安装的程序包。</td>

    </tr>

    <tr>

        <td class=number>8.</td>

        <td class=text>安装了程序包之后,重复步骤 1 和 2。然后,将 PolicyScope 注册表值更改回 0。</td>

    </tr>

    <tr>

        <td class=number>9.</td>

        <td class=text>如果从域中断开了计算机,请重新加入域,然后重新启动计算机</td>

    </tr>

</tbody>

原文出处:http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/

通常要开发网络应用程序并不是一件轻松的事情,不过,实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字,尝试进行连接,然后收发数据。真正难的是要写出一个可以接纳少则一个,多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方,当然,其中的许多要点对模型的双方都适用。

API与响应规模

通过Win32的重叠I/O机制,应用程序可以提请一项I/O操作,重叠的操作请求在后台完成,而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过,像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用,但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化,在Windows NT 和 Windows 2000上,使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。

完成端口

一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关联。

通常情况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是,线程数量等于处理器的数量,不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作,以免线程阻塞。每个线程都将分到一定的CPU时间,在此期间该线程可以运行,然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作,操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说,前一个线程没有充分使用其时间片,当发生这样的情况时,应用程序应该准备其它线程来充分利用这些时间片。

完成端口的使用分为两步。首先创建完成端口,如以下代码所示:

HANDLE    hIocp;

hIocp = CreateIoCompletionPort(

INVALID_HANDLE_VALUE,

NULL,

(ULONG_PTR)0,

0);

if (hIocp == NULL) {

// Error

}

完成端口创建后,要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数,第一个参数FileHandle设为套接字的句柄,第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。
以下代码创建了一个套接字,并把它和前面创建的完成端口关联起来:

SOCKET    s;

s = socket(AF_INET, SOCK_STREAM, 0);

if (s == INVALID_SOCKET) {

// Error

if (CreateIoCompletionPort((HANDLE)s,

hIocp,

(ULONG_PTR)0,

1) == NULL)

{

// Error

}

...

}


这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意,CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的“完成键(completion key)”(译者注:完成键可以是任何数据类型)。每当完成通知到来时,应用程序可以读取相应的完成键,因此,完成键可用来给套接字传递一些背景信息。

在创建了完成端口、将一个或多个套接字与之相关联之后,我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus ()函数并返回完成通知。

下面,我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时,要把指向一个overlapped结构的指针包括在其参数中。当操作完成后,我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过,单是根据这个指针所指向的overlapped结构,应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪,你可以自己定义一个OVERLAPPED结构,在其中加入所需的跟踪信息。

无论何时调用重叠操作函数时,总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、 WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息,当操作结束后,你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后,可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针。

OVERLAPPED 结构的定义如下:

typedef struct _OVERLAPPEDPLUS {

OVERLAPPED        ol;

SOCKET            s, sclient;

int               OpCode;

WSABUF            wbuf;

DWORD             dwBytes, dwFlags;

// 其它有用的信息

} OVERLAPPEDPLUS;

#define OP_READ     0

#define OP_WRITE    1

#define OP_ACCEPT   2

下面让我们来看看工作者线程的情况。

工作线程WorkerThread代码:

DWORD WINAPI WorkerThread(LPVOID lpParam)

{

ULONG_PTR       *PerHandleKey;

OVERLAPPED      *Overlap;

OVERLAPPEDPLUS  *OverlapPlus,

*newolp;

DWORD           dwBytesXfered;

while (1)

{

ret = GetQueuedCompletionStatus(

hIocp,

&dwBytesXfered,

(PULONG_PTR)&PerHandleKey,

&Overlap,

INFINITE);

if (ret == 0)

{

// Operation failed

continue;

}

OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);

switch (OverlapPlus->OpCode)

{

case OP_ACCEPT:

// Client socket is contained in OverlapPlus.sclient

// Add client to completion port

CreateIoCompletionPort(

(HANDLE)OverlapPlus->sclient,

hIocp,

(ULONG_PTR)0,

0);

//  Need a new OVERLAPPEDPLUS structure

//  for the newly accepted socket. Perhaps

//  keep a look aside list of free structures.

newolp = AllocateOverlappedPlus();

if (!newolp)

{

// Error

}

newolp->s = OverlapPlus->sclient;

newolp->OpCode = OP_READ;

// This function prepares the data to be sent

PrepareSendBuffer(&newolp->wbuf);

ret = WSASend(

newolp->s,

&newolp->wbuf,

1,

&newolp->dwBytes,

0,

&newolp.ol,

NULL);

if (ret == SOCKET_ERROR)

{

if (WSAGetLastError() != WSA_IO_PENDING)

{

// Error

}

}

// Put structure in look aside list for later use

FreeOverlappedPlus(OverlapPlus);

// Signal accept thread to issue another AcceptEx

SetEvent(hAcceptThread);

break;

case OP_READ:

// Process the data read

// ...

// Repost the read if necessary, reusing the same

// receive buffer as before

memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));

ret = WSARecv(

OverlapPlus->s,

&OverlapPlus->wbuf,

1,

&OverlapPlus->dwBytes,

&OverlapPlus->dwFlags,

&OverlapPlus->ol,

NULL);

if (ret == SOCKET_ERROR)

{

if (WSAGetLastError() != WSA_IO_PENDING)

{

// Error

}

}

break;

case OP_WRITE:

// Process the data sent, etc.

break;

} // switch

} // while

}  // WorkerThread

其中每句柄键(PerHandleKey)变量的内容,是在把完成端口与套接字进行关联时所设置的完成键参数;Overlap参数返回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUS结构的指针。

要记住,如果重叠操作调用失败时(也就是说,返回值是SOCKET_ERROR,并且错误原因不是WSA_IO_PENDING),那么完成端口将不会收到任何完成通知。如果重叠操作调用成功,或者发生原因是WSA_IO_PENDING的错误时,完成端口将总是能够收到完成通知。

Windows NT和Windows 2000的套接字架构

对于开发大响应规模的Winsock应用程序而言,对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。下图是Windows 2000中的Winsock架构:

与其它类型操作系统不同,Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面,而是采用了一种更为底层的API,叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理,以便向应用程序提供套接字仿真(在AFD.SYS文件中实现),同时负责与底层传输驱动程序对话。

谁来负责管理缓冲区?

正如上面所说的,应用程序通过Winsock来和传输协议驱动程序交谈,而AFD.SYS负责为应用程序进行缓冲区管理。也就是说,当应用程序调用send()或WSASend()函数来发送数据时,AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值),然后send()或WSASend()函数立即返回。也可以这么说,AFD.SYS在后台负责把数据发送出去。不过,如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小,那么WSASend()函数会阻塞,直至所有数据发送完毕。

从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据,而且没有超出SO_RCVBUF设定的值,AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时,数据将从内部缓冲拷贝到应用程序提供的缓冲区。

多数情况下,这样的架构运行良好,特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是,尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区),但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果,避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。

举例来说,一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭,然后发出一个阻塞send()调用。在这样的情况下,系统内核会把应用程序的缓冲区锁定,直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法,实际上却并非如此。想想看,即使远端TCP通知数据已经收到,其实也根本不代表数据已经成功送给客户端应用程序,比如对方可能发生资源不足的情况,导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是,在每个线程中每次只能进行一次发送调用,效率极其低下。

把SO_RCVBUF设为0,关闭AFD.SYS的接收缓冲区也不能让性能得到提升,这只会迫使接收到的数据在比Winsock更低的层次进行缓冲,当你发出receive调用时,同样要进行缓冲区拷贝,因此你本来想避免缓冲区拷贝的阴谋不会得逞。

现在我们应该清楚了,关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用,那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用,那么它将没有必要使用内部缓冲区。

高性能的服务器应用程序可以关闭发送缓冲区,同时不会损失性能。不过,这样的应用程序必须十分小心,保证它总是发出多个重叠发送调用,而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作,那浪费掉两次发送中间的空档时间,总之是要保证传输驱动程序在发送完一个缓冲区后,立刻可以转向另一个缓冲区。

资源的限制条件

在设计任何服务器应用程序时,其强健性是主要的目标。也就是说,

你的应用程序要能够应对任何突发的问题,例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题,从容地处理突发性事件。

你可以直接控制的、最基本的资源就是网络带宽。通常,使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制,以最大限度地减少包的丢失。然而,在使用TCP连接时,服务器必须十分小心地控制好,防止网络带宽过载超过一定的时间,否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定,这超出了本文讨论的范围。

虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存,或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存,将有助于控制服务器应用程序的内存开销(原文为“让服务器应用程序留下的脚印小一点”),避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为“让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中”)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。

在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭,当应用程序收发数据时,应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存,在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存,而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说,你的程序锁定内存时,不要超出系统规定的内存分页极限。

在Windows NT和2000系统上,所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计,不是你计算内存的依据)。如果你的应用程序不注意这一点,当你的发出太多的重叠收发调用,而且I/O没来得及完成时,就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意,系统会锁定包含你的缓冲区所在的整个内存页面,因此缓冲区靠近页边界时是有代价的(译者理解,缓冲区如果正好超过页面边界,那怕是1个字节,超出的这个字节所在的页面也会被锁定)。

另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域,这块内存用来存储一些供各种内核组件访问的数据,其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。

当应用程序创建一个套接字(或者是类似的打开某个文件)时,内核会从未分页池中分配一定数量的内存,而且在绑定、连接套接字时,内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现,如果你发出某些I/O请求时(例如收发数据),你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作,你可能需要给这个操作添加一个自定义结构,如前文所提及的)。最后这就可能会造成一定的问题,操作系统会限制未分页内存的用量。

在Windows NT和2000这两种操作系统上,给每个连接分配的未分页内存的具体数量是不同的,未来版本的Windows很可能也不同。为了使应用程序的生命期更长,你就不应该计算对未分页池内存的具体需求量。

你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时,某些与你的应用程序毫无关系的内核驱动就会发疯,甚至造成系统崩溃,特别是当系统中有第三方设备或驱动程序时,更容易发生这样的惨剧(而且无法预测)。同时你还要记住,同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序,因此在设计你的应用程序时,对资源量的预估要特别保守和谨慎。

处理资源不足的问题是十分复杂的,因为发生上述情况时你不会收到特别的错误代码,通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误,首先,把你的应用程序工作配置调整到合理的最大值(译者注:所谓工作配置,是指应用程序各部分运行中所需的内存用量,请参考 http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp ,关于内存优化,译者另有译文),如果错误继续出现,那么注意检查是否是网络带宽不足的问题。之后,请确认你没有同时发出太多的收发调用。最后,如果还是收到资源不足的错误,那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间,请关闭应用程序中相当部分的连接,等待系统自行渡过和修正这个瞬时的错误。

接受连接请求

服务器要做的最普通的事情之一就是接受来自客户端的连接请求。在套接字上使用重叠I/O接受连接的惟一API就是AcceptEx()函数。有趣的是,通常的同步接受函数accept()的返回值是一个新的套接字,而AcceptEx()函数则需要另外一个套接字作为它的参数之一。这是因为AcceptEx()是一个重叠操作,所以你需要事先创建一个套接字(但不要绑定或连接它),并把这个套接字通过参数传给AcceptEx()。以下是一小段典型的使用AcceptEx()的伪代码:

do {

-等待上一个 AcceptEx 完成

-创建一个新套接字并与完成端口进行关联

-设置背景结构等等

-发出一个 AcceptEx 请求

}while(TRUE);

作为一个高响应能力的服务器,它必须发出足够的AcceptEx调用,守候着,一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx才够,就取决于你的服务器程序所期待的通信交通类型。比如,如果进入连接率高的情况(因为连接持续时间较短,或者出现交通高峰),那么所需要守候的AcceptEx当然要比那些偶尔进入的客户端连接的情况要多。聪明的做法是,由应用程序来分析交通状况,并调整AcceptEx守候的数量,而不是固定在某个数量上。

对于Windows2000,Winsock提供了一些机制,帮助你判定AcceptEx的数量是否足够。这就是,在创建监听套接字时创建一个事件,通过WSAEventSelect()这个API并注册FD_ACCEPT事件通知来把套接字和这个事件关联起来。一旦系统收到一个连接请求,如果系统中没有AcceptEx()正在等待接受连接,那么上面的事件将收到一个信号。通过这个事件,你就可以判断你有没有发出足够的AcceptEx(),或者检测出一个非正常的客户请求(下文述)。这种机制对Windows NT 4.0不适用。

使用AcceptEx()的一大好处是,你可以通过一次调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两件事情。也就是说,如果客户端在发出连接的同时传输数据,你的AcceptEx()调用在连接创建并接收了客户端数据后就可以立刻返回。这样可能是很有用的,但是也可能会引发问题,因为AcceptEx()必须等全部客户端数据都收到了才返回。具体来说,如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数,那么AcceptEx()不再是一项原子型的操作,而是分成了两步:接受客户连接,等待接收数据。当缺少一种机制来通知你的应用程序所发生的这种情况:“连接已经建立了,正在等待客户端数据”,这将意味着有可能出现客户端只发出连接请求,但是不发送数据。如果你的服务器收到太多这种类型的连接时,它将拒绝连接更多的合法客户端请求。这就是黑客进行“拒绝服务”攻击的常见手法。

要预防此类攻击,接受连接的线程应该不时地通过调用getsockopt()函数(选项参数为SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设置为套接字被连接的时间,或者设置为-1(代表套接字尚未建立连接)。这时,WSAEventSelect()的特性就可以很好地利用来做这种检查。如果发现连接已经建立,但是很久都没有收到数据的情况,那么就应该终止连接,方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意,在多数非紧急情况下,如果套接字已经传递给AcceptEx()并开始守候,但还未建立连接,那么你的应用程序不应该关闭它们。这是因为即使关闭了这些套接字,出于提高系统性能的考虑,在连接进入之前,或者监听套接字自身被关闭之前,相应的内核模式的数据结构也不会被干净地清除。

发出AcceptEx()调用的线程,似乎与那个进行完成端口关联操作、处理其它I/O完成通知的线程是同一个,但是,别忘记线程里应该尽力避免执行阻塞型的操作。Winsock2分层结构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思,抱歉)。每个AcceptEx()调用都需要创建一个新套接字,所以最好有一个独立的线程专门调用AcceptEx(),而不参与其它I/O处理。你也可以利用这个线程来执行其它任务,比如事件记录。

有关AcceptEx()的最后一个注意事项:要实现这些API,并不需要其它提供商提供的Winsock2实现。这一点对微软特有的其它API也同样适用,比如TransmitFile()和GetAcceptExSockAddrs(),以及其它可能会被加入到新版Windows的API. 在Windows NT和2000上,这些API是在微软的底层提供者DLL(mswsock.dll)中实现的,可通过与mswsock.lib编译连接进行调用,或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针。

如果在没有事先获得函数指针的情况下直接调用函数(也就是说,编译时静态连接mswsock.lib,在程序中直接调用函数),那么性能将很受影响。因为AcceptEx()被置于Winsock2架构之外,每次调用时它都被迫通过WSAIoctl()取得函数指针。要避免这种性能损失,需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。

参见下图套接字架构:



TransmitFile 和 TransmitPackets

Winsock 提供两个专门为文件和内存数据传输进行了优化的函数。其中TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用,而TransmitPackets()则将在未来版本的Windows中实现。

TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是,先调用CreateFile()打开一个文件,然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率,因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile(),那么只需要给它一个已打开文件的句柄和要发送的字节数,而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次,然后TransmitFile()时再发生一次。这样效率就高多了。

TransmitPackets()比TransmitFile()更进一步,它允许用户只调用一次就可以发送指定的多个文件和内存缓冲区。函数原型如下:

BOOL TransmitPackets(

SOCKET hSocket,

LPTRANSMIT_PACKET_ELEMENT lpPacketArray,

DWORD nElementCount,

DWORD nSendSize,

LPOVERLAPPED lpOverlapped,

DWORD dwFlags

); 

其中,lpPacketArray是一个结构的数组,其中的每个元素既可以是一个文件句柄或者内存缓冲区,该结构定义如下:

typedef struct _TRANSMIT_PACKETS_ELEMENT {

DWORD dwElFlags;

DWORD cLength;

union {

struct {

LARGE_INTEGER     nFileOffset;

HANDLE            hFile;

};

PVOID             pBuffer;

};

} TRANSMIT_FILE_BUFFERS;

其中各字段是自描述型的(self explanatory)。
dwElFlags字段:指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE 和TF_ELEMENT_MEMORY指定);
cLength字段:指定将从数据源发送的字节数(如果是文件,这个字段值为0表示发送整个文件);
结构中的无名联合体:包含文件句柄的内存缓冲区(以及可能的偏移量)。

使用这两个API的另一个好处,是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后,就会在传输层级别断开连接,这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程,将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。

这两个API也都有一个共同的弱点:Windows NT Workstation 或 Windows 2000 专业版中,函数每次只能处理两个调用请求,只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。

放在一起看看

以上各节中,我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢?其实,这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时,那么你越能够避开瓶颈问题。

来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么,服务器将需要创建一个监听套接字,把它与某个完成端口进行关联,为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据,所以如果我们准备好接收缓冲区会使事情变得更为容易。当然,不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。

该设计中有一个重要的问题要考虑,我们应该允许多少个AcceptEx()进行守候。这是因为,每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区,那么内存中将会出现很多被锁定的页面(前文说过了,每个重叠操作都会消耗一小部分未分页内存池,同时还会锁定所有涉及的缓冲区)。这个问题很难回答,没有一个确切的答案。最好的方法是把这个值做成可以调整的,通过反复做性能测试,你就可以得出在典型应用环境中最佳的值。

好了,当你测算清楚后,下面就是发送数据的问题了,考虑的重点是你希望服务器同时处理多少个并发的连接。通常情况下,服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多,所消耗的未分页内存池也越多;等候处理的发送调用越多,被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。

对于上述环境,通常不需要关闭单个套接字的缓冲区,因为只在AcceptEx()中有一次接收数据的操作,而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是,如果客户机与服务器交互的方式变一变,客户机在发送了一次数据之后,还需要发送更多的数据,在这种情况下关闭接收缓冲就不太妙了,除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。

结论

开发大响应规模的Winsock服务器并不是很可怕,其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量,防止出现未分页内存池被耗尽,这才是最主要的挑战。按照我们前面讨论的一些原则,你就可以开发出大响应规模的服务器应用程序。

    首先在此郑重申明此观点只代表(msdpe)个人意见,与其他任何公司(包括微软)、媒体、个人无关。同时我有删除自己blog上恶意评论的权利。

    今天北大的BillG演讲因故去晚了一会,9点20多分才到,由于后面没有座位所以跟着另一位同事坐到了第一排(第一排有部分空位)。刚坐下BillG的演讲就结束了。可能是这几天时间安排太紧张了,我能从他的脸上清晰地看到疲惫,而且他不停咳嗽(好像感冒了)。不一会颁奖开始了,在六个无名英雄上台后又有一西装男子边向旁边散纸条边从展开一张纸,上面写着几个大字,迅速从右侧上台,开始有人以为是安排的搞笑环节,但很快他开始喊了,也很快自己下台了,不一会工作人员就把他带走了(注意有些媒体在首页链接的文字里写他是被扑到在地然后警方拖走的纯属没有新闻职业操守,一来是工作人员带走后来交给警方的,二来不是从地上“拖走”的,请慎重用词)。随后创新盛会继续进行。
    在这瞬间几位外国记者显得有点过于兴奋,使劲拍照。
    虽然这只是个小小的插曲,但已经被有些网络媒体发上头条,视屏音频文字图片五花八门的素材,挖出了该人的朋友同学前同事接触过的相关工作人员做侧面报道,比之前报道与吴仪副总理的会谈热烈多了。
    出了会场后很快知道这人原来是OSDNer之一,目前是Linux的一个销售代表,热衷开源,今天此举部分原因是为了在轰动场合表达意见,叫王洋。
中午时间翻看报道,网友已经议论纷纷,大多给他加上了爱国的头衔。但作为在第一排就座看清了事态全部,看到了西方记者迅速展开的报道和在站点对中国的恶意评论,我深深感到这样的抗议并不合适。
    他出去的时候用很不熟练的汉语喊不要暴力(让我误以为是留学生什么的),我们当然不希望他因此被暴打。如果有机会的话我想问他两个问题:
    中国软件产业的落后就是BillG没有开源造成的的吗,Windows的源代码已经在中国政府备案可以查看了,他所在的Linux公司为什么没有将自己商业化的组件让中国政府查看或者开源?
    不管出于爱好还是利益还是什么原因,支持开源是他的自由,但给中国的声誉带来损失他又怎样看待?


以下是本人观点:
不管商业还是非商业,开源和非开源,每个公司,集体,个人都有自己的生存方式,为什么要反对别人呢?自己做的就是对的吗?已所不欲,勿施于人.比尔大叔什么时间说过反正开源吗?开源就一定好嘛?

C:\ProgramData\Microsoft\User Account Pictures\Default Pictures
找了头天还是找到了.



我的系统版本6000.0.6000.

如果我愿意,我会不停地说下去,直到烦死你们,谁让我用的输入法快呢。

我说了几句搜狗或股沟输入法的坏话,引来一些人的争论,大大在我预料之中,这年头,当你想说一些知名度较高的人或物的坏话时,你一定做好被人扔板砖的心理准备,心理素质差的人,就会像杨丽娟她爸爸一样。但是我比你还混蛋,我不管你丫是谁,你出毛病我就要说说——只要我愿意。

其实搜狗和股沟就是王菲和周杰伦,知名度高,粉丝居多,总有很多人前来维护名誉。今年有个美国记者采访我,上来就扔过来一个问题:你写博客没遇到什么麻烦吗?然后用一种异样的表情等着我回答。如果我说我被公安局抓进去过一百多回,估计就会成为明天他们报纸的头条。我的回答让他很失望:“从来没有。如果说真正有什么麻烦的话,那就是有一些智商跟黑猩猩一样的傻逼粉丝到我这里证明他(她)跟黑猩猩差不多,有时候看着挺烦人的。”好在我已经习惯了,有人愿意证明他是个傻逼,那就让他自证好了。

关于这“狗歌”(搜狗和谷歌的简称)输入法,我第一篇文章里提到这是两个“垃圾输入法”,不管他们怎么改进词库,都是垃圾。我觉得,对于像我这样坚持使用黑马输入法的人毕竟是少数,但是,在此之前的拼音加加和紫光输入法我都用过,基本上已经很成熟了,“狗歌”先后推出完全是多此一举,但是他们非要这么做,你只能怀疑他们的目的根本不是提高输入效率,而是让他们的搜索引擎无所不在。这也是作为两家搜索公司干吗不把黑马买下来的原因,他们不是干文字校对的,买下来干吗用呢?

还有人说我不懂技术,没错,我知道C代表碳元素,C+代表碳离子,就是他妈的不知道C++代表什么,三个代表我就知道两个。谈技术,我的确外行,但是,我是把各种输入法一路用下来的人,我的工作就是打字,什么好用什么不好用我还是有亲身感受的。我看了很多人留言,让我想起了现在大家忙着怀念的王小波的一篇文章《沉默的大多数》。的确,跟王小波在世的时候相比,很多人不沉默了,因为有了互联网,说话容易了,每个人都可以发表自己的看法,不管你是爱因斯坦的智商还是黑猩猩的智商,都可以在一个舞台上说话。如果王小波说的“沉默的大多数”是指在一种政治背景下丧失、放弃表达权的人,那么今天在喧嚣的互联网上,在喧嚣的现实中,那些“喧嚣的大多数”只不过是被商业驯化成奴才的另一类“沉默的大多数”,他们连最起码的判断都丧失了,他们成了品牌和广告的奴隶,被商业牵着鼻子走。开始我还困惑为什么向别人推荐一个更好的东西会引起人们的怀疑,现在明白了,商业是另一种宗教,没有布道的人,但你不知不觉就被催眠了,还觉得骑上了哈里·波特大的扫帚。

如果在没有“输入法抄袭”的背景下,我介绍黑马输入法,人们反应不会这么激烈,因为我以前就在博客里介绍过这个输入法。但是有这个背景,效果果然不同了,有很多网站都转载我的这两篇文章,连新浪的技术频道的编辑都打电话要转载,要是搁往常,我一口拒绝,但这次我二话不说,同意,我连稿费都不要,不过要转载别的一个字5欧元。你看,这次我没有站在作恶和不作恶的任何一边,结果两边都向我开炮。我虽然不懂技术,但是我老人家也做过IT记者,IT行业什么操性,我还是比较清楚的,这是一个媒体与产业狼狈为奸的行业,以前我老觉得娱乐圈脏,娱乐圈的脏也就是你看到的那些,基本上浮在表面,用他妈电脑术语讲叫“所见即所得”,太深了也没什么了,但是IT圈的脏是在一种美丽的外表下的脏,太有欺骗性了,以前我还看IT报刊,自从我做了IT记者之后,到现在我再没看过任何一份IT报刊,觉得用手一碰都容易得非典,在这种肮脏的氛围内,你看到听到的,还有什么可信度呢?但是业内已经把这个游戏规则玩得很正常了,不就是联手糊弄老百姓吗,不就是从愚昧的大多数口袋里掏钱吗。然后他们培养出来的大多数会是什么样子呢?这些人可能一面看着王小波的书,发誓做一个特立独行的人,一面真的像头蠢猪一样加入到起哄架秧子的行列。

当搜狐公司站出来讨伐股沟的时候,我想起了一个寓言故事,一只螳螂举起力爪,准备结果眼前一只蝉的性命,这时一只黄雀跟在后面,准备用锋利的喙啄死这只肥硕的螳螂,但是戏剧性的场面出现了,蝉回过头,看到了准备向自己头上砍去的螳螂,慢悠悠地说:“你想干嘛啊?回头看看,谁吃谁呀?”螳螂回头,见黄雀蹲在身后,还没来得及解释,便被一口吃掉。我觉得搜狐就是那个螳螂,他们以前没少抄过别人吧,这次理直气壮,其实他们脖子后面也发凉。我们可以想象,在黄雀后面,可能还有只老鹰,在老鹰后面,可能还有只老虎,老虎后面可能还有个猎人,猎人后面可能还有个想不劳而获的人拿着刀子准备结果猎人的性命……它变成了一个完美的食物链,这就是当今中国的IT产业。

中国的IT产业是什么呢,其实跟福建某个村子生产破运动鞋没什么区别,都是在做低级劳动,具体就是复制和装卸。联想集团是中国数一数二的IT产业,不就是一群装卸工吗,别老拿高科技蒙人,我们以前好不容易弄出个芯片,不还是假的吗。更多的“高科技”产业干的都是低级事情,有多少是自主知识产权的?都是抄来抄去。百度抄股沟,股沟抄搜狐,搜狐谁都抄……外国有个什么新模式,我们就毫不费劲抄过来,要说IT产业没什么新鲜呢,都是拾人牙慧。硬件方面更差,就是攒劣质电脑。

往大了说,中国不重视高科技,人才都外流了,但是我们又需要高科技,所以只好靠欺骗。往小了说,“狗歌”输入法恰恰说明了IT行业真的没什么大能耐,就是小孩过家家那一套,在一些底层面上争来争去,也就是黑猩猩抢苹果,高级的东西他们哪会啊。看看你们电脑里装的各种软件,国产的软件都是小打小闹的,大的软件有几个是国产的?

我很佩服当年的求伯君,他就是想弄出一个WPS,逼着微软的OFFICE在中国市场改变策略,但是金山的蜕变,恰恰是中国IT产业低层次的缩影,你看他们现在弄的都是些什么破软件啊。

股沟这次作恶,我倒觉得很正常,自从他们把“Google”改成“谷歌”,不仅意味着它本土化,也意味着它开始作恶,因为在一个作恶多端的IT行业,你不作恶,就会饿死。股沟的作恶不是堕落,而是必须向堕落的大多数看齐,加入到这个游戏规则之中。然后大家都变成狗,狗咬狗一嘴毛的游戏才能继续下去。

虽然偶又开始裸奔了,但是看很多网友在问关于小狮子的问题,偶就来说下.
小狮子启动不了是因为你用的系统是优化版的.
把x:\windows\msagent文件夹里的文件删除了,
解决的方法是打开瑞星的安装目录,X:\Program Files\Rising\Rav\Update\,找到MsAgent.exe文件,安装一下就行了.


说明:"x:"代表你的系统盘及你的瑞星安装目录.

下面正式开始,先讲窗口类,创建窗口,销毁窗口,窗口消息处理函数. 

 

·窗口类WNDCLASS
struct WNDCLASS {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
};
style用来定义窗口的行为。如果打算共同使用GDID3D的话,可以使用CS_OWNDC作为参数。
lpfnWndProc一个函数指针,指向与这个窗口类绑定在一起的处理窗口消息的函数。
cbClsExtracbWndExtra为窗口和为分配内存空间。很少使用到这两个参数,一般设为0
hInstance应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)
hIconhCursorhbrBackground设置默认的图标、鼠标、背景颜色。不过在这里设置这些其实并不怎么重要,因为我们可以在后面定制自己的渲染方法。
lpszMenuName用来创建菜单
lpszClassName窗口类的名字。我们可以通过这个名字来创建以这个窗口类为模板的窗口。甚至可以通过这个名字来得到窗口的句柄。
设置好窗口类结构的内容后,使用RegisterClass(const WNDCLASS *lpWndClass)函数来注册它。关闭窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)来撤销注册。
·创建窗口CreateWindow
HWND CreateWindow(
   LPCTSTR lpClassName,
   LPCTSTR lpWindowName,
   DWORD dwStyle,
   int x, y,
   int nWidth, nHeight,
   HWND hWndParent,
   HMENU hMenu,
   HINSTANCE hInstance,
   LPVOID lpParam
);
lpClassName窗口类的名字。即窗口类结构体中的lpszClassName成员。
lpWindowName如果你的应用程序有标题栏,这个就是你标题栏上显示的内容。
dwStyle窗口的风格决定你的窗口是否有标题栏、最大最小化按钮、窗口边框等属性。在全屏的模式下,WS_POPUP|WS_VISIBLE是常用的设置,因为它产生一个不带任何东西的全屏窗口。在窗口的模式下,你可以设置很多窗口的风格,具体可以查看相关资料,这里不详细说明,不过WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一组常用的风格。
xy窗口创建的位置。(xy)表示窗口的左上角位置。
nWidthnHeight用来设置窗口的宽度和高度,以像素为单位。如果你想创建一个全屏的窗口,使用GetSystemMetrics(SM_CXSCREEN)GetSystemMetrics(SM_CYSCREEN)可以得到当前显示器屏幕的大小
hWndParent指定这个新建窗口的父窗口。在D3D应用程序中很少用,一般设为NULL
hMenu菜单句柄。
hInstance应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)
lpParam一个很神秘的参数。除非你知道自己在做什么,否则还是把它设为NULL吧。
·销毁窗口DestroyWindow
       销毁窗口有两种方法,一种是隐式的,一种是显式的。我们都知道Windows操作系统是一个基于消息驱动的系统。流动于系统中的消息使我们的窗口跑起来。在很多软件开发特别是商业软件的开发过程中,窗口的产生和销毁都是交由系统去做的,因为这些不是这类开发的关注所在。但是游戏开发不一样,尽管你也可以只向系统发送一条WM_DESTROY消息来销毁窗口,我们还是希望窗口是销毁的明明白白的。由于窗口的注册、产生和使用都是由我们亲手来做的,那么当然窗口的销毁也得由我们亲自来做。不过还是得说明一点,使用WM_DESTROY消息和DestroyWindow函数来销毁窗口在本质上并无太大差别,使用哪种方法可以说是根据个人的爱好吧。
       销毁窗口后是不是就完事了呢?不,还没有,因为应用程序的消息队列里可能还有没处理完的消息,为了彻底的安全,我们还得把那些消息都处理完。所以结束应用程序的时候,可以使用以下方法:
       MSG msg;
       DestroyWindow(h_wnd);
       while(PeekMessage(&msg , NULL , 0 , 0 , PM_REMOVE))
       {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
       }
·窗口消息处理过程
       窗口消息的处理函数是一个回调函数,什么是回调函数?就是由操作系统负责调用的函数。CALLBACK这个宏其实就是__stdcall,这是一种函数调用的方式,在这里不多说这些了,有兴趣的可以参考一些Windows编程的书籍,里面会有很详尽的说明。
       Windows里面有很多消息,这些消息都跑去哪里了呢?其实它们都在自己的消息队列里等候。消息是怎么从队列里出去的呢?就是通过GetMessagePeekMessage这两个函数。那么消息从队列里出去后又到哪里了呢?嗯,这时候消息就正式进入了我们的窗口消息处理过程,也即是窗口类中lpfnWndProc所指定的函数。一个消息处理函数有四个参数,下面分别说说:
       参数1HWND p_hWnd
       消息不都是传到以窗口类为模板产生的窗口吗?为什么还要使用窗口句柄来指明窗口呢?别忘了一个窗口类是可以产生多个窗口的呀,如果一个应用程序里面有多个窗口,并且它们之中的一些窗口是共用一个窗口类的,那么就得用一个窗口句柄来指明究竟这个消息是哪个窗口发过来的。
       参数2UINT p_msg
       这是一个消息类型,就是WM_KEYDOWN , WM_CLOSE , WM_TIMER这些东东。
       参数3WPARAM p_wparam
       这个参数内容就是消息的主要内容。如果是WM_KEYDOWN消息,那么p_wparam就是用来告诉你究竟是哪个键被按下。
       参数4LPARAM p_lparam
       这个参数的内容一般是消息的一些附加内容。
       最后说明一下DefWindowProc的作用。有时候我们把一个消息传到窗口消息处理函数里面,但是里面没有处理这个消息的内容。怎么办?很容易,交给DefWindowProc处理就对了。
 ·创建IDirect3D接口
DirectX是一组COM组件,COM是一种二进制标准,每一个COM里面提供了至少一个接口,而接口就是一组相关的函数,我们使用DirectX,其实就是使用那些函数。COMC++中的类有点像,只不过COM使用自己的方法来创建实例。创建COM实例的一般方法是使用coCreateInstance函数。有关coCreateInstance的使用方法,可以参考有关COM方面的资料,这里暂时不详细说明了,因为DirectX提供了更简洁的方法来创建DirectX组件的实例。这一章我要讲的就是Direct3D组件的使用方法。
       为了使用D3D中的函数,我们得先定义一个指向IDirect3D9这个接口的指针,顺便说明一下,其实接口也是一个指针,所以我们定义的就是一个指向指针的指针,也即二重指针,为什么要使用二重指针呢,我暂时还不是很懂,所以先留着这个疑问吧^_^。定义完这个接口指针后,例如IDirect3D9 *g_pD3D;现在我们使用Direct3DCreate9这个函数来创建一个D3D接口:
       g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
       Direct3DCreate9这个函数只有一个参数,它表明要创建接口的版本。如果你想创建一个老的接口版本当然也可以,不过没有人会那样做吧。
       创建接口后就可以创建D3D设备了,什么是D3D设备?你可以想象为你机上的那块显卡!什么?你有几块显卡!!没关系,那就创建多几个D3D设备接口吧。创建D3D设备需要的参数很多,如果把那些参数都挤在一个函数里面,那就太长了,所以就把一些参数放进结构体里面,只要先设定好这些结构体,再把这些结构体当作参数传给创建D3D设备的函数,那就清晰多了。首先要讲的就是D3DPRESENT_PARAMETERS这个结构。下面是它的定义:
struct D3DPRESENT_PARAMETERS{
   UINT                BackBufferWidth;
   UINT                BackBufferHeight;
   D3DFORMAT           BackBufferFormat;
   UINT                BackBufferCount;
   D3DMULTISAMPLE_TYPE MultiSampleType;
   DWORD               MultiSampleQuality;
   D3DSWAPEFFECT       SwapEffect;
   HWND                hDeviceWindow;
   BOOL                Windowed;
   BOOL                EnableAutoDepthStencil;
   D3DFORMAT           AutoDepthStencilFormat;
   DWORD               Flags;
   UINT                FullScreen_RefreshRateInHz;
   UINT                PresentationInterval;
};
        BackBufferWidthBackBufferHeight后备缓冲的宽度和高度。在全屏模式下,这两者的值必需符合显卡所支持的分辨率。例如(800600),(640480)。
       BackBufferFormat后备缓冲的格式。这个参数是一个D3DFORMAT枚举类型,它的值有很多种,例如D3DFMT_R5G6B5,这说明后备缓冲的格式是每个像素16位,其实红色(R)占5位,绿色(G)占6位,蓝色(B)占5位,为什么绿色会多一位呢?据说是因为人的眼睛对绿色比较敏感。DX9只支持16位和32位的后备缓冲格式,24位并不支持。如果对这D3DFORMAT不熟悉的话,可以把它设为D3DFMT_UNKNOWN,这时候它将使用桌面的格式。
       BackBufferCount后备缓冲的数目,范围是从03,如果为0,那就当成1来处理。大多数情况我们只使用一个后备缓冲。使用多个后备缓冲可以使画面很流畅,但是却会造成输入设备响应过慢,还会消耗很多内存。
       MultiSampleTypeMultiSampleQuality这两个参数可以使你的渲染场景变得更好看,但是却消耗你很多内存资源,而且,并不是所有的显卡都支持这两者的所设定的功能的。在这里我们分别把它们设为D3DMULTISAMPLE_NONE0
       SwapEffect交换缓冲支持的效果类型。它是D3DSWAPEFFECT枚举类型,可以设定为以下三者之一:D3DSWAPEFFECT_DISCARDD3DSWAPEFFECT_FLIPD3DSWAPEFFECT_COPY。如果设定为D3DSWAPEFFECT_DISCARD,则后备缓冲区的东西被复制到屏幕上后,后备缓冲区的东西就没有什么用了,可以丢弃(discard)了。如果设定为D3DSWAPEFFECT_FLIP,则表示在显示和后备缓冲之间进行周期循环。设定D3DSWAPEFFECT_COPY的话,我也不太清楚有什么作用*^_^*。一般我们是把这个参数设为D3DSWAPEFFECT_DISCARD
       hDeviceWindow显示设备输出窗口的句柄
       Windowed如果为FALSE,表示要渲染全屏。如果为TRUE,表示要渲染窗口。渲染全屏的时候,BackBufferWidthBackBufferHeight的值就得符合显示模式中所设定的值。
       EnableAutoDepthStencil如果要使用Z缓冲,则把它设为TRUE
       AutoDepthStencilFormat如果不使用深度缓冲,那么这个参数将没有用。如果启动了深度缓冲,那么这个参数将为深度缓冲设定缓冲格式(和设定后备缓冲的格式差不多)
       Flags可以设置为0D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用来做什么的,看字面好像是一个能否锁定后备缓冲区的标记。
       FullScreen_RefreshRateInHz显示器的刷新率,单位是HZ,如果设定了一个显示器不支持的刷新率,将会不能创建设备或发出警告信息。为了方便,一般设为D3DPRESENT_RATE_DEFAULT就行了。
       PresentationInterval如果设置为D3DPRENSENT_INTERVAL_DEFAULT,则说明在显示一个渲染画面的时候必要等候显示器刷新完一次屏幕。例如你的显示器刷新率设为80HZ的话,则一秒内你最多可以显示80个渲染画面。另外你也可以设置在显示器刷新一次屏幕的时间内显示1到4个画面。如果设置为D3DPRENSENT_INTERVAL_IMMEDIATE,则表示可以以即时的方式来显示渲染画面,虽然这样可以提高帧速(FPS),但是却会产生图像撕裂的情况。
·创建IDirect3DDevice接口
       当你把D3DPRESENT_PARAMETERS的参数都设置好后,就可以创建一个D3D设备了,和创建D3D接口一样,先定义一个接口指针IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函数来创建设备。CreateDevice的声明为:
       HRESULT CreatDevice(
              UINT Adapter,
              D3DDEVTYPE DeviceType,
              HWND hFocusWindow,
              DWORD BehaviorFlags,
              D3DPRESENT_PARAMETERS *pPresentationParameters,
              IDirect3DDevice9** ppReturnedDeviceInterface
       };
       第一个参数说明要为哪个设备创建设备指针,我之前说过一台机可以有好几个显卡,这个参数就是要指明为哪块显卡创建可以代表它的设备指针。但是我怎么知道显卡的编号呢?可以使用D3D接口里面的函数来获得,例如GetAdapterCounter可以知道系统有几块显卡;GetAdapterIdentifier可以知道显卡的具体属性。一般我们设这个参数为D3DADAPTER_DEFAULT
       第二个参数指明正在使用设备类型。一般设为D3DEVTYPE_HAL
       第三个参数指明要渲染的窗口。如果为全屏模式,则一定要设为主窗口。
       第四个参数是一些标记,可以指定用什么方式来处理顶点。
       第五个参数就要用到上面所讲的D3DPRESENT_PARAMETERS
       第六个参数是返回的接口指针。
·开始渲染
       有了设备接口指针,就可以开始渲染画面了。渲染是一个连续不断的过程,所以必定要在一个循环中完成,没错,就是第一章讲的那个消息循环。在渲染开始之前我们要用IDirect3DDevice9::Clear函数来清除后备缓冲区。
HRESULT Clear(
   DWORD Count,
   const D3DRECT *pRects,
   DWORD Flags,
   D3DCOLOR Color,
   float Z,
   DWORD Stencil
);
        Count说明你要清空的矩形数目。如果要清空的是整个客户区窗口,则设为0
       pRects这是一个D3DRECT结构体的一个数组,如果count中设为5,则这个数组中就得有5个元素。
       Flags一些标记组合。只有三种标记:D3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER
       Color清除目标区域所使用的颜色。
       float设置Z缓冲的Z初始值。小于或等于这个Z初始值的Z值才会被改写,但它的值只能取01之间。如果还不清楚什么是Z缓冲的话,可以自己找相关资料看一下,这里不介绍了,呵呵。
       Stencil设置模板缓冲的初始值。它的取值范围是02n次方减1。其中n是模板缓冲的深度。
       清除后备缓冲区后,就可以对它进行渲染了。渲染完毕,使用Present函数来把后备缓冲区的内容显示到屏幕上。
HRESULT Present(
   const RECT *pSourceRect,
   const RECT *pDestRect,
   HWND hDestWindowOverride,
   const RGNDATA *pDirtyRegion
);
        pSourceRect你想要显示的后备缓冲区的一个矩形区域。设为NULL则表示要把整个后备缓冲区的内容都显示。
       pDestRect表示一个显示区域。设为NULL表示整个客户显示区。
       hDestWindowOverride你可以通过它来把显示的内容显示到不同的窗口去。设为NULL则表示显示到主窗口。
       pDirtyRegion高级使用。一般设为NULL
  ·顶点属性与顶点格式
顶点可谓是3D世界中的基本元素。在计算机所能描绘的3D世界中,任何物体都是由多边形构成的,可以是三边形,也可以是四边形等。由于三边形,即三角形所具有的特殊性质决定其在3D世界中得到广泛的使用。构成三角形需要三个点,这些点的性质就是这章所要讲的内容。
       也许你已经知道顶点的结构定义,你可能会奇怪为什么D3D会知道我们“随便”定义的那些结构呢?其实那些顶点的定义可不是那么随便的哦。下面列举在Direct3D中,顶点所具有的所有属性。
       1)位置:顶点的位置,可以分别指定x,y,x三个值,也可以使用D3DXVECTOR3结构来定义。
       2RHW:齐次坐标W的倒数。如果顶点为变换顶点的话,就要有这个值。设置这个值意味着你所定义的顶点将不需要Direct3D的辅助(不能作变换、旋转、放大缩小、光照等),要求你自己对顶点数据进行处理。至于W是什么,WXYZ一样,只是一个四元组的一部分。RHW的英文是Reciprocal of the Homogenous W,即1/W,它是为了处理矩阵的工作变得容易一些(呼,线性代数的东东快都忘了,要恶补一下才行)。一般设RHW的值为1.0
       3)混合加权:用于矩阵混合。高级应用,这里不讲了(其实我不会,^_^
       4)顶点法线:学过高等数学就应该知道法线是什么吧?在这里是指经过顶点且和由顶点引出的边相垂直的线,即和三角形那个面垂直。用三个分量来描述它的方向,这个属性用于光照计算。
       5)顶点大小:设定顶点的大小,这样顶点就可以不用只占一个像素了。
       6)漫反射色:即光线照射到物体上产生反射的着色。理解这个比较麻烦,因为3D光照和真实光照没什么关系,不能像理解真实光照那样去理解3D光照。
       7)镜面反射色:它可以让一个3D物体的表面看起来很光滑。
       8)纹理坐标:如果想要在那些用多边形组成的物体上面贴上纹理,就要使用纹理坐标。由于纹理都是二维的,所以用两个值就可以表示纹理上面某一点的位置。在纹理坐标中,只能在0.01.0之间取值。例如(0.0 , 0.0)表示纹理的左上角,(1.0 , 1.0)表示纹理的右下角。
       好了,请记住上面属性的顺序。我们定义一个顶点结构的时候,不一定要包括全部的属性,但是一定要按照上面的顺序来定义。例如:
       struct MYVERTEX
{
              D3DXVECTOR3 position;
              float rhw;
              D3DCOLOR color;
       }
       上面定义了一个有漫反射色的变换顶点。
       定义完了顶点的结构后,我们就要告诉D3D我们定义的是什么格式。为了方便,我们通常会用#define来定义一个叫做描述“灵活顶点格式”(FVFFlexible Vertex Format)的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根据之前定义的顶点属性结构体,我们要定义相对应的宏。假如顶点结构中有位置属性,那么就要使用D3DFVF_XYZ;如果是变换顶点的话,就要使用D3DFVF_XYZRHW;如果使用了漫反射色属性的话,就要使用D3DFVF_DIFFUSE。这些值是可以组合使用的,像上面那样用“|”符号作为连结符。定义完灵活顶点格式后,使用IDirect3DDevice9::SetVertexShader函数来告诉D3D我们所定义的顶点格式,例如:g_pD3DDevice->SetVertexShader( MYFVF );
·顶点缓冲
       处理顶点信息的地方有两个,一个是在数组里,另一个是在D3D所定义的顶点缓冲里。换个说法的话就是一个在我们所能直接操作的内存里,另一个在D3D管理的内存里。对于我们这些对操作系统底层了解不多的菜鸟来说,直接操作内存实在是太恐怖了,所以还是交给D3D帮我们处理吧,虽然不知道背后有些什么操作。要想把顶点信息交给D3D处理,我们就要先创建一个顶点缓冲区,可以使用IDirect3DDevice9->CreateVertexBuffer,它的原型是:
HRESULT CreateVertexBuffer(
   UINT Length,
   DWORD Usage,
   DWORD FVF,
   D3DPOOL Pool,
   IDirect3DVertexBuffer9** ppVertexBuffer,
   HANDLE* pSharedHandle
);
       Length缓冲区的长度。通常是顶点数目乘以顶点大小,使用Sizeof( MYVERTEX )就可以知道顶点的大小了。
       Usage高级应用。设为0就可以了。
       FVF就是我们之前定义的灵活顶点格式。
       Pool告诉D3D将顶点缓冲存储在内存中的哪个位置。高级应用,通常可取的三个值是:D3DPOOL_DEFAULTD3DPOOL_MANAGEDD3DPOOL_SYSTEMMEM。多数情况下使用D3DPOOL_DEFAULT就可以了。
       ppVertexBuffer返回来的指向IDirect3DVertexBuffer9的指针。之后对顶点缓冲进行的操作就是通过这个指针啦。到这里还要再提醒一下,对于这些接口指针,在使用完毕后,一定要使用Release来释放它。
       pSharedHandle设为NULL就行了。
       得到一个指向IDirect3DVertexBuffer9的指针后,顶点缓冲也就创建完毕了。现在要做的就是把之前保存在数组中的顶点信息放在顶点缓冲区里面。首先,使用IDirect3DVertexBuffer9::Lock来锁定顶点缓冲区:
HRESULT Lock(
   UINT OffsetToLock,
   UINT SizeToLock,
   void **ppbData,
   DWORD Flags
);
       OffsetToLock指定要开始锁定的缓冲区的位置。通常在起始位置0开始锁定。
       SizeToLock指定在锁定的缓冲区的大小。设为0的话就是表示要锁定整个缓冲区。
       ppbData用来保存返回的指向顶点缓冲区的指针。通过这个指针来向顶点缓冲区填充数据。
       Flags高级应用。通常设为0
       填充为顶点缓冲区后,使用IDirect3DDevice9::Unlock来解锁。
       最后在渲染的时候使用IDirect3DDevice9::SetStreamSource来告诉D3D要渲染哪个顶点缓冲区里面的顶点。
HRESULT SetStreamSource(
   UINT StreamNumber,
   IDirect3DVertexBuffer9 *pStreamData,
   UINT OffsetInBytes,
   UINT Stride
);
     StreamNumber设置数据流的数量。顶点缓冲最多可以使用16个数据流。确定所支持的数据流的数量,可以检查D3DCAPS中的MaxStreams成员的值。通常设为0,表示使用单数据流。
pStreamData要与数据流绑定的数据。在这里我们要把顶点缓冲区与数据流绑定。
OffsetInBytes设置从哪个位置开始读数据。设为0表示从头读起。
Stride数据流里面数据单元的大小。在这里是每个顶点的大小。
·索引缓冲
       很多时候,相邻的三角形会共用一些顶点,例如组成四方形的两个三角形就共用了一条边,即共用了两个顶点信息。如果不使用索引,我们需要六个顶点的信息来绘制这个四方形,但实际上绘制一个四方形只要四个顶点信息就足够了。如果使用了索引就不一样了,在顶点缓冲区里我们可以只保存四个顶点的信息,然后通过索引来读取顶点信息。要使用索引得先创建一个索引缓冲。也许读到这里你会有个疑问,创建一个索引缓冲不就更浪费内存空间了吗?其实不然,索引缓冲区的元素保存的是数字,一个数字所占用的内存肯定要比一个顶点所占用的小得多啦。当你节省了几千个顶点,你就会发现浪费那么一点点索引缓冲区是很值得的。
       创建索引缓冲的函数是:IDirect3DDevice9::CreateIndexBuffer
HRESULT CreateIndexBuffer(
   UINT Length,
   DWORD Usage,
   D3DFORMAT Format,
   D3DPOOL Pool,
   IDirect3DIndexBuffer9** ppIndexBuffer
);
     Length索引缓冲区的长度。通常使用索引数目乘以sizeofWORD)或sizeof(DWORD)来设置,因为索引号的数据类型是字节(WORD)或双字节(DWORD),嗯,一个WORD只有两个字节,DWORD也就只有四个字节,比顶点的大小小多了吧。
     UsageCreateVertexBuffer中的Usage设置一样。一般设为0
Format设置索引格式。不是D3DFMT_INDEX16就是D3DFMT_INDEX32的啦。
Pool又是和CreateVertexBuffer中的一样。一般设为D3DPOOL_DEFAULT
ppIndexBuffer指向IDirect3DIndexBuffer9的指针。操作索引缓冲区就靠它的啦。记得使用完后要Release啊。
和填充顶点缓冲区一样,要填充索引缓冲区,要先使用IDirect3DIndexBuffer9::Lock来锁定缓冲区。
HRESULT Lock(
   UINT OffsetToLock,
   UINT SizeToLock,
   void **ppbData,
   DWORD Flags
);
     是不是和IDirect3DVertexBuffer9::Lock一样呢?具体说明也可以参照上面的内容。填充完之后使用IDirect3DIndexBuffer9::UnLock来解锁。
最后使用IDirect3DDevice9::SetIndices来告诉设备要使用哪个索引。
HRESULT Setindices(
   IDirect3DindexBuffer9* pIndexData,
   UINT BaseVertexIndex
);
     pIndexData设置使用哪个索引缓冲。
BaseVertexIndex设置以顶点缓冲区中的哪个顶点为索引0
     有关顶点的知识就说到这了。一下章说说点、线、三角形这种D3D所支持的图元(drawing primitives)。
·D3D中的图元简介
       D3D中,一共有三种基本图元,分别是点、线和三角形。点是最简单的图元,由它可以构成一种叫点列(point list)的图元类型。线是由两个不重合的点构成的,一些不相连的线组成的集合就叫线列(line list),而一些首尾相连但不形成环路的线的集合就叫线带(line strips)。同理,单独的三角形集合就叫三角形列(triangle list),类似于线带的三角形集合就叫三角形带(triangle strips),另外,如果多个三角形共用一个顶点作为它们的一个顶点的话,那么这个集合就叫三角形扇(triangle fans)。还是画图比较容易理解吧:
 
 
       这些图元有什么用呢?基本上我们可以使用这些图元来画我们想要的任何物体。例如画一个四方形可以使用三角形带来画,画一个圆则使用三角形扇。
       现在介绍一种不需要顶点缓冲来渲染的方法,就是使用IDirect3DDevice9::DrawPrimitiveUP函数。UP就是User Pointer的意思,也即是说要使用用户定义的内存空间。
HRESULT DrawPrimitiveUP(
    D3DPRIMITIVETYPE PrimitiveType,
    unsigned int PrimitiveCount,
    const void *pVertexStreamZeroData,
    unsigned int VertexStreamZeroStride
);
     PrimitiveType要绘画的图元的种类。就是上面介绍的那六种类型。
PrimitiveCount要绘画的图元的数量。假设有n个顶点信息,绘画的图元类型是点列的话,那么图元的数量就是n;如果绘画的图元类型是线列的话,那么图元的数量就是n/2;如果是线带的话就是n-1;三角形列就是n/3;三角形带就是n-2;三角形扇出是n-2
pVertexStreamZeroData存储顶点信息的数组指针
VertexStreamZeroStride顶点的大小
·使用顶点缓冲来绘画图元
很多时候我们使用顶点来定义图形之后,就把这些顶点信息放进顶点缓冲里面,然后再进行渲染。使用点顶缓冲的好处以及如何创建顶点缓冲我已经在上一章已讲过了,现在讲讲怎么把顶点缓冲里面的图元给画出来。其实也很简单,和上面的IDirect3DDevice9::DrawPrimitiveUP函数差不多,我们使用IDirect3DDevice9::DrawPrimitive函数。不过在使用这个函数之前,我们得告诉设备我们使用哪个数据源,使用IDirect3DDevice9::SetStreamSource函数可以设定数据源。
HRESULT SetStreamSource(
   UINT StreamNumber,
   IDirect3DVertexBuffer9 *pStreamData,
   UINT OffsetInBytes,
   UINT Stride
);
StreamNumber设置和哪个数据流梆定。如果使用单数据流的话,这里设为0。最多支持16个数据流。
pStreamData要绑定的数据。也就是我们创建的顶点缓冲区里面的数据。
OffsetInBytes设置从哪个字节开始读起。如果要读整个缓冲区里面的数据的话,这里设为0
Stride单个数据元素的大小。如果数据源是顶点缓冲的话,那么这里就是每个顶点信息的大小(Sizeof(vertex))。
设置好数据源后,就可以使用IDirect3DDevice9::DrawPrimitive来绘画了。
HRESULT DrawPrimitive(
   D3DPRIMITIVETYPE PrimitiveType,
   unsigned int StartVertex,
   unsigned int PrimitiveCount
);
     PrimitiveType要绘画的图元的种类。
StarVertex设置从顶点缓冲区中的第几个顶点画起。没有特殊情况当然是想把全部的顶点画出来啦,所以一般这里设置从0开始。
PrimitiveCount要绘画的图元的数量。
     好了,这章比较简单。写到这章的时候我才发现这不是入门手册,有一些重要但是我觉得没必要讲的东西我都没有讲明。如果是新手看我写的这些东西,搞不好还会被我迷惑了,呵呵。所以还是建议大家看DXSDK里面的说明文档,虽然是英文的,但是很详细,我现在都还没有看完呢。
       嗯,前面四章把最基本的东西讲完了,使用前面的知识我们可以画一些简单的静止图形。下一章就开始讲矩阵了,它可以使我们的图形动起来。
·向量(也叫矢量,英文叫vector)
    向量就是包含大小(长度)和方向的一个量。向量有2维的,也有3维甚至4维的。在DX的所有结构体中,有一个结构体是用来表示3维向量的,它就是D3DVECTOR,这个结构体很简单,只有三个成员:x、y、z。一般来说,如果不涉及到向量运算的话,用这个结构体来定义一个向量就可以了。我们可以它来表示方向以及顶点在3D世界中的位置等。如果你要对那些向量进行一些运算的话,使用D3DVECTOR就很不方便了,因为在D3DVECTOR这个结构体中没有重载任何的运算符,如果想要做一个加法运算,就得分别对结构体中的每一个成员进行运算了。嘿嘿,不用怕,在DX里面有个叫D3DX的东东(包含d3dx.h头文件),它里面定义了很多方便我们进行数学计算的函数和结构。其中就有D3DXVECTOR2,D3DXVECTOR3,D3DXVECTOR4这三个结构体。看它们的名字就应该知道它们的作用了吧。对于2维和4维的结构体这里就不讲了,其实它们也很简单,和D3DXVECTOR3差不多。不过要说明一点的是D3DXVECTOR3是从D3DVECTOR派生过来的,说明它和D3DVECTOR一样,有x、y、z这三个成员,除此之外,D3DXVECTOR3还重载了小部分算术运算符,这样我们就可以像对待整型那样对D3DXVECTOR3的对象进行加减乘除以及判断是否相等的运算了。同时,由于D3DXVECTOR3是从D3DVECTOR派生过来的,所以两者的对象可以互相赋值,在这两种类型中随便转换。
    还是简单说一下向量的数学运算吧。矢量的加减法很简单,就是分别把两个向量的各个分量作加减运算。向量的乘除法也很简单,它只能对一个数值进行乘除法,运算的结果就是向量中的各个分量分别对那个数值进行乘除法后得出的结果。向量的模就是向量的长度,就是各个分量的平方的和的开方。向量的标准化就是使得向量的模为1,这对在3D世界中实现光照是很有用的。对于向量的运算,还有两个“乘法”,那就是点乘和叉乘了。点乘的结果就是两个向量的模相乘,然后再与这两个向量的夹角的余弦值相乘。或者说是两个向量的各个分量分别相乘的结果的和。很明显,点乘的结果就是一个数,这个数对我们分析这两个向量的特点很有帮助。如果点乘的结果为0,那么这两个向量互相垂直;如果结果大于0,那么这两个向量的夹角小于90度;如果结果小于0,那么这两个向量的夹角大于90度。对于叉乘,它的运算公式令人头晕,我就不说了,大家看下面的公式自己领悟吧……
        //v3 = v1 X v2
        v3.x = v1.y*v2.z – v1.z*v2.y
        v3.y = v1.z*v2.x – v1.x*v2.z
        v3.z = v1.x*v2.y – v1.y*v2.x
是不是很难记啊,如果暂时记不了就算了。其实我们主要还是要知道叉乘的意义。和点乘的结果不一样,叉乘的结果是一个新的向量,这个新的向量与原来两个向量都垂直,至于它的方向嘛,不知大家是否还记得左手定则。来,伸出你的左手,按照第一个向量(v1)指向第二个向量(v2)弯曲你的手掌,这时你的拇指所指向的方向就是新向量(v3)的方向了。通过叉乘,我们很容易就得到某个平面(由两个向量决定的)的法线了。
终于写完了上面的文字,描述数学问题可真是费劲,自己又不愿意画图,辛苦大家了。如果你觉得上面的文字很枯燥,那也没关系。因为上面的不是重点,下面介绍的函数才是希望大家要记住的。
D3DX中有很多很有用的函数,它们可以帮助我们实现上面所讲的所有运算。不过下面我只说和D3DXVECTOR3有关的函数:
计算点乘:FLOAT D3DXVec3Dot
CONST D3DXVECTOR3* pV1,
CONST D3DXVECTOR3* pV2
计算叉乘:D3DXVECTOR3* D3DXVec3Cross
            D3DXVECTOR3* pOut,
CONST D3DXVECTOR3* pV1,
CONST D3DXVECTOR3* pV2
    计算模:FLOAT D3DXVec3Length(
                CONST D3DXVECTOR3* pV)
    标准化向量:D3DXVECTOR3* D3DXVec3Normalize(
                    D3DXVECTOR3* pOut,
                    CONST D3DXVECTOR3 pV)
    对于D3DXVECTOR3的加减乘除运算,上面已经讲了,用+ - * / 就行了。
·矩阵与矩阵运算
    什么是矩阵?这个概念还真不好解释,不过学过线性代数的人肯定都知道矩阵长什么样,那我在这里就不解释了。在D3D中,定义矩阵的结构体是D3DMATRIX:
typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
        };
        float m[4][4];
    };
} D3DMATRIX;
    看这个结构的样子,你就应该很清楚怎么使用它来定义一个矩阵了吧。在这里我顺便说一下C++中union的特性吧。像上面定义的结构体所示,在union里面有两个部分,一个是结构体,另一个是二维数组,它有16个元素。在union中,所有的成员都是共用一个内存块的,这是什么意思呢?继续看上面的代码,结构体中的成员_11和成员m数组的第一个元素是共用一个内存空间,即它们的值是一样的,你对_11赋值的同时也对m[0][0]进行了赋值,_11和m[0][0]的值是一样的。这样有什么好处呢?比如你定义了一个矩阵变量D3DMATRIX mat;你想访问矩阵中第三行第四列的元素,可以这样做:mat._34;另外也可以这样:mat.m[2][3](数组是从位置0开始储存的哦)。看起来使用后者比较麻烦,不过当你把中括号里面的数换成i和j,使用mat.m[i][j]来访问矩阵中的元素,你就应该知道它的好处了吧。
    实际上直接使用D3DMATRIX的情况不多,因为在D3DX中有个更好的结构体,那就是D3DXMATRIX。和D3DXVECTOR3相似,D3DXMATRIX是从D3DMATRIX继承过来的,它重载了很多运算符,使得矩阵的运算很简单。矩阵的运算方法我不打算多说了,下面只介绍和矩阵性质有关的三个函数。
    产生一个单位矩阵:D3DXMATRIX *D3DXMatrixIdentity(
                            D3DXMATRIX *pout);//返回结果
    求转置矩阵:D3DXMATRIX *D3DXMatrixTranspose(
    D3DXMATRIX *pOut,//返回的结果
    CONST D3DXMATRIX *pM );//目标矩阵
    求逆矩阵:D3DXMATRIX *D3DXMatrixInverse(
    D3DXMATRIX *pOut,//返回的结果
    FLOAT *pDeterminant,//设为0
    CONST D3DXMATRIX *pM );//目标矩阵
    至于什么是单位矩阵,什么是转置矩阵,什么是逆矩阵我就不说了,可以看一下线性代数的书,一看就明白了。简单的加减乘除法可以使用D3DXMATRIX结构体里面重载的运算符。两个矩阵相乘也可以用函数来实现,这将在接下来的矩阵变换中讲到。
·矩阵变换
矩阵的基本变换有三种:平移,旋转和缩放。
    平移:
D3DXMATRIX *D3DXMatrixTranslation(
    D3DXMATRIX* pOut,//返回的结果
    FLOAT x, //X轴上的平移量
    FLOAT y, //Y轴上的平移量
    FLOAT z) //Z轴上的平移量
;
    绕X轴旋转:
D3DXMATRIX *D3DXMatrixRotationX(
    D3DXMATRIX* pOut, //返回的结果
    FLOAT Angle //旋转的弧度
);
绕Y轴旋转:
D3DXMATRIX *D3DXMatrixRotationY(
    D3DXMATRIX* pOut, //返回的结果
    FLOAT Angle //旋转的弧度
);
绕Z轴旋转:
D3DXMATRIX *D3DXMatrixRotationZ(
    D3DXMATRIX* pOut, //返回的结果
    FLOAT Angle //旋转的弧度
);
    绕指定轴旋转:
    D3DXMATRIX *D3DXMatrixRotationAxis(  
            D3DXMATRIX *pOut,//返回的结果
        CONST D3DXVECTOR3 *pV,//指定轴的向量
            FLOAT Angle//旋转的弧度
);
    缩放:
D3DXMATRIX *D3DXMatrixScaling(
    D3DXMATRIX* pOut, //返回的结果
    FLOAT sx, //X轴上缩放的量
    FLOAT sy, //Y轴上缩放的量
    FLOAT sz  //Z轴上缩放的量
);
好了,这章就写这么一些东西。如果你觉得好像没学到什么的话,可能是因为不知道上面的知识有什么用吧。下一章我将介绍世界空间、视图空间(也叫摄像机空间)以及投影,这三者对应的是世界矩阵、视图矩阵和投影矩阵。搞清楚这三个空间的作用后,我们就可以利用这章的知识使我们的3D世界动起来了。
 
无论计算机图形技术如何发展,只要它以二维的屏幕作为显示介质,那么它显示的图像即使多么的有立体感,也还是二维的。有时我会想,有没有以某个空间作为显示介质的的可能呢,不过即使有,也只能是显示某个范围内的图像,不可能有无限大的空间作为显示介质,如果有,那就是现实世界了。
    既然显示器的屏幕是二维的,那么我们就要对图像作些处理,让它可以欺骗我们的眼睛,产生一种立体的真实感。在D3D中,这种处理就是一系列的空间变换,从模型空间变到世界空间,再变到视图空间,最后投影到我们的显示器屏幕上。
·世界空间与世界矩阵
    什么是模型空间呢?每个模型(3D物体)都有它自己的空间,空间的中心(原点)就是模型的中心。在模型空间里,只有模型上的不同点有位置的相对关系。那什么是世界空间呢?世界就是物体(模型)所存在的地方。当我们把一个模型放进世界里面去,那么它就有了一个世界坐标,这个世界坐标是用来标记世界中不同的模型所处的位置的。在世界空间里,世界的中心就是原点(0, 0, 0),也就是你显示器屏幕中间的那一点。我们可以在世界空间里摆放很多个模型,并且设置它们在世界空间中的坐标,这样模型与模型之间就有了相对的位置。
    世界矩阵有什么用呢?我们可以利用它来改变世界空间的坐标。这样,在世界空间里面的模型就可以移动、旋转和缩放了。
    我们可以使用上一章末尾所讲的那几个函数来产生世界矩阵。例如产生一个绕X轴旋转的转阵:D3DXMatrixRotationX(&matrix,1)。利用matrix这个矩阵,就可以使世界空间中的物体绕X轴转动1弧度。
    可以结合后面的例子来理解世界矩阵。
·视图空间与视图矩阵
    世界空间建立起来后,我们不一定能看到模型,因为我们还没有“眼睛”啊。在视图空间里,我们可以建立我们在三维空间中的眼睛:摄像机。我们就是通过这个虚拟的摄像机来观察世界空间中的模型的。所以视图空间也叫摄像机空间。
要建立起这个虚拟的摄像机,我们需要一个视图矩阵,产生视图矩阵的一个函数是:
D3DXMATRIX *D3DXMatrixLookAtLH(
    D3DXMATRIX* pOut,
    CONST D3DXVECTOR3* pEye,
    CONST D3DXVECTOR3* pAt,
    CONST D3DXVECTOR3* pUp
);
pOut返回的视图矩阵指针
pEye设置摄像机的位置
pAt设置摄像机的观察点
pUp设置方向“上”
这个函数的后缀LH是表示左手系的意思,聪明的你一定能够猜出肯定有个叫D3DXMatrixLookAtRH的函数。至于左手系和右手系的区别,这里就不多说了,记住左手系中的Z正方向是指向显示器里面的就行了。只能弄懂了视图矩阵的含义,建立视图矩阵完成可以不依赖函数,自己手动完成。视图矩阵其实就是定义了摄像机在世界空间中的位置、观察点、方向“上”这些信息。
    可以结合后面的例子来理解视图矩阵。
·投影与投影矩阵
    定义投影矩阵很像是定义摄像机的镜头,下面看它的函数声明:
D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
    D3DXMATRIX* pOut,
    FLOAT fovY,
    FLOAT Aspect,
    FLOAT zn,
    FLOAT zf
);
pOut返回的投影矩阵指针
fovY定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/4(90度角),那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值设为D3DX_PI/2(180度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为D3DX_PI的话。。。我先编译一下试试(building…)。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊。
Aspect设置纵横比。如果定义为1,那么所看到的物体大小不变。如果定义为其它值,你所看到的物体就会变形。不过一般情况下这个值设为显示器屏幕的长宽比。(终于明白为什么有些人会说电视上的自己看起来会比较胖了……)
zn设置摄像机所能观察到的最远距离
zf设置摄像机所能观察到的最近距离
·一小段代码
    请看以下代码片段:
D3DXMATRIXA16 matWorld;
    D3DXMatrixIdentity( &matWorld );
    D3DXMatrixRotationX( &matWorld, timeGetTime()/1000.0f );
    g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
 
    D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
    D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
 
    D3DXMATRIXA16 matProj;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/2, 1.0f, 1.0f, 500.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
 
通过上面三个转换,就建立了一个我们可以通过显示器屏幕来观察的3D世界。上面三个转换分别是:
从模型空间到世界空间的世界转换:SetTransform( D3DTS_WORLD, &matWorld )。
从世界空间到视图空间的视图转换:SetTransform( D3DTS_VIEW, &matView )。
从视图空间到到屏幕的投影转换:SetTransform( D3DTS_PROJECTION, &matProj )。
现在来观察matWorld,matView,matProj这三个矩阵的特点。我们使用D3DXMatrixRotationX函数来产生了一个绕X轴旋转的转换矩阵,通过设置世界转换,在世界空间里面的物体将绕X轴作旋转。然后我们定义了三个三维的向量,用来设置摄像机的位置,观察方向和定义方向“上”。使用D3DXMatrixLookAtLH函数来把这三个向量放进视图矩阵里面去。然后通过设置视图转换,我们就建立了一个虚拟的摄像机。最后通过D3DXMatrixPerspectiveFovLH函数,我们得到一个投影矩阵,用来设置虚拟摄像机的镜头。
我还是解释一下上面说的那个方向“上”是什么东西吧。这个“上”其实指的就是摄像机在刚建立的时候是如何摆放的,是向左边侧着摆,还是向右边侧着摆,还是倒过来摆,都是通过这个方向“上”来指定的。按照正常的理解,摄像机的“上”方向就是Y轴的正方向,但是我们可以指定方向“上”为Y轴的负方向,这样世界建立起来后就是颠倒的了。不过颠倒与否,也是相对来说的了,试问在没有引力的世界中,谁能说出哪是上哪是下呢?是不是看得一头雾水啊?只要自己亲手改变一下这些参数,就可以体会到了。
设置上面三个转换的先后顺序并不一定得按照世界到视图到投影这个顺序,不过习惯上按照这种顺序来写,感觉会好一点。
·使用矩阵相乘来创建世界矩阵
    在世界空间中的物体运动往往是很复杂的,比如物体自身旋转的同时,还绕世界的原点旋转。怎么实现这种运动呢?通过矩阵相乘来把两个矩阵“混”在一起。现在我们假设某一物体建立在世界的原点上,看以下代码:
    //定义三个矩阵
    D3DXMATRIX matWorld, matWorldY,matMoveLeft;
    //一个矩阵把物体移到(30,0,0)处,一个矩阵使物体绕原点(0,0,0)旋转
    D3DXMatrixTranslation(&matMoveRight,30,0,0);
    D3DXMatrixRotationY(&matWorldY, radian/1000.0f);
//第一次矩阵相乘。先旋转,再平移
    D3DXMatrixMultiply(&matWorld, &matWorldY, &matMoveRight);
    //第二次矩阵相乘。在第一次矩阵相乘的结果上,再以Y轴旋转
    D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldY);
    //设置世界矩阵
    m_pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );
    矩阵相乘的时候,矩阵的先后顺序很重要,如果顺序弄错了,物体就不会按我们预料的那样运动。从最后一次矩阵相乘看起,最后相乘的两个矩阵是matWorld和matWorldY,其中matWorld又是由matWorldY和matMoveRight相乘得来的,那么这三个矩阵相乘的顺序就是(matWorldY,matMoveRight,matWorldY)。这个顺序意味着什么呢?第一个matWorldY使物体绕Y轴旋转,这时候的物体还处于原点,所以它绕Y轴旋转也就是绕自身的旋转。它转呀转呀,这时候matMoveRight来了,它把物体从(0,0,0)移到了(30,0,0),这时候物体就不再是绕Y轴旋转了,它是在(30,0,0)这个位置继续绕自身旋转。然后matWorldY又来了,它使物体再次以Y轴旋转,不过此时物体不在原点了,所以物体就以原点为中心作画圆的运动(它自身的旋转仍在继续),这个圆的半径是30。如果换一个顺序,把matMoveRight放在第一的话,那么就是先移动再旋转再旋转(第二次旋转没用),这时候物体就只是画圆运动而已,它自身没有旋转。如果把matMoveRight放在最后,那么就是先旋转再旋转(第二次旋转没用)再移动,这时候物体就没有作画圆运动了,它只是在(30,0,0)这个位置上作自身旋转。好了,理解这个需要一点点想象力。你可以先写好几个矩阵相乘的顺序,自己想象一下相乘的结果会使物体作什么运动,然后再编译执行程序,看看物体的运动是不是和自己想像中的一样,这样可以锻炼自己的空间思维能力。
    好了,又写完一章了。下一章可能要过一些日子才能写。因为自己还没找到工作,国庆过后就得出发去找工了,接下来的日子要作一些找工前的准备,所以就没什么时间继续写了。至于什么时候写第七篇,呵呵,应该不用很久,找到工作后立刻回来这里报道~~大家祝我好运吧^_^

    其实DirectX9.0里有非常详细的教程和参考,大多数人只需要看看这些帮助就可以自己学习D3D了,我的这篇文章适合那些很懒但想快速入门、不懂英文或编程知识很欠缺的人看。装好DirectX9.0后,打开VC.net,新建一个Win32工程,在StdAfx.h里添加下面的语句:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">#include &lt;d3d9.h&gt; // D3D</font></span><span style="FONT-SIZE: 9pt"><font face="Courier New">标准头文件<br>#include &lt;D3dx9math.h&gt; // D3D数学库头文件<br>#include &lt;stdio.h&gt; // 这个不用我说了吧?<br>#pragma comment( lib, "d3d9" ) // D3D的静态库<br>#pragma comment( lib, "d3dx9" ) // D3D数学库的静态库</font></span></div>

        </td>

    </tr>

</tbody>

然后把winmain所在文件的代码做如下的修改:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">#include "stdafx.h"<br>#define </font><a href="http://www.yesky.com/key/1435/156435.html" target=_blank><font face="Courier New">MAX</font></a><font face="Courier New">_LOADSTRING 100<br><br>HINSTANCE g_hInst;<br>HWND g_hWnd;<br>IDirect3D9 *g_pD3D;<br>IDirect3DDevice9 *g_pd3dDevice;<br>IDirect3DVertexBuffer9 *g_pVB;<br><br>TCHAR szTitle[</font><a href="http://www.yesky.com/key/1081/161081.html" target=_blank><font face="Courier New">MAX</font></a><font face="Courier New">_LOADSTRING];<br>TCHAR szWindowClass[MAX_LOADSTRING];<br>ATOM MyRegisterClass(HINSTANCE hInstance);<br>BOOL InitInstance(HINSTANCE, int);<br>LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);<br>LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);<br>void OnIdle( void );<br>void OnCreate( HWND hWnd );<br>HRESULT InitD3D( void );<br>HRESULT CreateObject( void );<br>void ReleaseD3D( void );<br>HRESULT SetModalMatrix( void );<br>HRESULT SetProjMatrix( WORD wWidth, WORD wHeight );<br>void BeforePaint( void );<br>int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)<br>{<br></font></span><span style="FONT-SIZE: 9pt"><font face="Courier New"> MSG msg;<br> HACCEL hAccelTable;<br><br> LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);<br> LoadString(hInstance, IDC_D3DTEST, szWindowClass, MAX_LOADSTRING);<br> MyRegisterClass(hInstance);<br><br> if (!InitInstance (hInstance, nCmdShow)) <br> {<br>  return FALSE;<br> }<br><br> hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_D3DTEST);<br><br> while ( true )<br> {<br>  if ( PeekMessage( &amp;msg, NULL, 0, 0, PM_REMOVE ) )<br>  {<br>   if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg)) <br>   {<br>    TranslateMessage(&amp;msg);<br>    DispatchMessage(&amp;msg);<br>   }<br>  continue;<br>  }<br>  if ( WM_QUIT == msg.message )<br>  {<br>   break;<br>  }<br>  OnIdle();<br> } <br> UnregisterClass( szWindowClass, g_hInst );<br> return (int)msg.wParam;<br>}<br><br>ATOM MyRegisterClass( HINSTANCE hInstance )<br>{<br> WNDCLASSEX wcex;<br> wcex.cbSize = sizeof(WNDCLASSEX); <br> wcex.style = CS_HREDRAW | CS_VREDRAW;<br> wcex.lpfnWndProc = (WNDPROC)WndProc;<br> wcex.cbClsExtra = 0;<br> wcex.cbWndExtra = 0;<br> wcex.hInstance = hInstance;<br> wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_D3DTEST);<br> wcex.hCursor = LoadCursor(NULL, IDC_ARROW);<br> wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);<br> wcex.lpszMenuName = (LPCTSTR)IDC_D3DTEST;<br> wcex.lpszClassName = szWindowClass;<br> wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);<br> return RegisterClassEx(&amp;wcex);<br>}<br><br>BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )<br>{<br> g_hInst = hInstance;<br> CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL );<br> if ( !g_hWnd )<br> {<br>  return FALSE;<br> }<br> ShowWindow( g_hWnd, nCmdShow );<br> UpdateWindow( g_hWnd );<br> return TRUE;<br>}<br><br>LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br> int wmId, wmEvent;<br> </font><a href="http://www.yesky.com/key/656/160656.html" target=_blank><font face="Courier New">switch</font></a><font face="Courier New"> (message) <br> {<br>  case WM_CREATE:<br>   OnCreate( hWnd );<br>   break;<br>  case WM_COMMAND:<br>   wmId = LOWORD(wParam); <br>   wmEvent = HIWORD(wParam); <br>   switch (wmId)<br>   {<br>    case </font><a href="http://www.yesky.com/key/2371/152371.html" target=_blank><font face="Courier New">IDM</font></a><font face="Courier New">_EXIT:<br>     DestroyWindow(hWnd);<br>     break;<br>    default:<br>     return DefWindowProc(hWnd, message, wParam, lParam);<br>   }<br>   break;<br>  case WM_SIZE:<br>   SetProjMatrix( LOWORD( lParam ), HIWORD( lParam ) );<br>   break;<br>  case WM_DESTROY:<br>   ReleaseD3D();<br>   PostQuitMessage(0);<br>   break;<br>  default:<br>   return DefWindowProc(hWnd, message, wParam, lParam);<br> }<br> return 0;<br>}<br><br>void OnCreate( HWND hWnd )<br>{<br> g_hWnd = hWnd;<br> InitD3D();<br> CreateObject();<br>}<br><br>void ReleaseD3D( void )<br>{<br>}<br><br>HRESULT InitD3D( void )<br>{<br> return S_OK;<br>}<br><br>void BeforePaint( void )<br>{<br>}<br><br>HRESULT CreateObject( void )<br>{<br> return S_OK;<br>}<br><br>void OnIdle( void )<br>{<br>}<br><br>HRESULT SetModalMatrix( void )<br>{<br> return S_OK;<br>}<br><br>HRESULT SetProjMatrix( WORD wWidth, WORD wHeight )<br>{<br> return S_OK;<br>}</font></span></div>

        </td>

    </tr>

</tbody>

  上面的代码仅是一个框架,仔细研究过上篇文章的朋友应该非常清楚这些代码的含意,不过我还是需要大至讲解一下。在InitInstance函数中,初始化程序实例的开始,我们将该实例的句柄放到全局变量中去,以便以后使用:

g_hInst = hInstance;

  在创建窗体时,我并没有直接将CreateWindow的返回值——创建成功的窗体句柄赋给全局变量g_hWnd,因为CreateWindow函数在执行中会引发几个消息,其中有WM_CREATE,在这个消息的处理函数中OnCreate中,我执行了下面的语句:

g_hWnd = hWnd;

  这样,我们就可以早一点用到g_hWnd了。SetProjMatrix是设置投影矩阵的,投影矩形仅受窗口纵横比影响,所以在WM_SIZE事件时调用时就可以了。而SetModalMatrix是设置模型矩阵的,也就是眼睛点和视点之类的东东,所以它一定要在Rander的时候,准确的说是在Render之前执行。InitD3D在窗体创建时调用,用于初始化D3D设备。CreateObject和InitD3D一样是初始化函数,它用于创建三维对象。
下面我们来填写代码,首先是初始化,我们在InitD3D函数里填写下面的代码:
g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
if( NULL == g_pD3D )
{
return E_FAIL;
}
 
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
 
g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE,
&d3dpp, &g_pd3dDevice );
g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT,
D3DCOLOR_COLORVALUE( 0.3f, 0.3f, 0.3f, 1.0 ) );
g_pd3dDevice->LightEnable( 0, TRUE);
 
D3DMATERIAL9 mtrl;
ZeroMemory( &mtrl, sizeof(mtrl) );
mtrl.Diffuse.r = mtrl.Ambient.r = 140.0f / 255.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 200.0f / 255.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 255.0f / 255.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
g_pd3dDevice->SetMaterial( &mtrl );
return S_OK;
  Direct3DCreate9函数为我们创建了一个D3D接口指针,并将接口指针返回到全局变量中保存起来,参数必须为D3D_SDK_VERSION。D3DPRESENT_PARAMETERS是一个结构体,它就像OpenGL中的PixelFormat一样,是创建时指定设备的一些属性的。
d3dpp.Windowed = TRUE; // 设备是窗口设备而不是全屏
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // 翻转缓冲区时不改动后台缓冲
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; // ARGB颜色模式
  在这之后,我们就可以调用CreateDevice来创建设备了,第一个参数指定使用主显示驱动,第二个参数指定该设备是否使用硬件来处理显示,第三个参数当然是你的窗体句柄,第四个参数如果指定D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE效率会提高不少。
g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, D3DCOLOR_COLORVALUE( 0.6f, 0.6f, 0.6f, 1.0 ) ); 
  这两句用于打开灯光和设置环境光照颜色。接着我设置了材质:
mtrl.Diffuse.r = mtrl.Ambient.r = 140.0f / 255.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 200.0f / 255.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 255.0f / 255.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
  为了和上一篇的例程做对比,我使用了相同的材质。当CreateDevice执行成功后,我们就该创建基于D3D的三维物体了。
D3DVECTOR SrcBox[] = {
 { 5.0f, 5.0f, 0.0f }, { 5.0f, 5.0f, 10.0f },
 { 5.0f, -5.0f, 0.0f }, { 5.0f, -5.0f, 10.0f },
 {-5.0f, -5.0f, 0.0f }, {-5.0f, -5.0f, 10.0f },
 {-5.0f, 5.0f, 0.0f }, {-5.0f, 5.0f, 10.0f },
};
WORD wIndex[] ={
 0, 4, 6, 0, 2, 4,
 0, 6, 7, 0, 7, 1,
 0, 3, 2, 0, 1, 3,
 5, 2, 3, 5, 4, 2,
 5, 6, 4, 5, 7, 6,
 5, 1, 7, 5, 3, 1,
};
  要说明的是D3D为我们准备了很好用的结构体D3DVECTOR,封装了三维座标的X、Y和Z。我们还需要再定义一个我们自己的结构体来存放我们的三维座标信息:
struct CUSTOMVERTEX
{
 D3DVECTOR pos;
 D3DVECTOR normal;
}; 
  第一个成员pos用来存储顶点座标数据,第二个成员normal用来存储这个点所在平面的法向量——这个概念我在上一篇讲过的。和OpenGL一样,我们同样需要按索引序展开顶点数组:
CUSTOMVERTEX ExpandBox[sizeof(wIndex) / sizeof(WORD)];
for ( int i = 0; i < 36; i++ )
{
 ExpandBox[i].pos = SrcBox[ wIndex[i] ];
}
  然后用下面的代码为顶点计算法向量。
for ( i = 0; i < 12; i++ )
{
 D3DVECTOR Tri[3];
 Tri[0] = ExpandBox[ i * 3 + 0 ].pos;
 Tri[1] = ExpandBox[ i * 3 + 1 ].pos;
 Tri[2] = ExpandBox[ i * 3 + 2 ].pos;
 ExpandBox[ i * 3 + 0 ].normal.x = 0.0f;
 ExpandBox[ i * 3 + 0 ].normal.y = 0.0f;
 ExpandBox[ i * 3 + 0 ].normal.z = 1.0f;
 CalcNormal( Tri, &(ExpandBox[ i * 3 + 0 ].normal) );
 ExpandBox[ i * 3 + 1 ].normal = ExpandBox[ i * 3 + 0 ].normal;
 ExpandBox[ i * 3 + 2 ].normal = ExpandBox[ i * 3 + 0 ].normal;
  在这里我需要花点篇幅讲一个概念,一个仅在D3D中存在的概念FVF。D3D在处理显示数据时和OpenGL的方式不大一样,OpenGL是指定独立的数组,比如VertexBuffer、IndexBuffer、NormalBuffer等,而D3D是把每一个顶点的这些数据放在一起,组成一个单元进行处理,也就是说只有一个数组,而在数组的每一个元素中都包括了单个顶点的所有数据,所以他被称为Flexible Vertex Format,我刚才定义的结构体CUSTOMVERTEX也就是做为数组的一个单元。每一个单元可以包含你所需要的信息,比如你只需要顶点座标数据和颜色,那么你只需要对那个结构体稍加修改就可以了,但是怎么让D3D 知道你的结构体里包含哪些数据呢?当然,我们可以在CreateVertexBuffer的时候指定一个参数来告诉D3D:D3DFVF_XYZ | D3DFVF_NORMAL,在很多地方我们都可能用到这个值,所以为了方便使用和维护我们定义一个宏来表示它:
#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL )
那么到这里你可以会问另一个问题,也许D3D可以知道我们的元素里包含顶点的哪些数据,但D3D又是怎样得知这些数据在结构体里,也就是在内存中的排列顺序的呢?很不幸,D3D无法得知你的排列顺序,但D3D指定了这些数据的排列顺序,比如法向量一定在顶点后面,而颜色又一定要放在法向量后面。关于这个排列顺序表你得去看看MSDN里关于Vertex Formats的详细说明了。下面我们来创建顶点数组:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">if( FAILED( g_pd3dDevice-&gt;CreateVertexBuffer( sizeof(ExpandBox),<br>0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &amp;g_pVB, NULL ) ) )<br>{<br></font></span><span style="FONT-SIZE: 9pt"><font face="Courier New"> return E_FAIL;<br>}</font></span></div>

        </td>

    </tr>

</tbody>

  这些参数上面都提到过,相信你一看就会明白,我就不多说了。怎样把我们的方盒数据存储进这创建好的顶点缓冲区呢?DirectX有自己的内存管理方式,也就是Lock和UnLock的模式:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">VOID* pVertices;<br><br>if( FAILED( g_pVB-&gt;Lock( 0, sizeof(ExpandBox), (void**)&amp;pVertices, 0 ) ) )<br><br>return E_FAIL;<br><br>MoveMemory( pVertices, ExpandBox, sizeof(ExpandBox) );<br><br>g_pVB-&gt;Unlock();</font></span></div>

        </td>

    </tr>

</tbody>

  和OpenGL一样,在初始化工作结束后,必须要做的另一件事就是设置矩阵,首先是投影矩阵,我们把这些代码加到SetProjMatrix函数中去:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">D3DXMATRIX matProj;<br>D3DXMatrixPerspectiveFovLH( &amp;matProj, D3DX_PI/4, (float)wWidth / (float)wHeight, 1.0f, 100.0f );<br><br>return g_pd3dDevice-&gt;SetTransform( D3DTS_PROJECTION, &amp;matProj );</font></span></div>

        </td>

    </tr>

</tbody>

  然后是视图矩阵,把下面的代码加到SetModalMatrix中

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">static float fRadius = 0.5f;<br>fRadius -= 0.003f;<br>if ( fRadius &lt; 0)<br>{<br></font></span><span style="FONT-SIZE: 9pt"><font face="Courier New"> fRadius = D3DX_PI * 2 ;<br>}<br><br>D3DXMATRIX matWorld;<br>D3DXMatrixRotationZ( &amp;matWorld, 0.0f );<br>if ( FAILED( g_pd3dDevice-&gt;SetTransform( D3DTS_WORLD, &amp;matWorld ) ) )<br>{<br> return E_FAIL;<br>}<br><br>D3DXMATRIX matView;<br>D3DXMatrixLookAtLH( &amp;matView, &amp;D3DXVECTOR3( cosf( fRadius ) * 40.0f, sinf( fRadius ) * 40.0f, 30.0 ), &amp;D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), &amp;D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) );<br><br>g_pd3dDevice-&gt;SetTransform( D3DTS_VIEW, &amp;matView );<br>return S_OK;</font></span></div>

        </td>

    </tr>

</tbody>

  我想我不说你也可以理解这些代码的含义,是不是和OpenGL非常相似呢?好的,现在一切准备工作就续,现在我们可以开始绘制方盒了。找到OnIdel处理函数,然后在这里添加下面的代码:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">if ( g_pd3dDevice != NULL )<br>{<br></font></span><span style="FONT-SIZE: 9pt"><font face="Courier New"> g_pd3dDevice-&gt;Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,255,200), 1.0f, 0 );<br> if ( SUCCEEDED( g_pd3dDevice-&gt;BeginScene() ) )<br> {<br>  BeforePaint();<br>  if ( FAILED( SetModalMatrix() ) )<br>  {<br>   return;<br>  }<br>  g_pd3dDevice-&gt;SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );<br>  g_pd3dDevice-&gt;SetFVF( D3DFVF_CUSTOMVERTEX );<br>  g_pd3dDevice-&gt;DrawPrimitive( D3DPT_TRIANGLELIST, 0, 12 );<br>  g_pd3dDevice-&gt;EndScene();<br>  g_pd3dDevice-&gt;Present( NULL, NULL, NULL, NULL );<br> }<br>} </font></span></div>

        </td>

    </tr>

</tbody>

  g_pd3dDevice->Clear相当于glClear, g_pd3dDevice->BeginScene和g_pd3dDevice->EndScene 相当于glBegin和glEnd,g_pd3dDevice->SetStreamSource 相当于glSet**Pointer,g_pd3dDevice->DrawPrimitive 相当于glDrawArray,g_pd3dDevice->Present相当于SwapBuffer,这里和OpenGL的不同之处在于D3D在这里要设备FVF,一个很简单的过程:

g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

  另外,D3D的背景色是在Clear的时候指定的。现在你可以运行你的程序了,但是你会发现,盒子是黑色的一片,那是因为你没有打开任何光源。而OpenGL在没有打开任何光源时物体是纯白色的,呵呵,具有一定的迷惑性哦~现在我们来打开一个灯,来照亮这个物体。就在BeforePaint里:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">

        <div><span style="FONT-SIZE: 9pt"><font face="Courier New">D3DLIGHT9 light;<br>ZeroMemory( &amp;light, sizeof(light) );<br>light.Position = D3DXVECTOR3( 30.0f, 30.0f, 30.0f );<br>light.Attenuation1 = 0.05f;<br>light.Diffuse.r = 1.0f;<br>light.Diffuse.g = 1.0f;<br>light.Diffuse.b = 1.0f;<br>light.Range = 1000.0f;<br>light.Type = D3DLIGHT_POINT;<br>g_pd3dDevice-&gt;SetLight( 0, &amp;light );<br>g_pd3dDevice-&gt;LightEnable( 0, TRUE);</font></span></div>

        </td>

    </tr>

</tbody>

  为什么要放在BeforePaint里呢?因为我们在后面还可以实现动态光影的效果,也就是让光源动起来。这里要格外注意的时,这个顶不是方向光源,是一个点光源,D3DLIGHT_POINT,它的光是没有方向,从座标点向四周任何方向发散的,他也具有衰减性,你可以设置三个衰减分量,在D3D里的衰减公式是:Atten = 1 / ( att0 + att1 * d + att2 * d * d )所以,当你的三个衰减值都为0时,就会出错。在这里我们用到了att1,让它等于0.05,很小的值对吗?越小表示衰减越小,光照也就越强。

  现在你的代码应该是这样样子:

<tbody>

    <tr>

        <td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; FONT-SIZE: 10pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt; FONT-FAMILY: Times New Roman">

        <p>// D3D006.cpp : 定义应用程序的入口点。<br>//</p>

        <p>#include "stdafx.h"<br>#include "D3D006.h"</p>

        <p>#define MAX_LOADSTRING 100<br>#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL )</p>

        <p>struct CUSTOMVERTEX<br>{<br>&nbsp;D3DVECTOR pos;<br>&nbsp;D3DVECTOR normal;<br>};</p>

        <p>// 全局变量:<br>HWND g_hWnd;<br>IDirect3D9 *g_pD3D;<br>IDirect3DDevice9 *g_pd3dDevice;<br>IDirect3DVertexBuffer9 *g_pVB;</p>

        <p>HINSTANCE hInst;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 当前实例<br>TCHAR szTitle[MAX_LOADSTRING];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 标题栏文本<br>TCHAR szWindowClass[MAX_LOADSTRING];&nbsp;&nbsp;&nbsp;// 主窗口类名</p>

        <p>// 此代码模块中包含的函数的前向声明:<br>ATOM&nbsp;&nbsp;&nbsp;&nbsp;MyRegisterClass(HINSTANCE hInstance);<br>BOOL&nbsp;&nbsp;&nbsp;&nbsp;InitInstance(HINSTANCE, int);<br>LRESULT CALLBACK&nbsp;WndProc(HWND, UINT, WPARAM, LPARAM);<br>INT_PTR CALLBACK&nbsp;About(HWND, UINT, WPARAM, LPARAM);</p>

        <p>void OnIdle( void );<br>void OnCreate( HWND hWnd );<br>HRESULT InitD3D( void );<br>HRESULT CreateObject( void );<br>void ReleaseD3D( void );<br>HRESULT SetModalMatrix( void );<br>HRESULT SetProjMatrix( WORD wWidth, WORD wHeight );<br>void BeforePaint( void );<br>void CalcNormal( const D3DVECTOR *pVertices, D3DVECTOR *pNormal )<br>{<br>&nbsp;D3DVECTOR v1, v2;<br>&nbsp;v1.x = pVertices[0].x - pVertices[1].x;<br>&nbsp;v1.y = pVertices[0].y - pVertices[1].y;<br>&nbsp;v1.z = pVertices[0].z - pVertices[1].z;<br>&nbsp;v2.x = pVertices[1].x - pVertices[2].x;<br>&nbsp;v2.y = pVertices[1].y - pVertices[2].y;<br>&nbsp;v2.z = pVertices[1].z - pVertices[2].z;<br>&nbsp;D3DXVECTOR3 Temp( v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z,v1.x * v2.y - v1.y * v2.x );<br>&nbsp;D3DXVec3Normalize( (D3DXVECTOR3*)pNormal, &amp;Temp );<br>}</p>

        <p>class CTimer<br>{<br>public:<br>&nbsp;CTimer() {QueryPerformanceFrequency(&amp;m_Frequency); Start();}<br>&nbsp;void Start() {QueryPerformanceCounter(&amp;m_StartCount);}<br>&nbsp;double End() {LARGE_INTEGER CurrentCount;QueryPerformanceCounter(&amp;CurrentCount);return double(CurrentCount.LowPart - m_StartCount.LowPart) / (double)m_Frequency.LowPart;}</p>

        <p>private:<br>&nbsp;LARGE_INTEGER m_Frequency;<br>&nbsp;LARGE_INTEGER m_StartCount;<br>};</p>

        <p>int APIENTRY _tWinMain(HINSTANCE hInstance,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HINSTANCE hPrevInstance,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPTSTR&nbsp;&nbsp;&nbsp; lpCmdLine,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nCmdShow)<br>{<br>&nbsp;UNREFERENCED_PARAMETER(hPrevInstance);<br>&nbsp;UNREFERENCED_PARAMETER(lpCmdLine);</p>

        <p>&nbsp;&nbsp;// TODO: 在此放置代码。<br>&nbsp;MSG msg;<br>&nbsp;HACCEL hAccelTable;</p>

        <p>&nbsp;// 初始化全局字符串<br>&nbsp;LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);<br>&nbsp;LoadString(hInstance, IDC_D3D006, szWindowClass, MAX_LOADSTRING);<br>&nbsp;MyRegisterClass(hInstance);</p>

        <p>&nbsp;// 执行应用程序初始化:<br>&nbsp;if (!InitInstance (hInstance, nCmdShow))<br>&nbsp;{<br>&nbsp;&nbsp;return FALSE;<br>&nbsp;}</p>

        <p>&nbsp;hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D3D006));</p>

        <p>&nbsp;// 主消息循环:<br>&nbsp;while (true)<br>&nbsp;{<br>&nbsp;&nbsp;if (PeekMessage(&amp;msg, NULL, 0, 0, PM_REMOVE))<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg))<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;TranslateMessage(&amp;msg);<br>&nbsp;&nbsp;&nbsp;&nbsp;DispatchMessage(&amp;msg);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;continue;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;if (WM_QUIT == msg.message)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;OnIdle();<br>&nbsp;}</p>

        <p>&nbsp;UnregisterClass(szWindowClass, hInst);</p>

        <p>&nbsp;return (int) msg.wParam;<br>}</p>

        <p>&nbsp;</p>

        <p>//<br>//&nbsp; 函数: MyRegisterClass()<br>//<br>//&nbsp; 目的: 注册窗口类。<br>//<br>//&nbsp; 注释:<br>//<br>//&nbsp;&nbsp;&nbsp; 仅当希望<br>//&nbsp;&nbsp;&nbsp; 此代码与添加到 Windows 95 中的&#8220;RegisterClassEx&#8221;<br>//&nbsp;&nbsp;&nbsp; 函数之前的 Win32 系统兼容时,才需要此函数及其用法。调用此函数十分重要,<br>//&nbsp;&nbsp;&nbsp; 这样应用程序就可以获得关联的<br>//&nbsp;&nbsp;&nbsp; &#8220;格式正确的&#8221;小图标。<br>//<br>ATOM MyRegisterClass(HINSTANCE hInstance)<br>{<br>&nbsp;WNDCLASSEX wcex;</p>

        <p>&nbsp;wcex.cbSize = sizeof(WNDCLASSEX);</p>

        <p>&nbsp;wcex.style&nbsp;&nbsp;&nbsp;= CS_HREDRAW | CS_VREDRAW;<br>&nbsp;wcex.lpfnWndProc&nbsp;= WndProc;<br>&nbsp;wcex.cbClsExtra&nbsp;&nbsp;= 0;<br>&nbsp;wcex.cbWndExtra&nbsp;&nbsp;= 0;<br>&nbsp;wcex.hInstance&nbsp;&nbsp;= hInstance;<br>&nbsp;wcex.hIcon&nbsp;&nbsp;&nbsp;= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3D006));<br>&nbsp;wcex.hCursor&nbsp;&nbsp;= LoadCursor(NULL, IDC_ARROW);<br>&nbsp;wcex.hbrBackground&nbsp;= (HBRUSH)(COLOR_WINDOW+1);<br>&nbsp;wcex.lpszMenuName&nbsp;= MAKEINTRESOURCE(IDC_D3D006);<br>&nbsp;wcex.lpszClassName&nbsp;= szWindowClass;<br>&nbsp;wcex.hIconSm&nbsp;&nbsp;= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));</p>

        <p>&nbsp;return RegisterClassEx(&amp;wcex);<br>}</p>

        <p>//<br>//&nbsp;&nbsp; 函数: InitInstance(HINSTANCE, int)<br>//<br>//&nbsp;&nbsp; 目的: 保存实例句柄并创建主窗口<br>//<br>//&nbsp;&nbsp; 注释:<br>//<br>//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在此函数中,我们在全局变量中保存实例句柄并<br>//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建和显示主程序窗口。<br>//<br>BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)<br>{<br>&nbsp;&nbsp; HWND hWnd;</p>

        <p>&nbsp;&nbsp; hInst = hInstance; // 将实例句柄存储在全局变量中</p>

        <p>&nbsp;&nbsp; hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);</p>

        <p>&nbsp;&nbsp; if (!hWnd)<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return FALSE;<br>&nbsp;&nbsp; }</p>

        <p>&nbsp;&nbsp; ShowWindow(hWnd, nCmdShow);<br>&nbsp;&nbsp; UpdateWindow(hWnd);</p>

        <p>&nbsp;&nbsp; return TRUE;<br>}</p>

        <p>//<br>//&nbsp; 函数: WndProc(HWND, UINT, WPARAM, LPARAM)<br>//<br>//&nbsp; 目的: 处理主窗口的消息。<br>//<br>//&nbsp; WM_COMMAND&nbsp;- 处理应用程序菜单<br>//&nbsp; WM_PAINT&nbsp;- 绘制主窗口<br>//&nbsp; WM_DESTROY&nbsp;- 发送退出消息并返回<br>//<br>//<br>LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;int wmId, wmEvent;<br>&nbsp;PAINTSTRUCT ps;<br>&nbsp;HDC hdc;</p>

        <p>&nbsp;switch (message)<br>&nbsp;{<br>&nbsp;case WM_CREATE:<br>&nbsp;&nbsp;OnCreate(hWnd);<br>&nbsp;&nbsp;break;<br>&nbsp;case WM_COMMAND:<br>&nbsp;&nbsp;wmId&nbsp;&nbsp;&nbsp; = LOWORD(wParam);<br>&nbsp;&nbsp;wmEvent = HIWORD(wParam);<br>&nbsp;&nbsp;// 分析菜单选择:<br>&nbsp;&nbsp;switch (wmId)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;case IDM_ABOUT:<br>&nbsp;&nbsp;&nbsp;DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;case IDM_EXIT:<br>&nbsp;&nbsp;&nbsp;DestroyWindow(hWnd);<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;default:<br>&nbsp;&nbsp;&nbsp;return DefWindowProc(hWnd, message, wParam, lParam);<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;break;<br>&nbsp;case WM_PAINT:<br>&nbsp;&nbsp;hdc = BeginPaint(hWnd, &amp;ps);<br>&nbsp;&nbsp;// TODO: 在此添加任意绘图代码...<br>&nbsp;&nbsp;EndPaint(hWnd, &amp;ps);<br>&nbsp;&nbsp;break;<br>&nbsp;case WM_SIZE:<br>&nbsp;&nbsp;SetProjMatrix( LOWORD( lParam ), HIWORD( lParam ) );<br>&nbsp;&nbsp;break;<br>&nbsp;case WM_DESTROY:<br>&nbsp;&nbsp;ReleaseD3D();<br>&nbsp;&nbsp;PostQuitMessage(0);<br>&nbsp;&nbsp;break;<br>&nbsp;default:<br>&nbsp;&nbsp;return DefWindowProc(hWnd, message, wParam, lParam);<br>&nbsp;}<br>&nbsp;return 0;<br>}</p>

        <p>// &#8220;关于&#8221;框的消息处理程序。<br>INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;UNREFERENCED_PARAMETER(lParam);<br>&nbsp;switch (message)<br>&nbsp;{<br>&nbsp;case WM_INITDIALOG:<br>&nbsp;&nbsp;return (INT_PTR)TRUE;</p>

        <p>&nbsp;case WM_COMMAND:<br>&nbsp;&nbsp;if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;EndDialog(hDlg, LOWORD(wParam));<br>&nbsp;&nbsp;&nbsp;return (INT_PTR)TRUE;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;break;<br>&nbsp;}<br>&nbsp;return (INT_PTR)FALSE;<br>}</p>

        <p>void OnCreate(HWND hWnd)<br>{<br>&nbsp;g_hWnd = hWnd;<br>&nbsp;InitD3D();<br>&nbsp;CreateObject();<br>}</p>

        <p>void ReleaseD3D()<br>{<br>&nbsp;if (g_pVB != NULL)<br>&nbsp;{<br>&nbsp;&nbsp;g_pVB-&gt;Release();<br>&nbsp;}<br>&nbsp;if (g_pd3dDevice != NULL)<br>&nbsp;{<br>&nbsp;&nbsp;g_pd3dDevice-&gt;Release();<br>&nbsp;}<br>&nbsp;if (g_pD3D != NULL)<br>&nbsp;{<br>&nbsp;&nbsp;g_pD3D-&gt;Release();<br>&nbsp;}<br>}</p>

        <p>HRESULT InitD3D()<br>{<br>&nbsp;g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);<br>&nbsp;D3DPRESENT_PARAMETERS d3dpp;<br>&nbsp;ZeroMemory(&amp;d3dpp, sizeof(d3dpp));<br>&nbsp;d3dpp.Windowed&nbsp;=&nbsp;TRUE;<br>&nbsp;d3dpp.SwapEffect=&nbsp;D3DSWAPEFFECT_DISCARD;<br>&nbsp;d3dpp.BackBufferFormat&nbsp;=&nbsp;D3DFMT_A8R8G8B8;<br>&nbsp;g_pD3D-&gt;CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,<br>&nbsp;&nbsp;D3DCREATE_HARDWARE_VERTEXPROCESSING|D3DCREATE_PUREDEVICE,<br>&nbsp;&nbsp;&amp;d3dpp, &amp;g_pd3dDevice);<br>&nbsp;g_pd3dDevice-&gt;SetRenderState(D3DRS_LIGHTING, TRUE);<br>&nbsp;g_pd3dDevice-&gt;SetRenderState(D3DRS_AMBIENT,<br>&nbsp;&nbsp;D3DCOLOR_COLORVALUE(0.6f, 0.6f, 0.6f, 1.0));<br>&nbsp;g_pd3dDevice-&gt;LightEnable(0, TRUE);</p>

        <p>&nbsp;D3DMATERIAL9 mtrl;<br>&nbsp;ZeroMemory(&amp;mtrl, sizeof(mtrl));<br>&nbsp;mtrl.Diffuse.r&nbsp;=&nbsp;mtrl.Ambient.r&nbsp;=&nbsp;140.0f&nbsp;/&nbsp;255.0f;<br>&nbsp;mtrl.Diffuse.b&nbsp;=&nbsp;mtrl.Ambient.b&nbsp;=&nbsp;200.0f&nbsp;/&nbsp;255.0f;<br>&nbsp;mtrl.Diffuse.g&nbsp;=&nbsp;mtrl.Ambient.g&nbsp;=&nbsp;255.0f&nbsp;/&nbsp;255.0f;<br>&nbsp;mtrl.Diffuse.a&nbsp;=&nbsp;mtrl.Ambient.a&nbsp;=&nbsp;1.0f;<br>&nbsp;g_pd3dDevice-&gt;SetMaterial(&amp;mtrl);</p>

        <p>&nbsp;return S_OK;<br>}</p>

        <p>void BeforePaint(void)<br>{<br>&nbsp;D3DLIGHT9 light;<br>&nbsp;ZeroMemory(&amp;light, sizeof(light));<br>&nbsp;light.Position&nbsp;=&nbsp;D3DXVECTOR3(30.0f, 30.0f, 30.0f);<br>&nbsp;light.Attenuation1&nbsp;=&nbsp;0.5f;<br>&nbsp;light.Diffuse.r&nbsp;=&nbsp;1.0f;<br>&nbsp;light.Diffuse.g&nbsp;=&nbsp;1.0f;<br>&nbsp;light.Diffuse.b&nbsp;=&nbsp;1.0f;<br>&nbsp;light.Range&nbsp;=&nbsp;1000.0f;<br>&nbsp;light.Type&nbsp;=&nbsp;D3DLIGHT_POINT;<br>&nbsp;g_pd3dDevice-&gt;SetLight(0, &amp;light);<br>&nbsp;g_pd3dDevice-&gt;LightEnable(0, TRUE);<br>}</p>

        <p>HRESULT&nbsp;CreateObject()<br>{<br>&nbsp;D3DVECTOR&nbsp;SrcBox[]={<br>&nbsp;&nbsp;{ 5.0f,&nbsp; 5.0f, 0.0f},{ 5.0f,&nbsp; 5.0f, 10.0f},<br>&nbsp;&nbsp;{ 5.0f, -5.0f, 0.0f},{ 5.0f, -5.0f, 10.0f},<br>&nbsp;&nbsp;{-5.0f, -5.0f, 0.0f},{-5.0f, -5.0f, 10.0f},<br>&nbsp;&nbsp;{-5.0f,&nbsp; 5.0f, 0.0f},{-5.0f,&nbsp; 5.0f, 10.0f},<br>&nbsp;};<br>&nbsp;WORD wIndex[]={<br>&nbsp;&nbsp;0,4,6,0,2,4,<br>&nbsp;&nbsp;0,6,7,0,7,1,<br>&nbsp;&nbsp;0,3,2,0,1,3,<br>&nbsp;&nbsp;5,2,3,5,4,2,<br>&nbsp;&nbsp;5,6,4,5,7,6,<br>&nbsp;&nbsp;5,1,7,5,3,1,<br>&nbsp;};<br>&nbsp;CUSTOMVERTEX ExpandBox[sizeof(wIndex)/sizeof(WORD)];<br>&nbsp;for (int i = 0; i &lt; 36; i++)<br>&nbsp;{<br>&nbsp;&nbsp;ExpandBox[i].pos = SrcBox[wIndex[i]];<br>&nbsp;}<br>&nbsp;for (int i = 0; i &lt; 12; i++)<br>&nbsp;{<br>&nbsp;&nbsp;D3DVECTOR Tri[3];<br>&nbsp;&nbsp;Tri[0]&nbsp;= ExpandBox[i * 3 + 0].pos;<br>&nbsp;&nbsp;Tri[1]&nbsp;= ExpandBox[i * 3 + 1].pos;<br>&nbsp;&nbsp;Tri[2]&nbsp;= ExpandBox[i * 3 + 2].pos;<br>&nbsp;&nbsp;ExpandBox[i * 3 + 0].normal.x = 0.0f;<br>&nbsp;&nbsp;ExpandBox[i * 3 + 0].normal.y = 0.0f;<br>&nbsp;&nbsp;ExpandBox[i * 3 + 0].normal.z = 1.0f;<br>&nbsp;&nbsp;CalcNormal(Tri, &amp;(ExpandBox[i * 3 + 0].normal));<br>&nbsp;&nbsp;ExpandBox[i * 3 + 1].normal = ExpandBox[i * 3 + 0].normal;<br>&nbsp;&nbsp;ExpandBox[i * 3 + 2].normal = ExpandBox[i * 3 + 0].normal;<br>&nbsp;}<br>&nbsp;g_pd3dDevice-&gt;CreateVertexBuffer(sizeof(ExpandBox), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &amp;g_pVB, NULL);<br>&nbsp;VOID* pVertices;<br>&nbsp;g_pVB-&gt;Lock(0, sizeof(ExpandBox), (void**)&amp;pVertices, 0);<br>&nbsp;MoveMemory(pVertices, ExpandBox, sizeof(ExpandBox));<br>&nbsp;g_pVB-&gt;Unlock();</p>

        <p>&nbsp;return S_OK;<br>}</p>

        <p>void OnIdle()<br>{<br>&nbsp;static CTimer t;<br>&nbsp;static double dt = t.End();<br>&nbsp;double temp = t.End();<br>&nbsp;char szValue[256];<br>&nbsp;sprintf(szValue, "当前帧率:%f", 1/(temp-dt));<br>&nbsp;SetWindowTextA(g_hWnd, szValue);<br>&nbsp;dt = temp;<br>&nbsp;if (g_pd3dDevice != NULL)<br>&nbsp;{<br>&nbsp;&nbsp;g_pd3dDevice-&gt;Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,255,200), 1.0f, 0);<br>&nbsp;&nbsp;if (SUCCEEDED(g_pd3dDevice-&gt;BeginScene()))<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;BeforePaint();<br>&nbsp;&nbsp;&nbsp;if (FAILED(SetModalMatrix()))<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;return;<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;g_pd3dDevice-&gt;SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));<br>&nbsp;&nbsp;&nbsp;g_pd3dDevice-&gt;SetFVF(D3DFVF_CUSTOMVERTEX);<br>&nbsp;&nbsp;&nbsp;g_pd3dDevice-&gt;DrawPrimitive(D3DPT_TRIANGLELIST, 0, 12);<br>&nbsp;&nbsp;&nbsp;g_pd3dDevice-&gt;EndScene();<br>&nbsp;&nbsp;&nbsp;g_pd3dDevice-&gt;Present(NULL, NULL, NULL, NULL);<br>&nbsp;&nbsp;}<br>&nbsp;}<br>}</p>

        <p>HRESULT SetModalMatrix()<br>{<br>&nbsp;static float fRadius = 0.5f;<br>&nbsp;fRadius -= 0.003f;<br>&nbsp;if (fRadius &lt; 0)<br>&nbsp;{<br>&nbsp;&nbsp;fRadius = D3DX_PI * 2;<br>&nbsp;}<br>&nbsp;D3DXMATRIX matWorld;<br>&nbsp;D3DXMatrixRotationZ(&amp;matWorld, 0.0f);<br>&nbsp;if (FAILED(g_pd3dDevice-&gt;SetTransform(D3DTS_WORLD, &amp;matWorld)))<br>&nbsp;{<br>&nbsp;&nbsp;return E_FAIL;<br>&nbsp;}<br>&nbsp;D3DXMATRIX matView;<br>&nbsp;D3DXMatrixLookAtLH(&amp;matView, &amp;D3DXVECTOR3(cosf(fRadius)*40.0f, sinf(fRadius)*40.0f, 30.0),<br>&nbsp;&nbsp;&amp;D3DXVECTOR3(0.0f, 0.0f, 0.0f), &amp;D3DXVECTOR3(0.0f, 0.0f, 1.0f));<br>&nbsp;g_pd3dDevice-&gt;SetTransform(D3DTS_VIEW, &amp;matView);</p>

        <p>&nbsp;return S_OK;<br>}</p>

        <p>HRESULT SetProjMatrix(WORD wWidth, WORD wHeight)<br>{<br>&nbsp;D3DXMATRIX matProj;<br>&nbsp;D3DXMatrixPerspectiveFovLH(&amp;matProj, D3DX_PI/4, (float)wWidth/(float)wHeight,1.0f, 100.0f);<br>&nbsp;return g_pd3dDevice-&gt;SetTransform(D3DTS_PROJECTION, &amp;matProj);<br>}</p>

        </td>

    </tr>

</tbody>

   看看和OpenGL做出来的哪个效果更好一些呢?我想使用D3D来做这些事会很方便吧。

FVF(Flexible Vertex Format) 是Direct3d中的可变顶点格式,通过它可以定义三角形的顶点格式,然后通过创建顶点缓冲区并设置渲染源来显示基本的图形。

D3DFVF_XYZ和D3DFVF_XYZRHW的区别是:

1.D3DXYZ默认的坐标系统用户区中心是 (0,0) 而rhw的左上角是 (0,0)
2.D3DXYZ默认的非光照的,而RHW默认就是高洛夫的光照模式。

在 RHW下需要设置
#define FVF_XYZ (D3DFVF_XYZ | D3DFVF_DIFFUSE)
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING,FALSE)

而在 rhw下就不需要这样设置了。

先是一则消息:

2006年我国国内生产总值现价总量为159878亿元,比年快报核算数增多2.3万亿元,增加了10.7%,比目标8.0%高出2.0%。

然后是一个笑话:

两个千万富翁走在路上,看到路上有一堆屎。
甲说:“如果你吃一口,我就给你100万。”乙吃了一口,甲给了乙100万。
乙觉得不爽,便说:“如果你吃一口,我也给你100万。”甲也吃了一口,乙给了甲100万。
甲觉得不对,说:“咱俩这一来一去,等于白吃了两口屎。”
乙想了想,说:“不对,咱俩这一会儿功夫,为国家创造了200万的GDP增长。”

接下来是一个同事的牢骚:

看着GDP增加了那么多,怎么我们的生活就没什么改善?

最后是我的结论:

也许GDP的某些部分,是吃屎吃出来的。

一个很简单的主题,你的一个仇人爱上了你的女友,现在想要你退出,你是一个正常的人,你爱自己的女友。那个男人愿意出一点钱来补偿你。你多少钱可以把她卖掉?

无赖李马的答案:

1、问仇人:“如果你是RISE,你会出价多少钱?”

2、在仇人给出答案后,则自己出高一倍的价钱。譬如他说100万那么RISE就要200万。

3、在钱拿到手的时候,取出100万给他,说:“我现在买你退出,你丫可以滚了。”

有一家银行每天早上都在你的帐户里存入86,400
象有一家银行每天早上都在你的帐户里存入86,400, 
可是每天的帐户余额都不能结转到明天, 
一到结算时间, 
银行就会把你当日未用尽的款项全数删除。 
这种情况下你会怎幺做? 
当然, 
每天不留分文地全数提领是最佳选择。 
你可能不晓得, 
其实我们每个人都有这样的一个银行, 
她的名字是「时间(TIME)」。 

每天早上「时间银行」总会为你在帐户里自动存入86,400秒; 
一到晚上, 
她也会自动地把你当日虚掷掉的光阴全数注销, 
没有分秒可以结转到明天, 
你也不能提前预支片刻。 
如果你没能适当使用这些时间存款, 
损失掉的只有你自己会承担。 
没有回头重来,也不能预提明天, 
你必须根据你所拥有的这些时间存款而活在现在。 
你应该善加投资运用, 
以换取最大的健康、快乐与成功。 

时间总是不停地在运转,努力让每个今天都有最佳收获。 
想要体会「一年」有多少价值,你可以去问一个失败重修的学生。 
想要体会「一月」有多少价值,你可以去问一个不幸早产的母亲。 
想要体会「一周」有多少价值,你可以去问一个定期周刊的编辑。 
想要体会「一小时」有多少价值,你可以去问一对等待相聚的恋人。 
想要体会「一分钟」有多少价值,你可以去问一个错过火车的旅人。 
想要体会「一秒钟」有多少价值,你可以去问一个死里逃生的幸运儿。 
想要体会「一毫秒」有多少价值,你可以去问一个错失金牌的运动员。 
请珍视你所拥有的美好时光, 
特别是你可以和一些值得付出的人来分享这些时光。 
别忘了时间不等人。 

昨天以成为
历史, 
明天则遥不可知, 
而今天是一个礼物, 
所以英文把「现在」称为Present, 
请珍惜这份礼物 

<tbody>

    <tr>

        <td style="FONT-SIZE: 10pt" width="18%" height=42>

        <div style="FONT-SIZE: 10pt" align=left>    <strong>诸葛亮《前出师表》</strong>   <br></div>

        </td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left height=223>

        <div style="FONT-SIZE: 10pt" align=left>    臣亮言:先帝创业未半,而中道崩殂;今天下三分,益州疲敝,此诚危急存亡之秋也。然侍卫之臣,不懈于内;忠志之士,忘身于外者:盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气;不宜妄自菲薄,引喻失义,以塞忠谏之路也。宫中府中,俱为一体;陟罚臧否,不宜异同:若有作奸犯科,及为忠善者,宜付有司,论其刑赏,以昭陛下平明之治;不宜偏私,使内外异法也。侍中、侍郎郭攸之、费依、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下:愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰"能",是以众议举宠为督:愚以为营中之事,事无大小,悉以咨之,必能使行阵和穆,优劣得所也。亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也!侍中、尚书、长史、参军,此悉贞亮死节之臣也,愿陛下亲之、信之,则汉室之隆,可计日而待也。<br><br>    臣本布衣,躬耕南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,谘臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间:尔来二十有一年矣。先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧虑,恐付托不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,甲兵已足,当奖帅三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都:此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、依、允等之任也。愿陛下托臣以讨贼兴复之效,不效则治臣之罪,以告先帝之灵;若无兴复之言,则责攸之、依、允等之咎,以彰其慢。陛下亦宜自谋,以谘诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激!今当远离,临表涕泣,不知所云。 <br></div>

        </td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" width="18%" height=34>

        <div style="FONT-SIZE: 10pt" align=left>    <strong>诸葛亮《后出师表》</strong></div>

        </td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left height=79>

        <div style="FONT-SIZE: 10pt" align=left>    先帝虑汉、贼不两立,王业不偏安,故托臣以讨贼也。以先帝之明,量臣之才,故知臣伐贼,才弱敌强也。然不伐贼,王业亦亡。惟坐而待亡,孰与伐之?是故托臣而弗疑也。臣受命之日,寝不安席,食不甘味;思惟北征,宜先入南:故五月渡泸,深入不毛,并日而食。--臣非不自惜也:顾王业不可偏安于蜀都,故冒危难以奉先帝之遗意。而议者谓为非计。今贼适疲于西,又务于东,兵法"乘劳":此进趋之时也。谨陈其事如左:<br><br>    高帝明并日月,谋臣渊深,然涉险被创,危然后安;今陛下未及高帝,谋臣不如良、平,而欲以长策取胜,坐定天下:此臣之未解一也。刘繇、王朗,各据州郡,论安言计,动引圣人,群疑满腹,众难塞胸;今岁不战,明年不征,使孙策坐大,遂并江东:此臣之未解二也。曹操智计,殊绝于人,其用兵也,仿怫孙、吴,然困于南阳,险于乌巢,危于祁连,逼于黎阳,几败北山,殆死潼关,然后伪定一时耳;况臣才弱,而欲以不危而定之:此臣之未解三也。曹操五攻昌霸不下,四越巢湖不成,任用李服而李服图之,委任夏侯而夏侯败亡,先帝每称操为能,犹有此失;况臣弩下,何能必胜:此臣之未解四也。自臣到汉中,中间期年耳,然丧赵云、阳群、马玉、阎芝、丁立、白寿、刘合、邓铜等,及驱长屯将七十余人,突将无前,丛叟、青羌,散骑武骑一千余人,此皆数十年之内,所纠合四方之精锐,非一州之所有;若复数年,则损三分之二也。--当何以图敌:此臣之未解五也。今民穷兵疲,而事不可息;事不可息,则住与行,劳费正等;而不及今图之,欲以一州之地,与贼持久:此臣之未解六也。<br><br>    夫难平者,事也。昔先帝败军于楚,当此时,曹操拊手,谓天下已定。--然后先帝东连吴、越,西取巴、蜀,举兵北征,夏侯授首:此操之失计,而汉事将成也。--然后吴更违盟,关羽毁败,秭归蹉跌,曹丕称帝:凡事如是,难可逆见。臣鞠躬尽瘁,死而后已;至于成败利钝,非臣之明所能逆睹也。<br>   </div>

        </td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left height=163>    <strong>悼周瑜</strong><br>    呜呼公瑾,不幸夭亡!修短故天,人岂不伤?我心实痛,酹酒一觞;君其有灵,享我蒸尝!吊君幼学,以交伯符;仗义疏财,让舍以民。吊君弱冠,万里鹏抟;定建霸业,割据江南。吊君壮力,远镇巴丘;景升怀虑,讨逆无忧。吊君丰度,佳配小乔;汉臣之婿,不愧当朝,吊君气概,谏阻纳质;始不垂翅,终能奋翼。吊君鄱阳,蒋干来说;挥洒自如,雅量高志。吊君弘才,文武筹略;火攻破敌,挽强为弱。想君当年,雄姿英发;哭君早逝,俯地流血。忠义之心,英灵之气;命终三纪,名垂百世,哀君情切,愁肠千结;惟我肝胆,悲无断绝。昊天昏暗,三军怆然;主为哀泣;友为泪涟。亮也不才,丐计求谋;助吴拒曹,辅汉安刘;掎角之援,首尾相俦,若存若亡,何虑何忧?呜呼公瑾!生死永别!朴守其贞,冥冥灭灭,魂如有灵,以鉴我心:从此天下,更无知音!呜呼痛哉!伏惟尚飨。<br><br></td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left>    <strong>诫子书</strong><br>    夫君子之行,静以修身,俭以养德。非淡泊无以明志,非宁静无以致远。夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落,多不接世,悲守穷庐,将复何及!<br>    译文:有道德修养的人,是这样进行修养锻炼的,他们以静思反省来使自己尽善尽美,以俭朴节约财物来培养自己高尚的品德。不清心寡欲就不能使自己的志向明确坚定,不安定清静就不能实现远大理想而长期刻苦学习。要学得真知必须使身心在宁静中研究探讨,人们的才能是从不断的学习中积累起来的;如果不下苦工学习就 不能增长与发扬自己的才干;如果没有坚定不移的意志就不能使学业成功。纵欲放荡、消极怠慢就不能勉励心志使精神振作;冒险草率、急燥不安就不能陶治性情使节操高尚。如果年华与岁月虚度,志愿时日消磨,最终就会像枯枝落叶般一天天衰老下去。这样的人不会为社会所用而有益于社会,只有悲伤地困守在自己的穷家破舍里,到那时再悔也来不及了。(兰溪诸葛村)[注:应网友 武春森要求] <br><br></td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left>    <strong>隆中对</strong><br><br><!--

            亮躬耕陇亩,好为《梁父吟》。身高八尺,每自比于管仲、乐毅,时人莫之许也。惟博陵崔州平、颍川徐庶元直与亮友善,谓为信然。<br>

            时先主屯新野。徐庶见先主,先主器之,谓先主曰:&#8220;诸葛孔明者,卧龙也,将军岂愿见之乎?&#8221;先主曰:&#8220;君与俱来。&#8221;庶曰:&#8220;此人可就见,不可屈致也。将军宜枉驾顾之。&#8221; <br>

            由是先主遂诣亮,凡三往,乃见。因屏人曰:&#8220;汉室倾颓,奸臣窃命,主上蒙尘。孤不度德量力,欲信大义于天大,而智太短浅,遂用猖獗,至于今日。然志犹未已,君谓计将安出?&#8221;<br>

        -->    亮答曰:&#8220;自董卓已来,豪杰并起,跨州连郡者不可胜数。曹操比于袁绍,则名微而众寡,然操遂能克绍,以弱为强者,非惟天时,抑亦人谋也。今操已拥百万之众,挟天子而令诸侯,此诚不可与争锋。孙权据有江东,已历三世,国险而民附,贤能为之用,此可以为援而不可图也。荆州北据汉、沔,利尽南海,东连吴会,西通巴、蜀,此用武之国,而其主不能守,此殆天所以资将军,将军岂有意乎?益州险塞,沃野千里,天府之土,高祖因之以成帝业。刘璋暗弱,张鲁在北,民殷国富而不知存恤,智能之士思得明君。将军既帝室之胄,信义著于四海,总揽英雄,思贤如渴,若跨有荆、益,保其岩阻,西和诸戎,南抚夷越,外结好孙权,内修政理;天下有变,则命一上将将荆州之军以向宛、洛,将军身率益州之众出于秦川,百姓孰敢不箪食壶浆以迎将军者乎?诚如是,则霸业可成,汉室可兴矣。&#8221;<br><br><!--

            先主曰:&#8220;善!&#8221;于是与亮情好日密。<br>

            关羽、张飞等不悦,先主解之曰:&#8220;孤之有孔明,犹鱼之有水也。愿诸君勿复言。&#8221;羽、飞乃止。<br><br>

            译文:诸葛亮亲自耕种田地,喜爱吟唱《梁父吟》。他身高八尺,常常把自己与管仲、乐毅相比,当时的人没有谁承认这一点。只有博陵崔州平,颖川的徐庶徐元直跟他交情很好,说是确实这样。 <br><br>

            当时刘备驻军在新野。徐庶拜见刘备,刘备很器重他,徐庶对刘备说:&#8220;诸葛孔明,是卧龙啊,将军可愿意见他吗?&#8221;刘备说:&#8220;您和他一起来吧。&#8221;徐庶说:&#8220;这个人只能到他那里去拜访,不能委屈他,召他上门来,您应当屈身去拜访他。&#8221; <br>

            于是刘备就去拜访诸葛亮,共去了三次,才见到。刘备于是叫旁边的人避开,说:&#8220;汉朝的天下崩溃,奸臣窃取了政权,皇上逃难出奔。我没有估量自己的德行,衡量自己的力量,想要在天下伸张大义,但是自己的智谋浅短、办法很少,终于因此失败,造成今天这个局面。但是我的志向还没有罢休,您说该采取怎样的计策呢?&#8221;<br>

        -->    译文:诸葛亮回答道:&#8220;自董卓篡权以来,各地豪杰纷纷起兵,占据几个州郡的数不胜数。曹操与袁绍相比,名声小,兵力少,但是曹操能够战胜袁绍,从弱小变为强大,不仅是时机好,而且也是人的谋划得当。现在曹操已拥有百万大军,挟制皇帝来号令诸侯,这的确不能与他较量。孙权占据江东,已经历了三代,地势险要,民众归附,有才能的人被他重用,孙权这方面可以以他为外援,而不可谋取他。荆州的北面控制汉、沔二水,一直到南海的物资都能得到,东面连接吴郡和会稽郡,西边连通巴、蜀二郡,这是兵家必争的地方,但是他的主人刘表不能守住,这地方大概是老天用来资助将军的,将军难道没有占领的意思吗?益州有险要的关塞,有广阔肥沃的土地,是自然条件优越,物产丰饶,形势险固的地方,汉高祖凭着这个地方而成就帝王业绩的。益州牧刘玲昏庸懦弱,张鲁在北面占据汉中,人民兴旺富裕、国家强盛,但他不知道爱惜人民。有智谋才能的人都想得到贤明的君主。将军您既然是汉朝皇帝的后代,威信和义气闻名于天下,广泛地罗致英雄,想得到贤能的人如同口渴一般,如果占据了荆州、益州,凭借两州险要的地势,西面和各族和好,南面安抚各族,对外跟孙权结成联盟,对内改善国家政治;天下形势如果发生了变化,就派一名上等的将军率领荆州的军队向南阳、洛阳进军,将军您亲自率领益州的军队出击秦川,老百姓谁敢不用竹篮盛着饭食,用壶装着酒来欢迎您呢?如果真的做到这样,那么汉朝的政权就可以复兴了。&#8221;<br><!--

            刘备说:&#8220;好!&#8221;从此同诸葛亮的情谊一天天地深厚了。<br>

            关羽、张飞等人不高兴了,刘备劝解他们说:&#8220;我有了孔明,就像鱼得到水一样。希望你们不要再说什么了。&#8221;关羽、张飞才平静下来。<br><br>

        --></td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left>    <strong>诫外甥书</strong><br><br>    夫志当存高远,慕先贤,绝情欲,弃疑滞。使庶几之志揭然有所存,恻然有所感。忍屈伸,去细碎,广咨问,除嫌吝,虽有淹留,何损于美趣,何患于不济。若志不强毅,意气不慷慨,徒碌碌滞于俗,默默束于情,永窜伏不庸,不免于下流。<br></td>

    </tr>

    <tr>

        <td style="FONT-SIZE: 10pt" align=left>    <strong>诸葛亮预言《马前课》</strong><br><br>    相传为诸葛亮于军中闲暇时写了一个《马前课》,是预测天下大事的书。从字面上讲,就是在出兵之前,在马前面占卜一课,即起卦的意思。诸葛亮的《马前课》非常好破译,每个朝代就一卦,这样往下排就是了。&nbsp;而中国历史上出现的其他很多预言书比较难破译,因为它讲历史大事,有的时候一个朝代可能有很多大事,有的朝代大事要少一些,不规律。&nbsp; <br><br>《马前课》&nbsp; <br>第一课 ○●●●●○ 中下&nbsp; <br>无力回天 鞠躬尽瘁&nbsp; <br>阴居阳拂 八千女鬼&nbsp; <br>证曰:阳阴阴阴阴阳在卦为颐&nbsp; <br>解曰:诸葛鞠躬尽瘁而死,后蜀汉后主降于魏&nbsp;&nbsp; <br>第二课 ○●○○●○ 中下&nbsp; <br>火上有火 光烛中土&nbsp; <br>称名不正 江东有虎&nbsp; <br>证曰:阳阴阳阳阴阳在卦为离&nbsp; <br>解曰:司马炎篡魏元帝都建康属江东&nbsp; <br> &nbsp; <br>第三课 ○●●●●● 下下&nbsp; <br>扰扰中原 山河无主&nbsp; <br>二三其位 手终马始&nbsp; <br>证曰:阳阴阴阴阴阴在卦为剥&nbsp; <br>解曰:五代始于司马终于杨氏&nbsp;&nbsp; <br><br>第四课 ●●○●○● 中上&nbsp; <br>十八男儿 起于太原&nbsp; <br>动则得解 日月丽天&nbsp; <br>证曰:阴阴阳阴阳阴在卦为解&nbsp; <br>解曰:李唐起于太原武□称周&nbsp; <br><br>第五课 ○○○●●● 下中&nbsp; <br>五十年中 其数有八&nbsp; <br>小人道长 生灵荼毒&nbsp; <br>证曰:阳阳阳阴阴阴在卦为否&nbsp; <br>解曰:五代八姓共五十三年&nbsp; <br><br>第六课 ●○○●○○ 上中&nbsp; <br>惟天生水 顺天应人&nbsp; <br>刚中柔外 土乃生金&nbsp; <br>证曰:阴阳阳阴阳阳在卦为兑&nbsp; <br>解曰:赵宋黄袍加身而立敌为金&nbsp; <br><br>第七课 ●○●○○● 中中&nbsp; <br>一元复始 以刚处中&nbsp; <br>五五相传 尔西我东&nbsp; <br>证曰:阴阳阴阳阳阴在卦为井&nbsp; <br>解曰:元代共十主后各汗国分裂&nbsp;&nbsp; <br><br>第八课 ○○●●●○ 上上&nbsp; <br>日月丽天 其色若赤&nbsp; <br>绵绵延延 凡十六叶&nbsp; <br>证曰:阳阳阴阴阴阳在卦为益&nbsp; <br>解曰:朱即赤日月是明共十六主&nbsp; <br><br>第九课 ○●○●●● 中上&nbsp; <br>水月有主 古月为君&nbsp; <br>十传绝统 相敬若宾&nbsp; <br>证曰:阳阴阳阴阴阴在卦为晋&nbsp; <br>解曰:水月有主是清也,古月是胡也,满清十皇朝最后亡于宣统&nbsp;&nbsp; <br><br>第十课 ●○●○●● 中下&nbsp; <br>豕后牛前 千人一口&nbsp; <br>五二倒置 朋来无咎&nbsp; <br>证曰:阴阳阴阳阴阴在卦为蹇&nbsp; <br>解曰:豕后牛前辛亥也千人一口为和,五二倒置是民也朋者外邦也&nbsp; <br><br>第十一课 ○●○○●○ 中下&nbsp; <br>四门乍辟 突如其来&nbsp; <br>晨鸡一声 其道大衰&nbsp; <br>证曰:阳阴阳阳阴阳在卦为离&nbsp; <br>解曰:当朝之象也四门乍辟谓为门户开放,酉年当期时无人再相信其道理故&nbsp;&nbsp; <br><br>第十二课 ●○○○○● 上中&nbsp; <br>拯患救难 是唯圣人&nbsp; <br>阳复而治 晦极生明&nbsp; <br>证曰:阴阳阳阳阳阴在卦为大过&nbsp; <br>解曰:当来之象也灾难当头之极,其时圣人出现救苦救难故 &nbsp; <br><br>第十三课 ○●●○○○ 上中&nbsp; <br>贤不遗野 天下一家&nbsp; <br>无名无德 光耀中华&nbsp; <br>证曰:阳阴阴阳阳阳在卦为大畜&nbsp; <br>解曰:世界大同之象&nbsp;&nbsp; <br><br>第十四课 ○●○●○● 中下&nbsp; <br>占得此课 易数乃终&nbsp; <br>前古后今 其道无穷&nbsp; <br>证曰:阳阴阳阴阳阴在卦为未济 <br></td>

    </tr>

</tbody>

初步涉及3D,被高手指出对D3D的硬件流水线不熟悉.马上找资料,并做总结.我可不能落后哎.

   先发张流水线的图:
  
 
一.system memory(系统内存)
  3D数据被CPU创建后,在进入流水线之前,会储存在系统内存中.之后,这些待决数据将通过数据总线传入显卡的AGP存储器或显存中.
二.1.vertex data(顶点数据)
   点是最基本的几何图元,一个三角形由三个顶点组成,一个矩形有四个。
   D3D中定义的顶点不只包含位置信息,还可以加入好多其他的要素.举个例子
   typedef struct vertex
 {
   FLOAT x,y,z;
   FLOAT u,v;
   DWORD color;
 }COSTUMVERTEX;
 #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEXT1)
   这段代码就表示所定义的顶点包含位置,漫反射,纹理坐标的信息.这种形式称为FVF(Flexible Vertex Function)可变顶点格式.这些顶点数据会继续送入流水线下一级进行平移,旋转,光照,着色处理.
   2.Surface Data(页面数据)
  这是个啥东西以前还真没搞明白过,一直理解为2D中的surface,在D3D9SDK中,ddraw被去掉了,据说是运用3D流水线进行2D编程将比传统的2D流水线更高效><,今天算是碰到它了,借此机会一蹴而就!
   Andre的书上说,这种surface被称为high-order-surface,不是在CPU里创建的,使用的也不是自己定义的那个顶点格式,而是由一些数学函数直接在显卡里创建的,作用能够使网格模型更加平滑,还提到两个技术术语,一个N-Patches(貌似以前在哪见过...)和
TRUFORM meshes on ATI hardware(从没听说过,从字面上理解应该是ATI显卡特有的技术).完了,貌似有点深奥,时间紧迫,还是先放这儿了.....
三.1.Transform and Lighting(移动和光照)
   这部分作用就是让顶点组成的物体能够在虚拟世界中产生位移产生旋转以及进行光照计算,不然,整个世界不都黑忽忽的,不生动啊,不真实啊,现在人不都讲究个真实和诚信嘛!
   移动是用变换矩阵实现的,让组成物体的每个顶点乘上那个变换矩阵就OKAY了。光照稍微复杂一点,按光源分类有点光源(point light),平行光(directional),探照灯(spot light);按照性质有自发光(emisive),环境光(ambient),镜面反射(specular),漫反射(diffuse).在虚拟的环境中,这些光考虑的越周全,表现出的效果也就越真实,但渲染速度也会变慢的。
 2.Vertex Shader(顶点着色)
   分为flat着色和Gouraud着色,各有利弊吧.说的细点,flat着色是根据组成物体每个面的法线进行着色,做出的效果不平滑,如果对一个球体运用flat的话,效果会是disco舞厅天花板上的那个球装灯的样子.Gouraud着色根据各顶点的法线来计算插植进行着色,得到的效果要平滑的多。
                       
                              flat 
                  
                       Gouraud+specular
    还可以不用微软的API,自己用HLSL做算法,那个就太难了....(HIGH LEVEL SHADING LANGUAGE)   
四.clipping(裁减)
   这部很关键哦,能够有效减少GPU的运算量.在虚拟世界中,我们得到的是角都是有限的,就像在现实世界,人不可能看到身后的东西,除非后面长眼了,还有,太远的物体看不到,比如,美国人看不到本拉登在做什么,所以他们急啊,他们想找到那老头子....,太近了更看不到了,看到了可麻烦了,如果每个人都能看到细菌,那还吃饭吗?所以,在光栅化之前,要把理论上看不到的东西裁减掉,别让GPU运算,那没用啊。
五.1.MutiTexture(多重纹理)
    人靠衣服妆吧,总不能光着身子上大街,那也不好看啊。在虚拟世界里也一样,单单一个网格模型只不过是个骨架子,看上面那两个球就知道,很生硬吧.如果能加上纹理就会有生动的效果了哦。现在的显卡都能支持好多重纹理了,具体多少我也不清楚,十几层吧,你可以做个人物模型,先贴上皮肤,再贴上内衣内裤,再贴上秋衣秋库,再贴上毛衣毛库.....
   2.Pixel Shader(象素着色)
    就是产生象素级别的颜色控制,做的好的话能够使模型展现一流的效果,更加平滑,更加真实.比如柔顺的绣发,光滑富有质感的皮肤.....,API里目前我还不知道有啥,HLSL肯定能做.
 
六 FOG(雾化效果)
   想要朦胧感?雾化一下吧,参考PS上的寂静岭1......那个终年被浓雾笼罩的小镇.
 
七.stencil/depth/alpha test(色深,深度,啊而法测试)
   在将"渲"好的世界投影在屏幕之前还是做下物体遮挡测试,现实中肯定不会有人拥有透视眼吧,能看到墙后面的物体,那可绝了!那穿衣服也没啥用了....
   stencil和depth我还没分太清楚,好象都是进行物体的象素对比.depth可以用z-buffer或w-buffer运算,stencil还不清楚,先放这儿了.alpha是标识物体透明度的,比如,水总是透明的吧,丝绸也有透明的吧.玻璃总透明吧.....
八.frame buffer
   一切的一切都准备好了,送入帧缓冲,准备把美丽的3D世界投影到屏幕上吧^^

其实,微软在Vista激活上的态度十分宽松,如果不是此前某些组织做得过火——擅自架设Vista激活服务器,微软甚至愿意“悄悄”提供给用户免激活长期使用Vista的方式。

事实上,只需将Vista注册表中一个键值从0改为1,就可以无限次延长Vista激活最后期限——微软甚至在自己的Technet网站上提供了相关说明文档。

不过,某些 不良PC销售商有可能利用这一“漏洞”欺骗消费者,声称提供正版Vista激活——而用户至少要到几个月甚至一年后才会发现。

当然,相对目前网络上流行的Vista破解方式,这种“合法”途径需要进入注册表修改,而且有可能需要多次进行激活,并算不上方便,不适合普通用户使用,对注册表较为了解的用户倒是可以尝试一下。

The following describes the Registry key that’s involved.

Step 1. While running a copy of Windows Vista that hasn’t yet been activated, click the Start button, type regedit into the Search box, then press Enter to launch the Registry Editor.

Step 2. Explore down to the following Registry key:

HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ SL

Step 3. Right-click the Registry key named SkipRearm and click Edit. The default is a Dword (a double word or 4 bytes) with a hex value of 00000000. Change this value to any positive integer, such as 00000001, save the change, and close the Registry Editor.

Step 4. Start a command prompt with administrative rights. The fastest way to do this is to click the Start button, enter cmd in the Search box, then press Ctrl+Shift+Enter. If you’re asked for a network username and password, provide the ones that log you into your domain. You may be asked to approve a User Account Control prompt and to provide an administrator password.

Step 5. Type one of the following two commands and press Enter:

slmgr -rearm
or
rundll32 slc.dll,SLReArmWindows

Either command uses Vista’s built-in Software Licensing Manager (SLMGR) to push the activation deadline out to 30 days after the command is run. Changing SkipRearm from 0 to 1 allows SLMGR to do this an indefinite number of times. Running either command initializes the value of SkipRearm back to 0.

Step 6. Reboot the PC to make the postponement take effect. (After you log in, if you like, you can open a command prompt and run the command slmgr -xpr to see Vista’s new expiration date and time.

Step 7. To extend the activation deadline of Vista indefinitely, repeat steps 1 through 6 as necessary

VS1.1.7内部测试版:

修改了vs1.1.6的一些已知的bug,对于dota积分规则的改进现在经过测试发现仍有问题,但是vs1.1.7能够更好的支持vista

VS1.1.6内部测试版(测试群共享目录也可下载):

1 For Vista !
  全面支持vista的vs客户端,欢迎各位vista爱好者下载测试,特别的由于考虑兼容vista,这个版本做了不小的改动,因此请广大热心玩家都多多下载,在xp以及其他windows操作系统上面做测试,以避免新开发带来的新的bug
  
2 For Dota  !
  为了Dota用户特别定制了RPG新的计分规则,规则允许对抗性RPG游戏中,对手一方真人控制的玩家全部退出(包括掉线)以后,本方立即获胜(无需再继续拆基地),计算得分的公式保持不变。确认对方玩家全部退出后,本方即可随之退出比赛,系统不会再记录这个行为为负。请参与测试的dota爱好者把这个客户端分发给你的好友,争取保证测试时本方所有队员都使用这个测试版本,否则可能会由于客户端不一致影响测试效果。
  
3 我的收藏房间
  右键点击房间,菜单里面可以选择添加为自己的收藏房间,收藏房间会被列到你客户端显示的第一个。这个功能主要是给网吧网管用的,呵呵,大家要是金额的好自己也可以用用吧
  
这个版本还修正了以往出现的一些bug,特别是CS1.6积分的某些bug,欢迎VS所有爱好者下载测试,支持VS,支持电子竞技!

加群的各位请注意,群目前人比较多,群主要的目的是内部测试,加入群的人应该具有热情和责任感,应该有责任反馈测试情况,包括测试结果正常或者不正常的情况
如果仅仅只是等着要用最新功能,不愿意履行测试义务的用户可以等待内部测试阶段结束,版本正式开放,你也一样可以得到最新的功能


http://www.cnblogs.com/Files/flying_bat/VS1.1.7.zip

其实有更多的方法就是合并SP1包
RISE
VS2005 SP1 出来了,号称是M$有史以来最bt的补丁,看见有人装了5个多小时还在收集信息(其实就我自己)……

其实原因总结起来就两个:
1. 数字签名认证的问题
2. C盘空间的问题

原因1:Windows Installer设计的一个特性是可以验证文件的数字签名。但是这次的补丁太
大了,要验证签名的话基本上把内存都吃光了(要1GB左右的内存)
原因2:Windows Installer设计的一个特性是会在C盘生成一个$PatchCache$文
件,把补的文件再存一遍,对于这次的补丁嘛,这个大约会消耗掉1.3GB的空间

总之就是内存不够、C盘空间不够的系统,如果直接安装的话肯定死得很难看 *_*

解决方案:
针对原因1:按照 http://support.microsoft.com/kb/824642/zh-cn 的指示把管理员的数字签名验证关掉

针对原因2:不要直接打补丁,用下面的批处理:
reg export HKLM\Software\Policies\Microsoft\Windows\Installer installer.reg
reg add HKLM\Software\Policies\Microsoft\Windows\Installer /v MaxPatchCacheSize /t REG_DWORD /d 0 /f
@echo 关闭数字签名验证
reg add HKLM\SOFTWARE\Policies\Microsoft\windows\safer\codeidentifiers /v authenticodeenabled /t REG_DWORD /d 1 /f
net stop msiserver
start /wait VS80sp1-KB926604-X86-CHS.exe
reg delete HKLM\Software\Policies\Microsoft\Windows\Installer /v MaxPatchCacheSize /f
reg import installer.reg
reg add HKLM\SOFTWARE\Policies\Microsoft\windows\safer\codeidentifiers /v authenticodeenabled /t REG_DWORD /d 1 /f
net stop msiserver
del /q installer.reg 2>nul

可以为你的C盘省下1.3GB空间

如果你用vista,在vista下很简单就可以搞定的:
(1)把installer文件夹cut到d盘
(2) mklink /d /j C:\Windows\Installer d:\Installer

也就是把installer文件夹换一个位置
--------------------------------------------------------------------------------
最后,不着急的话等几天会有官方集成版本了,那时估计打补丁要方便多了。

如果以上步骤不能解决该问题,请按照下列步骤操作:
1. 单击“开始”,单击“运行”,键入 secpol.msc,然后单击“确定”。
2. 双击“本地安全策略”。
3. 单击“软件限制策略”。

注意:如果未列出软件限制,请右击“软件限制策略”,然后单击“新建策略”。
4. 在“对象类型”下,双击“强制”。
5. 单击“除本地管理员以外的所有用户”,然后单击“确定”。
6. 重新启动计算机。

HUM的3本是所有种族中最强的
可是脆弱的HUM基地和前期兵种往往无法使HUM安心升级3本
现在HUM的革命性战术出现了
速出双英雄对对手进行超强势压制同时安升3本使HUM真正成为了无敌的种族!
PS:中期别忘了出法师部队过渡双英雄压制最多持续2分钟
小地图请慎用
开局顺序见REP
对抗ORC:
由于女巫出现的时候ORC2本还没升完可以在给英雄补给完后立即攻击ORC并在打退ORC主力后优先砸掉建造中的2本建筑,对于放在基地内的建筑可以用召唤生物去打,切忌孤军深入,地洞会让你损失掉所有的女巫.在拥有4女巫+4牧师的时候回家补给并买3个塔升专家女巫,如果ORC用FS首发或是出法师部队就把专家牧师也升了并补到6个牧师.
可以先MF掉商店并买群补,如果ORC登记低可以直接攻击
最后注意保持阵行被狼7网住车子齐射就直接隐行
数量上的巨大优势会让ORC在30秒内打出GG
对抗UD:
初期注意点杀UD的狗而不是农民
如果是ZZ流那DK的出现时间会比我们的2英雄还要晚同样是点杀狗
不要妄图点杀ZZ,杀农民也是毫无意义的我们要做的是打断UD的木头供应使UD不能出大量ZZ来对抗法师
如果UD倾巢出动那再好不过H&R搞定狗并用召唤生物去UD家杀贫血的狗
接下来有个分岔点:要注意UD是准备升3本出DES还是2 本暴ZZ来对抗我们的压制
对抗准备用DES的UD:
2个女巫出现后补2个牧师继续造2个女巫开始升3本
始终要以杀狗为第一目标绝不能在我们的3本完成前让UD出现车子并升3本
74出现后立即冲往UD家优先打掉车子并砸掉商店
如果看到屠宰场在转砸掉这时候的UD是没有能力和我们一战的
切忌不要妄图用飞机来对抗DES
虽然我们的科技比UD要快但单矿的HUM是没有能力同时产74和飞机的
优先造飞机会让我们失去对地面的控制权,而如果UD看到飞机转型ZZ+车子的阵行我们很可能失去对场面的控制
对抗2本暴ZZ+车子的UD:
我们需要在2女巫2牧师后出2个破法来对抗UD并且在拥有4女巫4牧师2破法并且开始升及专家女巫的时候升3本
在和UD的拉锯战中我们不能轻易损失单位在对方有NC的情况下我们要让破法优先废掉LICH并把接近100HP的法师隐行
拖到74的出现UD的末日就到了
注意带保存并且MF商店买无敌防御和群补
大量的ZZ使UD没办法在及时出DES来对抗74
我们要防止UD强杀英雄逼我们回程所以一旦英雄被齐射就隐行
用法师点车子74砍ZZ
UD只有往后退的义务没有攻击我们的权利
不要犹豫直接杀进UD的老家!
对抗NE:
我们需要猜测对手的开局
对抗DR+AC换兵流:
我们可以在NE换兵之前把他们的兵全干掉
我们需要DR和NAGA的组合用兵黑双箭不断把对手的AC变成我们的骷髅如果WISP上来爆优先把骷髅分散,操作允许的话把英雄也拉开我们双英雄出现的时候NE的AC不会超过3个,如果对手用BR练级则更少
由于对手缺乏对我们英雄有效的杀伤我们持续地进行压制
在有雇佣兵营的地图上我们会拥有更大的优势
我们可以用骷髅引开CREEP然后买绿P黄P石头人和胖子等
圣地可以缓放先升3本准备出74牧师加小炮的组合
前线就用双英雄和雇佣兵持续压制用单个骷髅砍WISP延缓NE科技
在拥有2个大师牧师和1个74+1个小炮的时候NE就可以GG了
保存和无敌同样不可少
对抗DH+AC传统熊鹿流(包括中期暴DY过度的打法):
由于DH的存在我们需要买HP较高切速度快的英雄
PL的分裂是不错的选择
为了加强攻击输出BM招熊也是很不错的
当然希望各位尝试各种英雄的组合
我们要做的是保持持续的压制所以雇佣兵也是必要的
对于没有雇佣兵营的地图(如TR)则必要靠召唤英雄的力量,注意WISP的自爆
看初期的压制效果决定下一步战术
初期压制效果理想,即杀了数个AC砍掉几个WISP,则缓放圣地先升3本
如果不理想则放下2个圣地造破法和女巫,补给方面先靠商店顶一下
3法的阵行在NE出鹿之前还是有优势的
砸掉建造中的知识古树和商店后开始升3本
同时要利用游击战骚扰NE的科技和木头尽量延缓NE鹿的出现时间,因为鹿的出现会彻底宣告压制时间的结束
NE很有可能2本买NAGA来对抗我们的压制
要利用NAGA低HP的特点配合缓速的挡路干掉他
我们要做的不是用74去打AC而是准备用74打熊所以不要欺人太甚到杀红了眼
只要没有致命的失误我们的74+牧师+小炮的组合是可以草割熊鹿的
中期留下的女巫和破法也有不小的作用所以中期竟可能不要死兵
对抗DH+HT的开局:
对手的HT会在我们2英雄之后出现
在这之前我们双英雄决不可能打不过单DH
英雄自由选择吧
注意把各英雄组合的可行性报告发上来大家共享
象干ORC一样用3法把NE直接干掉
同族大战不便多说

很久以前听一个故事:从前有个小伙子,少时有大志,长大后却无好营生,开了个豆腐作坊,每天磨豆腐累得腰酸背疼。每到夜深人静,小伙子辗转反侧,总想找条更好的“事业之路”,可是想过千百条、尝试过几十条路,都走不通。夜不成寝,白天干活更累,小伙子不由慨叹:“晚上想过千般路,白天还得磨豆腐”。

不久以前看过一篇文章:《CMM欺骗了中国的软件业》,内容是对CMM热的反思。CMM当然不会主动欺骗人,实际上是我们的软件业自己欺骗自己。我们从来不缺少“某某模式”,“面向某某”,“某某认证”等等听起来美妙无比的东西,问题是实际的研发过程中能做得到码?现实是残酷的,美妙的概念漫天飞舞,开发过程仍然是作坊式的,正是:“晚上想过千般路,白天还得磨豆腐”。

中国的故事通常都有圆满的结局,现在接着说“磨豆腐”的故事。过了很长时间,小伙子终于面对现实,不再沉迷于不切实际的空想,用心磨好豆腐,闲时琢磨些个窍门,慢慢地,他的豆腐质量越来越好,每天产量也越来越多,作坊越开越大,成了远近闻名的“豆腐老板”,后来,他做起了别的生意,发现年轻时的空想,其实很多都是可行的,因为现在“能力”和“财力”都不同了。

再说软件开发。我们不反对任何理论、技术、方法、模式等等,但第一,您的企业或团队做得到吗?不要做“如果开发时间延长一倍,就可以做到”之类毫无意义的假设。第二,做了真的有效益吗?效益是指扣除成本之后的收益。如果不具备这两点,那么还是不要整天想着“千般路”,首先想想如何好好的“磨豆腐”吧。

对于所有软件开发来说,代码编写都是无可逃避的“磨豆腐”。改进代码编写工作,高率效低成本地开发出高质量的代码,对于软件产品能否在激烈的竞争中胜出,对于软件企业的生存和发展,都具有重要的现实意义。

微软以及其它许多公司在面试中都有一个“秘密测试武器”,这个秘密武器通常被称为“挑战”。我的一个朋友没有通过微软的面试。面试后,我与他共进晚餐。他抱怨说:“我恨死那个主考官了,他简直是个蠢货。他连皮亚诺公理这样的基础知识都不懂!”我的这位朋友对此感到愤怒,因为他觉得之所以面试搞砸,是这个主考官对所谈论话题的无知造成的。

结果实情是什么呢?他所申请的职位是项目经理,这是一个负责设计软件而不是编程工作的职位,因此这个职位需要做很多说服性的工作。项目经理要和一群有着很强逻辑思维能力、但缺乏社交技巧的程序员打交道,这需要一种特别的才能。要想做一名项目经理,你需要具备的独特能力是:说服人们接受那些你认为正确的事实,而这基本上就是你的日常工作。你必须是充满耐心和友好的,这是项目经理职位对一个人的素质的基本要求。

“挑战”的最早版本出现在口头进行的斯坦福-比奈智商测试中。测试的人可能会给出下列题目:我们都知道,水能够把水中的鱼托起来,那么请回答这个问题:如果我们有一个桶只有半桶水,把桶放到天平上,水和桶刚好重45公斤。然后把一条5公斤的鱼放进桶里,现在总重量为多少?

大多数成年人都会说45加5是50公斤。主持测试的人这时候可能会问:“这怎么会是正确的呢?你知道水是会把鱼托起来的。”如果被测试的人不断地改变答案,然后说“我觉得答案应该是50公斤,但并不能十分确定。”那么得分为零。只有在被测试者利用逻辑为自己的正确答案进行辩护,并连续挫败两次“挑战”,答案才会被认为是正确的。

这些问题似乎并不针对智力做出测试,但是毫无疑问,应用这种“诡辩”测试的公司很重视这种测试的结果。一般情形如下:在整个面试过程中,考官会引导应聘者说出一些完全肯定、毫无争议的正确答案。然后说“等一下,等一下”,再故意和他唱两分钟的反调,直到他们能够充分证明自己答案的正确。

懦弱的应聘者会选择放弃,这样的人绝对不会被录取。好的应聘者会搬用一整套戴尔·卡耐基的做法来说服你,始终坚持自己的立场。这样的人才会被录用。

无答案的公开试题

微软对面试问题的公开虽不乐意,但也很无奈。早在互联网出现之初,这些试题就已经陆续公开了。

20世纪90年代早期,克里斯·塞尔斯在DevelopMentor公司面试。在面试快结束的时候,一位公司的创始人说道:“好的,你被录用了。但是再问你一个微软公司在面试时常常问到的问题———为什么下水道盖子是圆形的?”

塞尔斯回答说:“没问题,如果你先回答了这个问题,我再来回答你的问题———为什么消防员的背带是红色的?”

这位公司创始人哑口无言。

这次经历促使塞尔斯开始收集微软的面试问题。1996年,他创建了一个网站,并开始把自己听说的微软试题张贴在网上。

南加利福尼亚大学学生柯朗·邦德拉帕提和他的几个朋友一起参加了微软的一次面试。邦德拉帕提把问题整理出来,形成了自己的“微软面试题库”。还有另外一些网站提供类似的服务,例如,4guys from Rolla.com网站的“微软面试问题”和迈克尔·普赖尔的“技术面试问题”(网站里包含各种各样的试题,不只是微软的面试试题)。

你可能认为微软对自己公司的面试问题被公开十分恼怒,事实并不完全是这样。邦德拉帕提和塞尔斯都听说过,微软人力资源部门的人指点员工在初次担任主考官时,上他们的网站参考参考。

当然,应聘者也可以利用这些网站为面试预先准备,但塞尔斯和邦德拉帕提的网站都不提供或者很少提供问题的答案。邦德拉帕提有一次就接到了一个朋友的朋友打来的紧急电话,当时是她参加微软面试的前夜。她面前摆着一大堆从邦德拉帕提网站上打印出来的东西,但是没有一个问题有确定的答案,她想让邦德拉帕提告诉她。

塞尔斯收到许多公司的电子邮件,他们也想“像微软一样招聘”。但他们需要知道问题答案,而塞尔斯的网站把答案都略去了。塞尔斯说:“我总是回答,如果你们不知道答案,就不应该在面试时问这些问题。这往往使他们恼羞成怒。”

微软智力题从哪儿来

逻辑题目是微软16号楼自助餐厅里午餐休闲谈话的一部分。微软的竞争精神使大家认为,能够提出“新”而“有效”问题是一件很“酷”的事情。

许多微软难题的产生带有传奇色彩。据说,有一次史蒂夫·鲍尔默和另一位微软高官慢跑时,看到了一个下水道盖子,便随口问道:“为什么下水道盖子是圆形的?”另一个人灵机一动道:“哟,这可是一个面试的好问题。”这个故事可能真有其事,然而几乎可以肯定,鲍尔默并不是第一个提出下水道盖子这个问题的人。实际上,微软公司的面试智力题都在一些智力题汇编书,或者一些以智力题为主要内容的网站上出现过,微软只不过是给它们“化了妆”而已。

最广为流传的面试问题:

在不使用天平的情况下,怎样称出一架喷气式飞机的重量?

为什么镜子里的影像左右颠倒而不是上下颠倒?

为什么你在宾馆里一打开热水龙头就有热水流出来?

M&M巧克力是怎样做出来的?

你在船上,把一只箱子抛起来,水平面会升高还是下降?

世界上有多少钢琴调音师?

美国有多少加油站?

每小时有多少密西西比河水流过新奥尔良?

一个曲棍球场里的冰有多重?

如果你能够搬走美国50个州中的任何一个,你会搬走哪一个?

地球上有多少个这样的点:往南走1公里,往东走1公里,再往北走1公里,你能回到原来的出发点?

一天中钟表的指针重叠多少次?

迈克和托德两人一共有21美元。迈克的钱比托德多20美元,每个人各有多少钱?在你的答案中不能有分数。

一般说来,将曼哈顿的电话册翻多少次,才能找到你想要找的人名?

你会怎样设计比尔·盖茨的浴室?

你怎样设计一个由计算机控制的微波炉?

  新浪科技讯 2月27日消息,北京市海淀区人民法院已于去年12月20日判决腾讯公司起诉珊瑚虫版QQ侵犯著作权胜诉,该软件作者陈寿福被判向腾讯赔偿经济损失十万元。此次判决很可能将导致珊瑚虫版QQ停止更新。

  业内猜测可能是担忧激起珊瑚虫版QQ老用户的抵触情绪,腾讯在胜诉后采取了低调处理的态度,近日才由知情人士将该案判决结果公诸于众。腾讯公司发言人27日下午对新浪科技表示,陈寿福服从法院判决,已支付赔偿并在网站道歉。

  据了解,腾讯公司2006年8月20日对珊瑚虫版QQ的作者陈寿福提起诉讼,认为珊瑚虫版QQ已侵犯腾讯的著作权,并有不正当竞争行为,要求陈寿福公开赔礼道歉,并赔偿腾讯经济损失人民币50万元。珊瑚虫版QQ在腾讯即时通讯软件QQ的基础上增加了现实IP地址、去除广告等特性,受到很多用户的欢迎,很多用户不安装官方版本的QQ软件,而安装珊瑚虫QQ。

  虽然陈寿福在使用协议中表示珊瑚虫版QQ“仅为方便用户使用之辅助工具,没有任何侵权意图”,但法院认为腾讯对QQ享有计算机软件著作权,陈寿福未经许可修改QQ并在其网站“珊瑚虫工作室”中提供相关下载服务行为已构成侵权。法院最终裁定陈寿福开发珊瑚虫版QQ侵犯腾讯著作权,但驳回了腾讯指其不正当竞争行为的要求,并将赔偿额定为10万元人民币。

  在珊瑚虫工作室的官方论坛上,有不少用户对腾讯赢得官司表示失望,甚至有用户称如果珊瑚虫版QQ停止更新,将不再使用QQ。腾讯公司发言人对此表示,珊瑚虫QQ软件等非官方版本将会带来极大的安全性问题,希望用户采用官方版本。对于与珊瑚虫版QQ类似的非官方软件,腾讯也将继续用法律维护其合法权益。

  陈寿福是北京理工大学计算中心的老师,最早于2001年推出珊瑚虫版QQ,并随着QQ的升级也不断更新。2002年11月,陈寿福曾发表声明称将停止更新和传播珊瑚虫版QQ,并对腾讯保证不再对腾讯QQ软件作出任何修改,但2003年后再度开始更新珊瑚虫版QQ。值得一提的是,和珊瑚虫版QQ类似的木子版QQ在2003年6月由于受到腾讯的压力而停止更新,木子版QQ曾经比珊瑚虫版QQ更为流行。

  目前,珊瑚虫工作室的官方网站上已经不再提供珊瑚虫版QQ的下载,而只有“珊瑚虫增强包”、“珊瑚虫工具栏”等没有更改QQ安装软件本身的插件下载。一位知情人士表示,由于此次官司败诉,珊瑚虫工作室可能将停止珊瑚虫版QQ的更新,而只保留“珊瑚虫增强包”等不更改QQ软件的插件,以规避法律风险。

Oracle(甲骨文)的CEO Larry.Ellison在耶鲁大学2000届毕业典礼上的演讲:耶鲁的毕业生们,我很抱歉—如果你们不喜欢这样的开场。我想请你们为我做一件事。请你—好好看一看周围,看一看站在你左边的同学,看一看站在你右边的同学。
请你设想这样的情况:从现在起5年之后,10年之后,或30年之后,今天站在你左边的这个人会是一个失败者;右边的这个人,同样,也是个失败者。而你,站在中间的家伙,你以为会怎样?一样是失败者。失败的经历。失败的优等生。