<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>H31家园</title>
<link>http://h31home.com/</link>
<Description>欢迎光临使用H31家园</Description>
<language>zh-cn</language>
<copyright>Copyright 2006-2008 H31Home</copyright>
<webMaster>miao.whu@Gmail.com</webMaster>
<image>
	<title>H31家园</title> 
	<url>http://h31home.com/images/miao.gif</url> 
	<link>http://h31home.com/</link> 
	<description>欢迎光临使用H31家园</description> 
</image>
<item><link>http://h31home.com/h312005/blogview.asp?ID=980</link><title><![CDATA[.NET软件保护与破解浅析[转]]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-6-21 10:53:07</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=980</guid><description><![CDATA[<P>.NET软件保护与破解浅析&nbsp;&nbsp;&nbsp;&nbsp; 网上很少看到有关.NET软件保护与破解的文章，刚好分析了几款有一定代表性的.NET软件，于是便将他们的保护措施和如何破解方法记录下来，以便和大家交流。在开始之前，首先申明：本文中反编译和破解的软件只是为学习和研究的目的，请勿非法使用。 </P><P><BR>&nbsp;&nbsp;&nbsp; .NET平台下的软件(exe,dll文件)叫做程序集。它使用一种扩展的PE格式文件来保存。.NET程序集与以往的应用程序不同，它保存的是Microsoft中间语言指令(MSIL)和元数据(Metadata)，而不是机器指令和数据。.NET程序集在运行的时候才会动态将Microsoft中间语言编译成机器指令执行。所以我们不能简单的使用反汇编来解读程序逻辑。初学者明白这点很重要。不过幸运的是，.NET程序集是一种自描述的组件，可以用它自描述的特性反编译出高级程序代码(如c#,vb.net)，这比汇编代码更容易读懂得多。即使不反编译成高级程序代码，Microsoft中间语言本身也是一种抽象、基于堆栈的面向对象伪汇编语言。它本身也比汇编代码更容易读懂。在代码易阅读这点上，.NET程序更容易遭到破解。不过，有矛必有盾，现在有很多产品都可以混淆.NET代码，使得反编译出来后的结果也同样没有可读性。</P><P>&nbsp;&nbsp;&nbsp; .NET程序集与以往的应用程序另一个不同点在于它可以使用强名称签名来防止自身被篡改。使用强名称签名的程序集包含公钥和数字签名信息。.NET在执行具有强名称的程序集前会对它进行安全检查，以防止它被非法篡改。这一点很厉害，它限制了我们通过修改程序代码（爆破）来破解程序的方法。</P><P>&nbsp;&nbsp;&nbsp; .NET平台还提供了很多安全相关的类库，利用这些类库可以写成很强壮的注册算法（如使用RSA对注册文件签名，只有使用私钥才能产生正确的注册码）。这些算法破解者很难破解。</P><P>&nbsp;&nbsp;&nbsp; 另外，现在还出现了很多其他的保护措施，如流程混淆，元数据加密，加密壳，虚拟机技术，编译为本地代码等保护手段。综合使用这些保护手段，会使破解难道大幅度提高。</P><P>&nbsp;&nbsp;&nbsp; 软件破解一般有两种方式。一种方式为不对软件做出修改，只是分析软件注册算法，根据注册算法写出生成正确注册码的注册机程序完成注册过程。第二种方式需要对软件逻辑作出修改，使软件正常判断注册逻辑失效，总是认为软件已经注册成功。这种方式就是通常说的“爆破”。由于第二种方法需要修改软件逻辑，不能保证软件修改后的行为和原来一样，故一般只有在第一种方式尝试失败后才使用第二种方式。</P><P>&nbsp;&nbsp;&nbsp; 下面，我将通过对两款软件的分析来讲解.NET平台下软件的保护措施和破解方法。</P><P>准备的工具： <BR>1，Reflector <A href="http://www.aisto.com/roeder/dotnet/">http://www.aisto.com/roeder/dotnet/</A> <BR>.NET平台下极好的反编译工具。 <BR>2，ManagedSpy <BR>可以查看.NET代码写的程序窗口。&nbsp; </P><P>实例一，GatherBird Copy Large Files 2.4</P><P>&nbsp;&nbsp;&nbsp; 这是个拷贝大文件的工具，它的注册算法不强，可以很容易写出注册机。我们可以通过破解这个软件来了解破解.NET软件的一般流程。</P><P>&nbsp;&nbsp;&nbsp; 首先运行Copy Large Files 2.4 (Windows .Net 2.0 version) 。发现界面有个“Register”的按钮。查看功能说明书，知道这是没有注册的标志，注册了界面上就不会多这个按钮。点击这个按钮，就弹出注册框，窗口标题为“Register”。然后运行ManagedSpy，可以看出DotNetCopyLargeFiles程序有两个窗体，一个“Form1_class”，另一个“RFLib_Forms_Registration_class”。很显然，第二个就是我们弹出的注册框。</P><P>&nbsp;&nbsp;&nbsp; 运行Reflactor，将DotNetCopyLargeFiles.exe拖进Reflactor，查找“RFLib_Forms_Registration_class”就是反编译好的注册框C#或VB源代码。读懂代码。发现在点击“Register”按钮后会触发“button3_Click”函数，在这个函数中将文本框中输入的注册码保存到了registerstring属性中。使用Reflactor查找哪些地方在调用registerstring属性。发现在Form1_class中点击“Register”按钮后将这个registerstring属性保持在Form1_class中的RegistryString字段中。用Reflactor查找谁在使用RegistryString，发现在Form1_class的OnTimerTick方法中将RegistryString传给RSF1_class.SF40Helper方法检查。</P><P>&nbsp;&nbsp;&nbsp; 读懂RSF1_class，这就注册算法所在的类。读代码的时候，可以借助Reflactor反编译成源文件，然后使用VS2005或VS2008编译后动态调试。这样，可以分析SF4就是注册码产出的方法，根据这个方法，我们不难写出注册机算法。下面就是我写的一个注册机算法。当然，你也可以自己写，方法中用到的其他方法和类都可以从Reflector反编译的源代码中得到。</P><P>GenerateKey <BR>1public static string GenerateKey(byte[] exename)&nbsp; <BR>2{&nbsp; <BR>3&nbsp;&nbsp;&nbsp; byte[] buffer = new byte[0x5f];&nbsp; <BR>4&nbsp;&nbsp;&nbsp; byte[] row = new byte[0x800];&nbsp; <BR>5&nbsp;&nbsp;&nbsp; DotNetRandom_class random = new DotNetRandom_class();&nbsp; <BR>6&nbsp;&nbsp;&nbsp; StringBuilder key = new StringBuilder();&nbsp; <BR>7&nbsp;&nbsp;&nbsp; if (!SF20())&nbsp; <BR>8&nbsp;&nbsp;&nbsp; {&nbsp; <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; random.RRandomSeed2(exename);&nbsp; <BR>10 <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 0x5f; i++)&nbsp; <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer[i] = (byte)(i + 0x20);&nbsp; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>15 <BR>16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SF2(row, ref random);&nbsp; <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Random r = new Random();&nbsp; <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int numberIndex = GetNumberIndex(buffer,(byte)r.Next(50,57));&nbsp; <BR>19 <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 6; i ++)&nbsp; <BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key.Append( Convert.ToChar(row[numberIndex * 2]));&nbsp; <BR>23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key.Append(Convert.ToChar( row[numberIndex * 2+1]));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int j = 0; j &lt; buffer[numberIndex]; j++)&nbsp; <BR>25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; random.RRandomUnsignedLong();&nbsp; <BR>27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>28&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SF2b(row, ref random);&nbsp; <BR>29 <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; numberIndex = GetNumberIndex(buffer, (byte)r.Next(48, 57));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>32&nbsp;&nbsp;&nbsp; }&nbsp; <BR>33 <BR>34&nbsp;&nbsp;&nbsp; return key.ToString();&nbsp; <BR>35}&nbsp; <BR>36 <BR>37public static int GetNumberIndex(byte[] buffer, byte number)&nbsp; <BR>38{&nbsp; <BR>39&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; buffer.Length; i++)&nbsp; <BR>40&nbsp;&nbsp;&nbsp; {&nbsp; <BR>41&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (buffer[i] == number)&nbsp; <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return i;&nbsp; <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>45&nbsp;&nbsp;&nbsp; }&nbsp; <BR>46 <BR>47&nbsp;&nbsp;&nbsp; return 20;&nbsp; <BR>48}<BR>&nbsp;</P><P>实例二，ANTS Profiler</P><P>&nbsp;&nbsp;&nbsp; ANTS Profiler是一个检测基于.Net Framework的任何语言开发出的应用程序的代码性能的工具。它使用激活码和网络激活的方式进行双重验证。代码经过了混淆器混淆，程序集也经过强名称签名，注册算法也用的是成熟的RSA算法。所以整体安全性不错，是.NET平台下比较成熟的做法，很有借鉴意义。</P><P>&nbsp;&nbsp;&nbsp; 在安装完ANTS Profiler后，运行ANTS Profiler ，会弹出Trial界面，提示还有14天试用期。另外，有一个激活按钮。点击激活按钮，第一步会弹出提示输入激活码的窗口。这里需要先得到正确的激活码，才能进行下面的验证步骤。那么怎么才能得到正确的激活码呢？像分析第一款软件一样，我们先打开ManagedSpy。在ManagedSpy中我们发现Trial界面的窗体类叫_53，输入激活码的窗体类叫_3。很显然，都是经过代码混淆的，这是ANTS Profiler安全保护的第一关-“代码混淆关”。这一关只是增加过其他关的难度，不需要特别处理。</P><P>&nbsp;&nbsp;&nbsp; 在找到注册信息相关的类名_53后，打开Reflactor，搜索_53类，顺藤摸瓜，再找到_3类，这个类在RedGate.Licensing.Client.dll中(RedGate.Licensing.Client.dll是注册相关的程序集，需要重点关注)。读_3类的代码，发现输入的激活码被设置到_2类中SerialNumber属性中，_2类中_2(string text1)方法就是校验激活码的方法。代码如下： </P><P><BR>Code <BR>1private static bool _2(string text1) <BR>2{ <BR>3&nbsp;&nbsp;&nbsp; text1 = text1.ToUpper().Trim(); <BR>4&nbsp;&nbsp;&nbsp; Regex regex = new Regex(@"[A-Z]{2}-[0-9A-Z]{1}-[0-9A-Z]{1}-\d{5}-[0-9A-F]{4}"); <BR>5&nbsp;&nbsp;&nbsp; Regex regex2 = new Regex(@"\d{3}-\d{3}-\d{6}-[0-9A-F]{4}"); <BR>6&nbsp;&nbsp;&nbsp; if (regex.IsMatch(text1)) <BR>7&nbsp;&nbsp;&nbsp; { <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str = text1.Substring(0, 12); <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str2 = string.Format("{0:X4}", _7._1(str)); <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!text1.EndsWith(str2)) <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>14&nbsp;&nbsp;&nbsp; } <BR>15&nbsp;&nbsp;&nbsp; else if (regex2.IsMatch(text1)) <BR>16&nbsp;&nbsp;&nbsp; { <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str3 = text1.Substring(0, 14); <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str4 = string.Format("{0:X4}", _7._1(str3)); <BR>19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!text1.EndsWith(str4)) <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>23&nbsp;&nbsp;&nbsp; } <BR>24&nbsp;&nbsp;&nbsp; else <BR>25&nbsp;&nbsp;&nbsp; { <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>27&nbsp;&nbsp;&nbsp; } <BR>28&nbsp;&nbsp;&nbsp; return true; <BR>29}<BR>&nbsp;&nbsp;&nbsp; 这段代码表明，激活码是符合regex和regex2这两种正则表达式的形式。同时，满足激活码使用_7._1(string text1)方法返回值结尾。很显然，前12为是激活码信息，后四位是激活码校验位。_7._1(string text1)就是计算校验位的方法，代码如下： </P><P><BR>Code <BR>1internal static uint _1(string text1) <BR>2{ <BR>3&nbsp;&nbsp;&nbsp; long num = 0L; <BR>4&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; text1.Length; i++) <BR>5&nbsp;&nbsp;&nbsp; { <BR>6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num4 = text1[i]; <BR>7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int j = 7; j &gt;= 0; j--) <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool flag = ((num &amp; 0x8000L) == 0x8000L) ^ ((num4 &amp; (((int) 1) &lt;&lt; j)) != 0); <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; num = (num &amp; 0x7fffL) &lt;&lt; 1; <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (flag) <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; num ^= 0x1021L; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>16&nbsp;&nbsp;&nbsp; } <BR>17&nbsp;&nbsp;&nbsp; return (uint) num; <BR>18} <BR>19<BR>&nbsp;&nbsp;&nbsp; 知道了激活码的合法形式和校验位的计算方法，我们就可以构建一个合法的激活码。以regex表示的合法激活码形式为例，选择前12位为最小值“AA-0-0-00000”的一个合法激活码，然后使用_7._1(string text1)方法计算校验码为：“DE33”。那么一个合法的激活码就这样得到了“AA-0-0-00000-DE33”。复制激活码到激活窗口，成功通过验证。至此，ANTS Profiler安全保护的第二关-“激活码验证关”通过了。在激活码验证通过后，点击下一步会进入到网络激活或Email激活界面。选择Email激活，我们可以看见ANTS Profiler收集了版本信息，激活码，session，和机器硬件等信息。这可以保障一个注册码只能用在一台电脑上，值得学习。激活请求信息如下：</P><P>activationrequest <BR>1&lt;activationrequest&gt;&nbsp; <BR>2&lt;version&gt;2&lt;/version&gt;&nbsp; <BR>3&lt;machinehash&gt;F6FB-285E-CD12-667D&lt;/machinehash&gt;&nbsp; <BR>4&lt;productcode&gt;5&lt;/productcode&gt;&nbsp; <BR>5&lt;majorversion&gt;3&lt;/majorversion&gt;&nbsp; <BR>6&lt;minorversion&gt;0&lt;/minorversion&gt;&nbsp; <BR>7&lt;serialnumber&gt;AA-0-0-00000-DE33&lt;/serialnumber&gt;&nbsp; <BR>8&lt;session&gt;689fae08-2fdb-485e-9df7-28e2888cfbff&lt;/session&gt;&nbsp; <BR>9&lt;locale&gt;zh-CN&lt;/locale&gt;&nbsp; <BR>10&lt;/activationrequest&gt; <BR>11<BR>&nbsp;&nbsp;&nbsp; 接下来，就是输入激活响应信息，验证激活响应信息的界面了。这是激活界面的第四步，同样代码在_3类中。跟踪代码，发现验证逻辑在RedGate.Licensing.Client.Licence类中的_2(XmlDocument document1, ref _2 _Ref1)方法中，代码如下： </P><P><BR>Code <BR>&nbsp; 1private bool _2(XmlDocument document1, ref _2 _Ref1) <BR>&nbsp; 2{ <BR>&nbsp; 3&nbsp;&nbsp;&nbsp; XmlNodeList elementsByTagName = document1.GetElementsByTagName("data"); <BR>&nbsp; 4&nbsp;&nbsp;&nbsp; XmlNodeList list2 = document1.GetElementsByTagName("signature"); <BR>&nbsp; 5&nbsp;&nbsp;&nbsp; if ((elementsByTagName.Count != 1) || (list2.Count != 1)) <BR>&nbsp; 6&nbsp;&nbsp;&nbsp; { <BR>&nbsp; 7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._16); <BR>&nbsp; 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>&nbsp; 9&nbsp;&nbsp;&nbsp; } <BR>10&nbsp;&nbsp;&nbsp; string outerXml = elementsByTagName[0].OuterXml; <BR>11&nbsp;&nbsp;&nbsp; string innerXml = list2[0].InnerXml; <BR>12&nbsp;&nbsp;&nbsp; if (innerXml.Length == 0) <BR>13&nbsp;&nbsp;&nbsp; { <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._17); <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>16&nbsp;&nbsp;&nbsp; } <BR>17&nbsp;&nbsp;&nbsp; RSACryptoServiceProvider provider = new RSACryptoServiceProvider(); <BR>18&nbsp;&nbsp;&nbsp; string xmlString = "&lt;RSAKeyvalue&gt;&lt;Modulus&gt;zLizNmLUd4VlIWee1GXgn/KxEwcghPASQ+NUzZhbY2fTGzpW64T6yEOdHlIbhX1DX6yAz2gMZKfnpQL2aFqxh5ACFV9dONSTzuQzkqeXwFEARsMxGP3eTQSWMpwVhEcraSn1zOqMb3CRDeQpgasq0lv4HRFhbwalOifKarjEL/8=&lt;/Modulus&gt;&lt;Exponent&gt;AQAB&lt;/Exponent&gt;&lt;/RSAKeyvalue&gt;"; <BR>19&nbsp;&nbsp;&nbsp; provider.FromXmlString(xmlString); <BR>20&nbsp;&nbsp;&nbsp; byte[] signature = Convert.FromBase64String(innerXml); <BR>21&nbsp;&nbsp;&nbsp; byte[] bytes = Encoding.UTF8.GetBytes(outerXml); <BR>22&nbsp;&nbsp;&nbsp; if (!provider.VerifyData(bytes, new SHA1Managed(), signature)) <BR>23&nbsp;&nbsp;&nbsp; { <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._18); <BR>25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>26&nbsp;&nbsp;&nbsp; } <BR>27&nbsp;&nbsp;&nbsp; _Ref1._1 = false; <BR>28&nbsp;&nbsp;&nbsp; foreach (XmlNode node in elementsByTagName[0].ChildNodes) <BR>29&nbsp;&nbsp;&nbsp; { <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string name = node.Name; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == null) <BR>32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>33&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>35&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name = string.IsInterned(name); <BR>36&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name != "productcodes") <BR>37&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>38&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "serialnumber") <BR>39&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_0294; <BR>41&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "productcode") <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02A7; <BR>45&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "majorversion") <BR>47&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>48&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02BF; <BR>49&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "minorversion") <BR>51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02D7; <BR>53&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>54&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "edition") <BR>55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02EF; <BR>57&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>58&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "machinehash") <BR>59&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>60&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02FF; <BR>61&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>62&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "extension") <BR>63&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_030F; <BR>65&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>66&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "session") <BR>67&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>68&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_0319; <BR>69&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>70&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>71&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>72&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach (XmlNode node2 in node.ＳelectNodes("product")) <BR>73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>74&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _1 _ = new _1(); <BR>75&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>76&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>77&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._1 = node2.ＳelectSingleNode("productname").InnerText; <BR>78&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._1 = Convert.ToInt32(node2.ＳelectSingleNode("productcode").InnerText); <BR>79&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._2 = Convert.ToInt32(node2.ＳelectSingleNode("majorversion").InnerText); <BR>80&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._3 = Convert.ToInt32(node2.ＳelectSingleNode("minorversion").InnerText); <BR>81&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._2 = node2.ＳelectSingleNode("edition").InnerText; <BR>82&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1.Add(_); <BR>83&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>85&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (Exception) <BR>86&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>87&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>89&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>91&nbsp;&nbsp;&nbsp; Label_0294: <BR>92&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._2 = node.InnerText; <BR>93&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>94&nbsp;&nbsp;&nbsp; Label_02A7: <BR>95&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>96&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>97&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = Convert.ToInt32(node.InnerText); <BR>98&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>99&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>101&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>102&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>103&nbsp;&nbsp;&nbsp; Label_02BF: <BR>104&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>105&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>106&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._2 = Convert.ToInt32(node.InnerText); <BR>107&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>108&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>109&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>110&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>112&nbsp;&nbsp;&nbsp; Label_02D7: <BR>113&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>114&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>115&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = Convert.ToInt32(node.InnerText); <BR>116&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>117&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>118&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>119&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>120&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>121&nbsp;&nbsp;&nbsp; Label_02EF: <BR>122&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._5 = node.InnerText; <BR>123&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>124&nbsp;&nbsp;&nbsp; Label_02FF: <BR>125&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = node.InnerText; <BR>126&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>127&nbsp;&nbsp;&nbsp; Label_030F: <BR>128&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = true; <BR>129&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>130&nbsp;&nbsp;&nbsp; Label_0319: <BR>131&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._4 = node.InnerText; <BR>132&nbsp;&nbsp;&nbsp; } <BR>133&nbsp;&nbsp;&nbsp; return true; <BR>134} <BR>135<BR>&nbsp;&nbsp;&nbsp; 细读这段代码可以发现，激活响应信息已经使用RSA算法进行过数字签名，这样可以防止激活响应信息被篡改。要篡改或伪造激活响应信息，必需破解RSA私钥公钥对。公钥已经在方法体中给出，所以需要破解私钥。从公钥可以看出，它使用的1024位密钥长度，理论攻破时间1011MIPS年。虽然破解的可能性存在，但破解几率很小。这就是ANTS Profiler安全保护的第三关-“RSA数字签名关”。这使得我们不得不放弃伪造激活响应信息的办法。通常碰到RSA等成熟算法，我们只能选择“爆破”这款软件了。</P><P>通过前面的分析，我们发现程序中判断软件是否注册的逻辑放在RedGate.Licensing.Client.dll程序集中的Licence类里面。我们需要做的就是打开Reflactor软件，加载RedGate.Licensing.Client.dll文件，将RedGate.Licensing.Client.dll文件Export成C#工程源文件。然后将源文件中Licence类中所有公共方法都返回注册成功的信息，再重新编译生成新的修改后的RedGate.Licensing.Client.dll文件，用新生成的文件替换原RedGate.Licensing.Client.dll文件。这样，注册判断逻辑就被非法篡改了，使软件误认为已经注册。修改后的Licence类就像下面这样： </P><P><BR>Code <BR>1namespace RedGate.Licensing.Client&nbsp; <BR>2{&nbsp; <BR>3&nbsp;&nbsp;&nbsp; using System;&nbsp; <BR>4 <BR>5&nbsp;&nbsp;&nbsp; public class Licence&nbsp; <BR>6&nbsp;&nbsp;&nbsp; {&nbsp; <BR>7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public bool DisplayUI()&nbsp; <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;&nbsp; <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>11 <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static Licence GetLicence(int productCode, string productName, int majorVersion, int minorVersion)&nbsp; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new Licence();&nbsp; <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>16 <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static Licence GetLicence(int productCode, string productName, int majorVersion, int minorVersion, string path)&nbsp; <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new Licence();&nbsp; <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>21 <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static void InitializeAtInstall(string productName, int productCode, int majorVersion, int minorVersion, string guid)&nbsp; <BR>23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>25 <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public bool Activated&nbsp; <BR>27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>28&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>29&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;&nbsp; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>33 <BR>34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public int DaysLeftInTrial&nbsp; <BR>35&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>36&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>37&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>38&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0x7fffffff;&nbsp; <BR>39&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>41 <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string Edition&nbsp; <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>45&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "professional";&nbsp; <BR>47&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>48&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>49 <BR>50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string LicenceFilePath&nbsp; <BR>51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>53&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>54&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return string.Empty;&nbsp; <BR>55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>57 <BR>58&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string SerialNumber&nbsp; <BR>59&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>60&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>61&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>62&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "AA-0-0-00000-DE33";&nbsp; <BR>63&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set&nbsp; <BR>65&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>66&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>67&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>68 <BR>69&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public RedGate.Licensing.Client.TrialStatus TrialStatus&nbsp; <BR>70&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>71&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>72&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return RedGate.Licensing.Client.TrialStatus.InTrial;&nbsp; <BR>74&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>75&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>76&nbsp;&nbsp;&nbsp; }&nbsp; <BR>77}&nbsp; <BR>78</P><P>&nbsp;&nbsp;&nbsp; 爆破虽然是一种好方法，不过.NET中有专门对付这种方法的杀手锏-“强名称签名”。不幸的是，ANTS Profiler使用了强名称签名机制，是我们在爆破软件这条路上受阻。这就是ANTS Profiler软件保护的第四关-“强名称签名”。强名称签名会在软件被加入GAC时验证。如果软件没在GAC中，那么运行软件的时候也会验证。如果软件被篡改，在运行软件的时候软件会直接抛出异常，使得软件无法正常运行。</P><P>&nbsp;&nbsp;&nbsp; 那么如何破解这种强名称签名验证机制呢？</P><P>&nbsp;&nbsp;&nbsp; 有几种办法可以解除强名称的验证机制。第一种方法适合单个文件的软件，对单个文件的软件我们可以移除强名称签名信息，使软件变成弱名称的程序，这样在软件运行的时候就不会验证是否被篡改了。对于引用关系复杂的多文件软件，我们可以采取第二种方法。这种方法需要理由.NET平台上设计的“后门”才能完成。这个“后门”是什么呢？原来，为了使软件开发后能够使用混淆工具混淆，微软允许延迟签名程序集，被延迟签名的程序集可以在混淆之后再重新签名。而为了测试方便，只需要在注册表中加入一条记录就可以使混淆修改后的程序在没有被重新签名前也能运行，即不进行强名称验证。</P><P>&nbsp;&nbsp;&nbsp; 利用这个“后门”，我们只需要将修改后的修改完Licence类编译成新的RedGate.Licensing.Client.dll文件，编译选项中使用任意一对公钥私钥对强名称签名，并选择延迟签名，然后篡改编译后的public key为原始RedGate.Licensing.Client.dll文件的public key。再在注册表中加入一条记录就可以是强名称签名验证机制失效，达到破解的目的。具体做法如下：</P><P>1，我们用sn命令生成一个新的公钥私钥对，用于签名</P><P>sn -k my.key</P><P>2，将反编译的RedGate.Licensing.Client.dll工程文件中Licence类修改成上面的代码，并在编译选项中使用第一步得到的my.key签名程序集。注意，一定要选择延迟签名选项。编译生成新的RedGate.Licensing.Client.dll文件。</P><P>3，使用sn命令得到原始RedGate.Licensing.Client.dll文件的public key和新RedGate.Licensing.Client.dll文件的public key。</P><P>sn -Tp RedGate.Licensing.Client.dll</P><P>4，使用二进制编辑软件，如WinHex打开新的RedGate.Licensing.Client.dll文件，替换掉它的public key为老文件中的public key。</P><P>5，在注册表“Software\Microsoft\StrongName\Verification\”下加一条子健“RedGate.Licensing.Client,7F465A1C156D4D57”即可完成破解。这步也可以使用sn命令代替。</P><P>sn -Vr RedGate.Licensing.Client.dll</P><P>&nbsp;&nbsp;&nbsp; 当然，你可以将修改后的RedGate.Licensing.Client.dll文件打包到一个Patch文件中，并在Patch文件中自动完成替换文件和修改注册表的操作。至此，ANTS Profiler就被破解了。 </P><P>&nbsp;&nbsp;&nbsp; 通过对以上两款软件的分析，我们可以看出，ANTS Profiler的保护手段还是比较成熟的，值得借鉴。它使用了名称混淆，激活码+网络验证，RSA数字签名，强名称签名等保护手段，整体安全系数较高。而GatherBird Copy Large Files则没有充分运用上.NET平台提供的保护措施，还处于比较低的保护级别。如果我们综合运用流程混淆，元数据加密，加密壳，虚拟机技术，编译为本地代码等保护手段，软件保护强度将更高。</P><P>&nbsp;</P>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=979</link><title><![CDATA[关于CAB在ASP内的调用与使用[转]]]></title><author>admin</author><category>ASP学习</category><pubDate>2008-6-21 10:19:09</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=979</guid><description><![CDATA[具体代码如下: <BR><FONT face=Verdana><FONT face=Verdana>&lt;%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%&gt;<BR>&lt;% on error resume next%&gt;<BR>&lt;!--#include file="../Connections/ds.asp" --&gt;<BR>&lt;!--#include file="../Connections/informix.asp" --&gt;</FONT><BR>&lt;html&gt;<BR>&lt;head&gt;<BR>&lt;title&gt;*********title&gt;<BR>&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<BR>&lt;link rel="stylesheet" href="../css/css1.css" type="text/css"&gt;<BR>&lt;/head&gt;<BR>&lt;body&gt;<BR><SPAN style="COLOR: red">&lt;OBJECT id=htactx name=htactx <BR>classid=clsid:FB4EE423-43A4-4AA9-BDE9-4335A6D3C74E codebase="../js/HTActX.cab#version=1,0,0,1" style="HEIGHT: 0px; WIDTH: 0px"&gt;&lt;/OBJECT&gt;<BR><SPAN style="COLOR: #000000"><SPAN style="COLOR: #008000">'在此对CAB包进行应用以后,在下面的代码中进行调用,但总是有问题出现,错误代号为:424(缺少对象)<BR></SPAN>&lt;%<BR><P><FONT face=Verdana>'启用USBkey进行验证</FONT></P><P><FONT face=Verdana>dim SubmitTm<BR>dim ReceiveTm<BR>dim y<BR>dim m<BR>dim d<BR>dim h<BR>dim n<BR>dim se<BR>Dim FirstDigest<BR>Dim Digest<BR>dim EnData<BR>Digest= "01234567890123456"<BR>'dim htactx<BR>'set htactx = CreateObject("HTSrvActX.HTSrvActXCtrl")<BR>dim LibVer<BR>&nbsp;LibVer = htactx.GetLibVersion<BR>&nbsp;If Err.number &lt;&gt; 0 Then<BR>&nbsp;&nbsp;&nbsp;' response.Write(err.number)<BR>&nbsp;&nbsp;&nbsp; response.write "&lt;script language=javascript&gt; alert('加载客户端控件失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;else<BR>&nbsp;dim hCard<BR>&nbsp;&nbsp;hCard = 0&nbsp;<BR>&nbsp;&nbsp;hCard = htactx.OpenDevice(1)'打开设备<BR>&nbsp;&nbsp;If Err.number&lt;&gt;0 or hCard = 0 then<BR>&nbsp;&nbsp; response.Write(err.number)<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('打开硬件锁失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;dim UserName<BR>&nbsp;&nbsp;UserName = htactx.GetUserName(hCard)'获取用户名<BR>&nbsp;&nbsp;if id&lt;&gt;UserName then<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('您输入的登录编号与锁不一致,请核查！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;Digest = htactx.HTSHA1(Rnddata,lRndLen)'SHA1数据<BR>&nbsp;&nbsp;if Err.number&lt;&gt;0 then<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('USB安全锁数据加密失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;Digest = Digest&amp;"04040404"'对SHA1数据进行补码<BR>&nbsp;&nbsp;EnData = htactx.HTCrypt(hCard,0 ,0,Digest, len(Digest))'DES3加密SHA1后的数据<BR>&nbsp;&nbsp;If Err.number&lt;&gt;0 Then <BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('HashToken compute！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;&nbsp; end if<BR>&nbsp;dim obj<BR>&nbsp;dim PasswordInFile,UserNameInFile<BR>&nbsp;PasswordInFile=rs1("usbkey")<BR>&nbsp;UserNameInFile=rs1("gys")<BR>&nbsp;&nbsp;&nbsp;set obj = CreateObject("HTSrvActX.HTSrvActXCtrl")<BR>&nbsp;&nbsp;&nbsp;Digest = obj.HTSrvSHA1(Rnddata, len(Rnddata))<BR>&nbsp;&nbsp;&nbsp;Digest = Digest&amp;"04040404"<BR>&nbsp;&nbsp;&nbsp;ServerEncData = obj.HTSrvCrypt(0, PasswordInFile,len(PasswordInFile),0, Digest, len(Digest))<BR>&nbsp;&nbsp;&nbsp;if UCase(ServerEncData)= UCase(EndData) and UCase(UserNameInFile) = UCase(UserName) Then <BR>&nbsp;&nbsp;&nbsp;session("userID")=rssql2("gys")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session("name")=rssql2("gysname")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session("userclass")=1<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.redirect "../default.asp"<BR>&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('Anknow Error Happend！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;&nbsp;end if<BR></FONT></P>&nbsp; end if<BR>%&gt;<BR><FONT face=Verdana>&lt;/body&gt;<BR>&lt;/html&gt;</FONT></SPAN></SPAN></FONT>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=978</link><title><![CDATA[SQLServer2000的“数据库维护计划”[转]]]></title><author>admin</author><category>Oralce</category><pubDate>2008-6-21 10:07:18</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=978</guid><description><![CDATA[<FONT color=#5b5b5b>具体实现步骤<BR>　　<STRONG>目录 </STRONG><BR>　　<STRONG>第一步：打开SQL Server“企业管理器”窗体</STRONG><BR>　　<STRONG>第二步：找到“数据库维护计划”功能</STRONG><BR>　　<STRONG>第三步：创建“数据库维护计划”</STRONG><BR>　　<STRONG>第四步：维护和管理“数据库维护计划”</STRONG><BR>　　<STRONG>第五步：启动SQL Server 2000代理以便执行“作业”</STRONG><BR>　　<STRONG>第六步：检查结果 </STRONG><BR>　　“数据库维护计划”功能在SQL Server 2000的“企业管理器”中可以找到。<BR>　　<STRONG>说明： </STRONG><BR>　　1.以下操作是在服务器的Windows 2000 Server上进行操作的。<BR>　　2.由于SQL Server 2000执行备份时将产生许多文件(特别是在进行事务日志备份时)，所以建议按数据库名称分别建立独立的备份目录进行存储。<BR>　　3.以下所有操作过程当中一般不会对数据库的使用产生影响。<BR>　　<STRONG>第一步：打开SQL Server“企业管理器”窗体 </STRONG><BR>　　用鼠标单击任务栏上的“开始”按钮中的“程序(P)”菜单下的“Microsoft SQL Server”子菜单中的“企业管理器”菜单项，即可打开SQL Server 2000的“企业管理器”窗体。<BR>　　<STRONG>第二步：找到“数据库维护计划”功能 </STRONG><BR>　　在“企业管理器”窗体中左侧的树型选项卡中，用鼠标单击“+”图标扩展开“控制台根目录”下的“Microsoft SQL Servers”，可以看到其下有一个“SQL Server组”；接着继续扩展开“SQL Server组”，此时可以看到其下出现了服务器的名称(图1中的“JXNC-SERVER”就是我的服务器的名称)；再继续扩展开此服务器，可以看到其下列出了诸如“数据库”、“数据转换服务”等项目；最后单击“管理”项目，可以看到其下存在一个“数据库维护计划”(如图1)。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </FONT><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑">单击“数据库维护计划”项目，在“企业管理器”窗口右侧将会显示出已经存在的维护计划项目。每个维护计划均包括以下项目：<BR>　　1.名称：就是维护计划的名称。此名称可以自定义，中英文皆可。<BR>　　2.数据库：就是维护计划所进行维护的数据库的名称。<BR>　　因为一个维护计划允许同时维护多个数据库，所以此处可以显示出多个数据库的名称(在图1中可以看到名为“系统数据库备份”的数据库维护计划中的“数据库”就包括三个数据库：master、model和msdb)。<BR>　　3.服务器：也就是维护计划所维护的数据库所处的服务器的名称。“(local)”表示是本地服务器。<BR>　　4.对策：是指维护计划所需要进行的具体维护工作的内容。<BR>　　图1中有3个“数据库维护计划”均为“数据库备份，事务日志备份”，它的含义就是这些维护计划中同时对所指定的数据库进行“数据库”和“事务日志”的备份。<BR>　　<STRONG>第三步：创建“数据库维护计划” </STRONG><BR>　　鼠标右击“数据库维护计划”项目，选择“新建维护计划(P)”功能，将打开“数据库维护计划向导”窗体，依照此向导能够创建一个新的“数据库维护计划”。<BR>　　步骤1：单击 “下一步(N)”按钮，打开“选择数据库”窗体(如图2)。在此窗体中可以选定一个或多个的数据库作为操作对象。为了叙述方便，我在此只选择了一个数据库“regie”。</SPAN><FONT color=#3366ff> </FONT><P style="LINE-HEIGHT: 150%; TEXT-ALIGN: center" align=center><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑"><FONT color=#3366ff><IMG height=383 alt="" src="http://www.cnblogs.com/images/cnblogs_com/zhaoqing/bf1.JPG" width=497 border=0></FONT>　 <BR>　　步骤2：单击图2中的“下一步(N)”按钮，打开“更新数据优化信息”窗体(如图3)。<BR>　　</SPAN></P><P style="LINE-HEIGHT: 150%; TEXT-ALIGN: center" align=center><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑"><IMG height=383 alt="" src="http://www.cnblogs.com/images/cnblogs_com/zhaoqing/bf2.JPG" width=498 border=0>　</SPAN></P><P style="LINE-HEIGHT: 150%"><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑"><BR></SPAN><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑">　　在此窗体中可以对数据库中的数据和索引重新进行组织，以及能够设定在满足一定条件的情况下，维护计划自动删除数据库中的未使用的空间，以便提高性能。<BR>　　但要注意的是，在此窗体中，只要选定了“重新组织数据和索引页[R]”复选框，“更新查询优化器所使用的统计。示例[D]”复选框将失效(变成灰色，不能选择)。而且“重新组织数据和索引页[R]”复选框和“从数据库文件中删除未使用的空间[M]”复选框二者只要有一个被选中，其下的“调度[S]”功能才有效。单击“更改[C]”按钮可以对“调度”进行自定义。</SPAN></P><P style="LINE-HEIGHT: 150%; TEXT-ALIGN: left" align=left><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑">各位读者可以根据自身情况决定是否选用其中的功能。当然也可以通过单击“帮助”按钮来查看各功能的具体含义。<BR>　　在此窗体中能够便捷地设定每项作业的持续运行时间和运行的频率。完成自己的设置后，一定要选定右上角的“启用调度[B]”复选框，这样一个作业调度才算真正完成了。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN><SPAN style="FONT-SIZE: 9pt; COLOR: #5b5b5b; LINE-HEIGHT: 150%; FONT-FAMILY: 微软雅黑">步骤3：单击图3中的“下一步(N)”按钮，打开“检查数据库完整性”窗体。<BR>　　在此窗体中可以设定维护计划在备份数据库前自动检查数据库的完整性，以便检测由于硬件或软件错误而导致数据的不一致。在此窗体中只有先选定了“检查数据库完整性[H]”复选框即可。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 剩下的操作就不提了，只要根据自己的需要进行相应的配置即可，最后不要忘了数据库内的作业任务检查一下，看有没有启用。<BR></SPAN></P>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=977</link><title><![CDATA[重装grub的方法及grub相关配置]]></title><author>admin</author><category>Linux学习</category><pubDate>2008-6-6 16:24:38</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=977</guid><description><![CDATA[<P>&nbsp; 重装系统的时候,MBR都会被重写,这样原来的GRUB或LILO 就会不见了,或者由于某些原因使原来的GRUB不见了,就得重装GRUB.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 重装GRUB的方法,任选一种试试:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.用安装光盘启动,选择升级安装,再只选安装GRUB.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.用安装光盘启动,到BOOT 那里输入 linux rescue&nbsp;&nbsp;&nbsp;&nbsp; 进入救援模式,到出现#命令提示符时输入 chroot&nbsp;&nbsp; /mnt/sysimage&nbsp;&nbsp; 之后再输入 grub-install&nbsp;&nbsp; /dev/hda <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.没有软驱如何修复grub/lilo 引导菜单?<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a.把第一张linux安装盘里的dosutils 目录复制到windows盘中.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b.进入纯DOS,进入dosutils目录,执行loadlin&nbsp; autoboot/vmlinuz&nbsp;&nbsp; root=/dev/hdx -&gt;hdx 是linux的根分区号,这样就能进入linux.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.执行grub-install&nbsp;&nbsp; /dev/hdx&nbsp; (x=a,b,c,d)即可重写引导.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.如果用grub来引导linux 和windows,当windows出毛病重装安装后会破坏MBR中的GRUB,这时需要恢复GRUB<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a.把linux 安装光盘的第一张放到光驱,重启到BIOS中,把第一引导设为光驱引导.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b.安装界面出来后,按F4<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.一系列简单的点鼠标后会出现提示符 sh#,这样我们就可以操作GRUB了,sh#&nbsp; grub&nbsp; 会出现这样的提示符: grub&gt;root&nbsp; (hdx,y)&nbsp;&nbsp;&nbsp; x为0是第一个盘,为1是第二个盘;y为linux系统所在的根分区.grub&gt;setup&nbsp; (hd0)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5.如何删除GRUB或LILO<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在DOS下执行fdisk&nbsp;&nbsp; /mbr&nbsp; 就OK了.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.如何配置grub?<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 修改 /boot/grub/grub.conf 文件,其中default=n n是数字,是grub引导菜单默认被选中的项,n从0开始,0表示第一项,1表示第二项依次类推.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout=x&nbsp;&nbsp;&nbsp; 是时间,单位为秒,也就是引导菜单显示后,如果x秒内不进行选择,那么grub将启动默认项.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; splashimage=xxxxxxx&nbsp;&nbsp; 这是引导菜单的背景图.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; title Red Hat 8.0&nbsp;&nbsp; 是启动菜单列表里显示的名字.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; root&nbsp;&nbsp; (hd1,6)&nbsp;&nbsp; 用来指定你的boot分区位置,hdx是所在的第几块硬盘,hd1是第二块;y的分区位置,从0开始<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kernel&nbsp;&nbsp; /boot/vmlinuz-2.4.18-14&nbsp;&nbsp;&nbsp; 是要用的内核路径,如果你编译了新的内核,把它改成新内核的路径.</P><P>&nbsp;</P>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=976</link><title><![CDATA[于梯度方向图的汽车牌照定位[收集]]]></title><author>admin</author><category>VC学习</category><pubDate>2008-6-6 16:20:59</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=976</guid><description><![CDATA[<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由梯度公式获取梯度纹理方向图<BR>用Sobel算子分别求取水平方向、垂直方向、倾斜方向(包括45度和135度)的梯度分量，分别用Gx、Gy、Gxy、Gyx表示。<BR>将车牌灰度图分成若干块WxW(5x5)进行分块处理，其计算量减少为对整幅图象的25倍，速度快、计算少能满足系统的实时性要求。<BR>再分别对分块的灰度图利用已求取的Gx、Gy、Gxy、Gyx求取每块的局部方向vx、vy。</P><P>?根据反正切函数求取车牌图象的方向角度值。<BR>a=atan(Vx(i,j)/ Vy(i,j))/2<BR>以上步骤已经求取了图象的纹理梯度方向，主要代码如下：<BR>CClientDC dc(this);<BR>&nbsp;&nbsp;&nbsp; CPlateDoc *pDoc=GetDocument();<BR>&nbsp;&nbsp;&nbsp; CPen mypen(PS_SOLID,0,RGB(0,100,126));<BR>&nbsp;&nbsp;&nbsp; dc.ＳelectObject(&amp;mypen);<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp; Image *im;<BR>&nbsp;&nbsp;&nbsp; im=pDoc-&gt;m_dib.DibToImage();<BR>&nbsp;&nbsp;&nbsp; <BR>//---------------------------------------------<BR>Field&nbsp; *im_x,*im_y,*im_jd,*im_vx,*im_vy；<BR>Field&nbsp; *im_wenli;<BR>Field&nbsp; *im_xy,*im_yx;</P><P>&nbsp;&nbsp; im_x=new&nbsp; Field(im-&gt;width,im-&gt;height);<BR>&nbsp;&nbsp; im_y=new&nbsp; Field(im-&gt;width,im-&gt;height);<BR>&nbsp;&nbsp; im_xy=new&nbsp; Field(im-&gt;width,im-&gt;height);<BR>&nbsp;&nbsp; im_yx=new&nbsp; Field(im-&gt;width,im-&gt;height);<BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp; im_vx=new Field(im-&gt;width,im-&gt;height);<BR>&nbsp;&nbsp; im_vy=new Field(im-&gt;width,im-&gt;height);</P><P>&nbsp;&nbsp; im_jd=new Field(im-&gt;width/W,im-&gt;height/W);<BR>&nbsp;&nbsp; im_wenli=new Field(im-&gt;width/W,im-&gt;height/W);<BR>&nbsp;&nbsp; </P><P>&nbsp;//-----------------------------求取方向图------<BR>&nbsp;&nbsp; int i,j,u,v,k,l;<BR>&nbsp; for(j=0,l=1;j&lt;im-&gt;height-W;j+=W,l++)<BR>&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(i=0,k=1;i&lt;im-&gt;width-W;i+=W,k++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_x-&gt;field[k][l]=(im-&gt;ng[k-1][l+1]+2*im-&gt;ng[k][l+1]+im-&gt;ng[k+1][l+1])-<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (im-&gt;ng[k-1][l-1]+2*im-&gt;ng[k][l-1]+im-&gt;ng[k+1][l-1]);//水平方向的梯度</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_y-&gt;field[k][l]=(im-&gt;ng[k-1][l-1]+2*im-&gt;ng[k-1][l]+im-&gt;ng[k-1][l+1])-<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (im-&gt;ng[k+1][l-1]+2*im-&gt;ng[k+1][l]+im-&gt;ng[k+1][l+1]);//垂直方向的梯度</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_xy-&gt;field[k][l]=(im-&gt;ng[k+2][l]+2*im-&gt;ng[k+1][l+1]+im-&gt;ng[k][l+2])-<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (im-&gt;ng[k-1][l+1]+2*im-&gt;ng[k][l]+im-&gt;ng[k+1][l-1]);//45方向的梯度</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_yx-&gt;field[k][l]=(im-&gt;ng[k][l-1]+2*im-&gt;ng[k+1][l]+im-&gt;ng[k+2][l+1])-<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (im-&gt;ng[k-1][l]+2*im-&gt;ng[k][l+1]+im-&gt;ng[k+1][l+2]);//135方向的梯度<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp; }<BR>//---------------------------------------------<BR>&nbsp;&nbsp; for(j=0,l=0;j&lt;im-&gt;height-W;j+=W,l++)<BR>&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(i=0,k=0;i&lt;im-&gt;width-W;i+=W,k++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(v=j;v&lt;j+W;v++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(u=i;u&lt;i+W;u++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_vx-&gt;field[u][v]=(2*im_x-&gt;field[u][v]*im_y-&gt;field[u][v])/(fabs(im_x-&gt;field[u][v])+fabs(im_y-&gt;field[u][v]))+(im_xy-&gt;field[u][v]*<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_xy-&gt;field[u][v]-im_yx-&gt;field[u][v]*im_yx-&gt;field[u][v])/(fabs(im_xy-&gt;field[u][v])+fabs(im_yx-&gt;field[u][v]));<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_vy-&gt;field[u][v]=(im_x-&gt;field[u][v]*im_x-&gt;field[u][v]-im_y-&gt;field[u][v]*im_y-&gt;field[u][v])/(fabs(im_x-&gt;field[u][v])+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fabs(im_y-&gt;field[u][v])) -(2*im_xy-&gt;field[u][v]*im_yx-&gt;field[u][v])/(fabs(im_xy-&gt;field[u][v])+fabs(im_yx-&gt;field[u][v]));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_jd-&gt;field[k][l]=(atan2(-im_vx-&gt;field[k][l],-im_vy-&gt;field[k][l]))/2;</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(im_jd-&gt;field[k][l]&lt;0)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_wenli-&gt;field[k][l]=(im_jd-&gt;field[k][l]+2*PI)/2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_wenli-&gt;field[k][l]=im_jd-&gt;field[k][l]/2; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(fabs(im_x-&gt;field[k][l]-im_y-&gt;field[k][l])&lt;16 &amp;&amp; fabs(im_xy-&gt;field[k][l]-im_yx-&gt;field[k][l])&lt;21)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; im_wenli-&gt;field[k][l]=0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp; }</P>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=975</link><title><![CDATA[基于四叉树空间划分的地形实时渲染方法[收集]]]></title><author>admin</author><category>VC学习</category><pubDate>2008-6-6 15:57:09</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=975</guid><description><![CDATA[<P>地形是计算机图形的一个重要组成部分，而它又具有特殊的形态。地形往往覆盖面积极广，且精度要求很高，使得我们必须用许多多边形来描述。这样的特点使得我们不能像对待其他普通模型那样对待地形。要想实时地渲染地形，我们需要一些特殊的方法。 <BR>&nbsp;&nbsp;&nbsp; 地形渲染一直以来都是计算机图形学中一个重要的研究领域。并且在这一方面已经诞生了许多优秀的算法。其中包括基于体素的渲染方法，也有基于多边形的渲染方法。早期的游戏，如三角洲特种部队就是采用体素渲染法的成功例子。体素法类似光线追踪渲染，它从屏幕空间出发，找到地形与屏幕像素发出的射线交点，然后确定该像素的颜色。这种方法不依赖具体的图形硬件，整个渲染过程完全使用CPU处理，因此它不能使用现代硬件来加速，并且对于一个场景来说，往往不只是地形，还有其他使用多边形描述的物体，体素法渲染的图像很难与硬件渲染的多边形进行混合，因此这种方法现在用得极少。而多边形渲染方法则成为一种主流。选择多边形来描述和渲染地形有很多的理由和优点。最重要的是它能够很好地使用硬件加速，并且能够和其他多边形对象一起统一管理。</P><P>&nbsp;&nbsp;&nbsp; 已有大量优秀的基于多边形的地形渲染算法。比较经典的算法有M. Duchaineau等人提出ROAM算法。这个算法采用一棵三角二叉树来描述整个地形。一个地形在最初的层次上由两个较大的等腰直角三角形组成，这两个等腰直角三角形可以被不断地细分来展现地形的更多细节。每一次细分过程都向直角三角形的斜边的中点处增加一个由高程数据所描述的顶点，该点将所在的直角三角形一分为二，同时该算法也定义了一些规则来保证地形中不会因相邻两个三角形细节层次的不同而出现裂缝。这个算法已被许多游戏所采用。还有一类算法，通过将地形在X-Z投影面上不断地规则细分来得到不同的细节，这就是本文要介绍的四叉树空间划分算法。另外，最新提出的一个地形算法也不得不提，Hugues Hoppe在2004年提出的几何裁剪图方法(Geometry Clipmaps)，算法使用了最新硬件所支持的顶点纹理来定义地形的外观，并且对于距离摄影机不同远近的地方采用不同的纹理层，最大限度地使用硬件加速了地形渲染的过程。这个方法听起来非常美妙，但它目前只被较少的硬件支持。因为顶点纹理是Shader Model 3.0才支持的功能，也就是说只有DirectX 9.0c级别的显卡才能支持这种算法。这对于某些有普及性要求的图形应用程序，尤其是对游戏来讲不是一件好的事情。因此大多数人现在还在使用经典的地形渲染方法。</P><P>&nbsp;&nbsp;&nbsp; 首先，基于四叉树的地形渲染方法使用高程数据作为数据源。且算法要求高程数据的大小必须为2n+1的正方形。所谓高程数据，即色彩范围在0-255的灰度图片，不同的灰度代表了不同的高度值。如果某高程数据指出这个高程数据最高处的Y坐标值是4000，那么在高程数据中一个值为255的像素点就表示这个点所代表的地形区域的高度是4000，同理如果该像素值是127那么就表示这个点所代表的地形区域的高度是4000×(127/255)=2000。高程数据的每个像素都对应所渲染网格中的一个顶点。另外还有一个参数描述顶点与顶点之间的水平距离，以及一个描述最大高度的参数。因此地形的基本数据结构如下：</P><P>&nbsp;&nbsp;&nbsp; struct Terrain<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char **DEM; //一个描述高程数据的二维数组<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float CellSpace;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float HeightScale;&nbsp; <BR>&nbsp;&nbsp;&nbsp; }</P><P>&nbsp;&nbsp;&nbsp; 其中，各变量的具体意义如下图所示：<BR></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><CITE style="FONT-STYLE: normal"><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai1.jpg" border=0>&nbsp;有了这些参数，我们可以很容易地由高程数据的参数值得到它所表述的多边形网格。得到这个网格之后，可以简单地把它放入顶点数组，并为之建立一个顶点索引，就可以传入硬件进行渲染了。然而，事情并不是这么简单。对于较小尺寸的高程数据(如129×129)，这样做确实可行，但随着高程数据规模的增大，所需的顶点数和描述网格的三角形数会急剧膨胀。这个数值很快就会大到最新的显卡也无法接受。比如一个1025×1025的高程数据，我们需要1025×1025=1050625个顶点，以及1050625×2=2101250个三角形。就算你的显卡每秒能够渲染1000万个三角形，你也只能得到不到5fps的渲染速度，况且你的场景可能还不只包括地形。因此我们必须想办法在不影响视觉效果的情况下缩减所渲染的三角形数量，另外还应该注意一次性将最多的数据预先传给硬件以节约带宽。</P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><CITE style="FONT-STYLE: normal"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 这里要讲解的算法，目的就是在不影响或在视觉可以接受的范围内缩减所渲染三角形的数量，以达到实时渲染的要求。根据测试，本算法在漫游大小为1025*1025的地形时速度稳定在150fps以上(在nVidia Geforce 6200 + P4 1.6GHz的硬件上得到)。</SPAN></CITE></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><CITE style="FONT-STYLE: normal"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 由于地形覆盖范围广，但它的投影在XZ平面上均匀分布(以下采用OpenGL中的右手坐标系，Y轴为竖直向上的坐标轴)，因此我们有必要考虑对地形进行空间划分。正是由于这样的均匀分布，给我们的划分过程带来了便利。我们不需要具体地去分割某个三角形，只要选择那些过顶点且和X或Z轴垂直的平面作为划分面即可。例如对于一个高程数据，我们可以以坐标原点作为地形的中心点，然后沿着X轴和Z轴依次展开来分布各个顶点。如下如所示。</SPAN></CITE></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai2.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 首先，我们可以选择X=0和Z=0这两个平面，将地形划分为等大的四个区域，然后对划分出来的四个子区域进行递归划分，每次划分都选择交于区域中心点并且互相垂直的两个平面作为划分面，直到每个子区域都只包含一个地形单元块（即两个三角形）而不能再划分为止。例如对于上图所示9*9大小的地形块，经过划分之后如下图所示：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai3.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 由图可知，只有高程数据满足<CITE style="FONT-STYLE: normal">大小2<SUP>n</SUP>+1的正方形这个条件，我们才可能对地形进行均匀划分。</CITE>我们可以把划分结果用一棵树来表述，由于每次划分之后产生四个子节点，因此这棵树叫四叉树。那么，这棵树中应该存储那些信息呢？首先对于每个节点，应该指定这个节点所代表的地形的区域范围。并不是把地形网格中实际的顶点放入树中，而是要在树中说明这个节点覆盖了地形的那些区域。比如一个子节点应该有一个Center(X,Y)变量，指定这个节点的中心点所对应的顶点索引，或编号。为了方便起见，可以把地形中心点编号为(0,0)然后沿着坐标轴递增。此外还要有个变量指定这个节点到底覆盖了地形的多少个顶点。如下图所示。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai4.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 我们目前的四叉树的数据结构如下：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; <STRONG>struct</STRONG> QuadTreeNode<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>QuadTreeNode</STRONG> *Children[4];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>int</STRONG> CenterX,CenterY;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>int</STRONG> HalfRange;<BR>&nbsp;&nbsp;&nbsp; }</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 有了四叉树之后，如何利用它的优势呢？首先我们考虑简单的视见体裁剪(View Frustum Culling，以下简称VFC)。相信很多接触过基本图形优化的人都应该熟悉VFC，VFC的作用既是对那些明显位于可见平截头体之外的多边形在把它们传给显卡之前剔除掉。这个过程由CPU来完成。虽然简单，但它却非常有效。VFC过程如下：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 1.为每个节点计算包围球。包围球可以简单的以中心顶点为球心，最大坐标值点(节点所覆盖的所有顶点的最大X、Y、Z值作为此点的坐标值)到球心的距离为半径。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 2.根据当前的投影和变换矩阵计算此时可视平截头体的六个平面方程。这一步可以参考Azure的Blog上的一篇文章，这篇文章给出了VFC的具体代码。<A href="http://www.azure.com.cn/article.asp?id=155" target=_blank><FONT color=#0022cc>单击这里</FONT></A>。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 3.从树的根结点以深度优先的顺序遍历树。每次访问节点时，测试该节点包围球与视见体的相交情况。在下面的情况下，包围球与视见体相交：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1) 球心在六个平面所包围的凸状区域内部。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2) 球心在六个平面所包围的凸状区域外部，但球心到某个平面的距离小于半径。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 4.如果相交测试显示包围球和视见体存在交集，继续递归遍历此节点的4个子节点，如果此节点已经是叶节点，则这个节点应被绘制。如果不存在交集，放弃这个节点，对于这个节点的所有子节点不再递归检查。因为如果一个节点不可见，那么其子节点一定不可见。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 这样，我们剔除了那些不在视见体内的地形区域，节约了一些资源。但这还不够。在某些情况下，VFC可能还会指出整个地形都可见，在这种情况下，将这么多三角形都画出显然是不可取的。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 因此还要考虑地形的细节层次(LOD)。我们应该考虑到，地形不可能所有部分都一样平坦或陡峭。对于平坦的部分，我们用过多的三角形去描述是没有意义的。而对于起伏程度较大的区域，只有较多的三角形数量才不让人感到尖锐的棱角。再者，无论地形起伏程度如何，那些距离视点很远的区域，也没有必要花费太多的资源去渲染，毕竟它们投影到屏幕上的面积很小，对其进行简化也是必要的。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 既然我们要对起伏程度不同的区域采用不同的细节级别，我们首先必须找到一种描述地形起伏程度的量。与其说起伏程度，不如说是地形的某个顶点因为被简化后而产生的误差。要计算这个误差，我们先要了解地形是如何被简化的。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 考虑下图所示的地形块，它的渲染结果如下图右图所示。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai6.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; 现在如果要对所需渲染的三角形进行简化，我们可以考虑这个地形块每条边中间的顶点(下图左侧红色点)：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai7.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; 如果将这些红色的顶点剔除，我们可以得到上图右边所示的简化后的网格。误差就在这一步产生。由于红色的顶点被剔除后，原本由红色顶点所表示的地形高度现在变成了两侧黑色顶点插值后的高度。这个高度就是误差。如下图。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai8.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 因此，对于每个节点，我们先计算这个节点所有边中点被删除后所造成的误差，分别记为ΔH1, ΔH2, ΔH3, ΔH4。如果这个节点包含子节点，递归计算子节点的误差，并把四个子节点的误差记为ΔHs1, ΔHs2, ΔHs3, ΔHs4。这个节点的误差就是这八个误差值中的最大值。由于这是一个递归的过程，因此应该把这个过程加到四叉树的生成过程中，并向四叉树的数据结构中加入一个误差变量。如下。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; struct QuadTreeNode<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; QuadTreeNode *Children;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int CenterX,CenterY;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int HalfRange;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float DeltaH;&nbsp; //节点误差值<BR>&nbsp;&nbsp;&nbsp; }</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 下面来看一下地形的具体渲染过程。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 首先，我们位于四叉树的根结点。我们此时考虑根结点的误差，如果这个误差小于一个阈值，直接使用根结点的中心点以及此节点的四个边角点作为顶点渲染一个三角扇形，这个三角扇形就是渲染出来的地形。但是更经常的情况下，根结点的误差值是很大的，因此算法认为要对根结点进行细分，以展现更多细节。于是对于根结点的每个子节点，重复这个步骤，即检查它的误差值是否大于阈值，如果大于，直接渲染这个节点，如果小于，递归细分节点。目前我们的算法伪代码如下。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; procedure DrawTerrain(QuadTreeNode *node)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (node-&gt;DeltaH &gt; k)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (i=0;i&lt;4;i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DrawTerrain(node-&gt;Children[i]);//递归划分<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GraphicsAPI-&gt;DrawPrimitive(node);//以节点的中心点和四个边角点绘制三角扇形 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; }</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 这个伪代码在一个较高的层次上表述了算法的基本思想。然而我们还有许多问题要考虑。其一是目前我们仅仅考虑了地形的细节层次和地形表面起伏程度的关系，但还应该考虑地形块距离视点远近跟地形细节层次的关系。解决这个问题很简单，我们只需在伪代码的条件中加入距离这一因素即可。即把</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (node-&gt;DeltaH &gt; k)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else ...</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 改为：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (node-&gt;DeltaH / d &gt; k)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else ...</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 其中d为节点中心点与视点之间的距离。而事实上，当细节程度与距离的平方成反比时，能够减少更多的三角形，而且视觉效果更好，只要阈值k设置得当，根本感觉不出地形因为视点的移动而发生几何形变。因此，我们最终的条件式为：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; node-&gt;DeltaH / d<SUP>2</SUP> &gt; k</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 还有一个很重要的问题，就是这个算法所产生的地形会因为节点之间细节层次的不同而产生裂缝。下图说明了裂缝的产生原因。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai9.gif" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 有两个方法可以解决这个问题，一个方法是删除左侧节点中产生裂缝的顶点，使两条边能够重合。另一种方法是人为地在右侧地形块中插入一条边，这条边连接中心点和造成裂缝的顶点，从而消除裂缝。在渲染地形时，可以采取下面的办法避免裂缝的产生：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 1.在预处理阶段，为所有顶点创建一个标记数组，标记以该顶点为中心点的节点在某一帧是否被细分。如果被细分则标记为1，否则标记0。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 2.从根节点开始，以广度优先的顺序遍历四叉树，使用之前提出的条件式判断节点是否需要分割。如果公式表明需要分割，并且与节点相邻的四个节点的中心点都被标记为1，那么把这个节点及其四个子节点的标记设为1，并递归细分这个节点。否则，将这个节点的标记设为1，把这个节点的四个子节点的标记设为0，然后采用下面的方法绘制这个地形块：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1)将节点的中心顶点和四个边角点添加到即将绘制的三角扇形列表中。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2)依次检查与四条边相邻的节点的标记数组，如果相应的标记为1，那么将该点添加到三角扇形的顶点列表中，否则跳过该点。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3)绘制三角扇形。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 我们最终的伪代码如下。</SPAN></P><DIV align=center><TABLE id=table1 width="72%" border=1><TBODY><TR><TD><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><STRONG></STRONG><SPAN style="FONT-SIZE: 10.5pt"><STRONG>bool</STRONG> IsNodeInFrustum(QuadTreeNode *node)</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">{</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; <STRONG>return</STRONG> (node-&gt;BoudingSphere in frustum);</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">}</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><STRONG></STRONG><SPAN style="FONT-SIZE: 10.5pt"><STRONG>bool</STRONG> NeighbourIsValid(QuadTreeNode *node)</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">{</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; <STRONG>return</STRONG> (all four neighbours of node are identified as 1)</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">}</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt"><STRONG>void</STRONG> RenderTerrain()</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">{</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; list&lt;QuadTreeNode *&gt;next,current,draw;</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; <STRONG>int</STRONG> level =0;<BR>&nbsp;&nbsp; current.push_back(root);<BR>&nbsp;&nbsp; <STRONG>while</STRONG> (current.size()!=0)</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; {</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>for</STRONG> <STRONG>each</STRONG> thisNode in current</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>if</STRONG> (!IsNodeInFrustum(thisNode))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>continue</STRONG>;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>if</STRONG> (level == MaxResolution)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; draw.push_back(thisNode);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>else</STRONG></SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>if</STRONG> (thisNode-&gt;DeltaH/(distance*distance) &gt; k</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; NeighbourIsValid(thisNode) )</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetFlag(thisNode,1); </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>for</STRONG> j= 1 <STRONG>to</STRONG> 4</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; next.push_back(thisNode-&gt;Children[j]);</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetFlag(thisNode-&gt;Children[j],1)</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>else</STRONG></SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetFlag(thisNode,1);&nbsp; </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <STRONG>for</STRONG> j= 1 <STRONG>to</STRONG> 4</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; draw.push_back(thisNode-&gt;Children[j]);</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetFlag(thisNode-&gt;Children[j],0);</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SwapList(current,next);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; next.clear();</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; level++;</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; }</SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; GraphicsAPI-&gt;DrawPrimitives(draw);&nbsp;&nbsp; </SPAN></FONT></P><P style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 18px"><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">}</SPAN></FONT></P></TD></TR></TBODY></TABLE></DIV><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 另外，一个重要的优化是利用硬件的缓冲区或顶点数组(对于不支持顶点缓冲的硬件而言)。因为地形无论怎样简化，顶点数据总是固定不变的。我们在每一帧动态产生的仅仅是顶点索引，因此我们有必要实现将地形的所有顶点数据输入到顶点缓冲中，然后在渲染时一次性将所有的索引传给显卡，以提高速度。实验表明，使用顶点缓冲比直接使用glBegin/glEnd绘制图形要快5倍以上。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 以上讲述了如何做到实时地渲染大型地形。主要应用了LOD和VFC两种手段来精简三角形数量。然而VFC只能剔除不在视见体内的图形，而对于在视见体内但被其他更近的物体遮挡的情况却无能为力。如果要实现地形的自遮挡剔除，地平线算法是一个好的选择。然而当你的场景不仅仅是包含地形时，地平线算法也只能处理地形的自遮挡情况。因为地平线算法只对2.5D的地图(即在XZ平面上无重合投影的场景)有效。对于完全3D场景，地平线并不能很好的工作。所以当你在引擎中使用地形时，可以考虑将地形分块后放入场景的管理树中，如BSP或Octree等。然后根据引擎的性质使用入口(Portal)、PVS或者遮挡测试(Occlusion Culling)等方法进行遮挡剔除。值得强调的是，遮挡测试是一个非常灵活的实时的剔除算法，且无需任何预计算过程。但要想有效的实现它并不是一件容易的事。我曾将地形分块后使用遮挡剔除来完成地形的自遮挡，但是渲染速度不但没有提升，反而有轻微的下降。因此如果要使用遮挡剔除的话必须和引擎结合起来统一进行遮挡测试，才有可能提高效率。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 现在你应该了解了基本的地形实时渲染方法。要想让地形的外观更加真实，我们还需要更多的工作。我们需要为地形加上纹理贴图和光照。首先考虑地形的光照。由于地形的多边形网格是实时产生的，它会随着视点的移动而变化，因此如果你直接使用OpenGL内置的顶点光照，你会得到极度不稳定的光照效果。你会看到地形表面会因为你的移动而不断跳动。因此我们必须使用其他的光照方法来避免这个问题。我们想到了光照贴图。光照贴图是一个游戏中常用的光照技术。它是一个覆盖了场景中所有多边形的贴图。通过给贴图赋值，我们可以得到多边形表面复杂的光照效果。使用好的算法计算出来的光照贴图可以模拟极度逼真的光影效果。它给我们带来的视觉享受远远地超过了OpenGL的内置光照。有关光照贴图的计算可以参考我翻译的一篇文章：</SPAN><SPAN style="FONT-SIZE: 10.5pt"><A href="http://program.stedu.net/showArticle.asp?index=68"><FONT color=#0022cc>辐射度算法(Radiosity)</FONT></A></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; <IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai11.jpg" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=left><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp; 你可以简单地为地形覆盖上单一的纹理，这看起来些许增加了地形的真实性：</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px" align=center><SPAN style="FONT-SIZE: 10.5pt"><IMG src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai12.jpg" border=0></SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 在上图中，我们创建了一个地形，并运用了一个重复的纹理。这个过程让地形的无论哪一个区域看起来都是一样的（例如都是草地）。这显然不太真实，也过于乏味。或许你会创建了一幅超大的图片，以拉伸覆盖的方式映射到地形表面。这样做的后果是内存开销过于庞大，这样做也很会受到硬件的限制。因此我们应该使用一种更好的纹理贴图方式，纹理索引贴图。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 纹理索引贴图对三个可重复的纹理进行索引贴图。所谓索引贴图，就是对三个可重复纹理进行索引，以决定地形的哪些区域需要使用哪些纹理的混合来贴图。因为对于任意的贴图，都由一组包含3个颜色通道（即R、G、B）的像素组成。用于索引的贴图的像素并不表示地形的某个区域的具体颜色，而是表示地形的某个区域用何种具体的纹理贴图。因为具体的纹理细节存储在这三个可重复的纹理中，因此索引贴图的贴图方式也为拉伸到地形表面，但它的分辨率可以大大降低。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 纹理索引贴图的工作方式如下：对于地形投影到屏幕上的像素，查找该像素所映射到索引贴图上的像素。然后根据这一像素R、G、B分量的不同，决定R、G、B分量所代表的具体纹理贴图的混合因子。根据这个混合因子混合三个可重复贴图后，将混合得到的最终颜色值输出到屏幕上。</SPAN></P><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 例如，令索引贴图的R分量代表沙滩的纹理，G分量代表草地，B分量代表岩石。如果索引贴图上一个像素的值是(0,255,0)，即绿色，则这个像素所对应的地形区域的具体纹理就为草地。如果该像素颜色值是(127,127,0)，即黄色，则该像素所对应的地形区域的纹理为草地和沙滩的混合，看起来既有草，又有沙。又如下图显示了一个样本索引贴图，以及使用该贴图索引纹理之后的渲染效果。</SPAN></P><DIV align=center><TABLE id=table2 width="20%" border=0><TBODY><TR><TD align=middle><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt"><IMG height=238 src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai13.jpg" width=236 border=0></SPAN></P></TD><TD align=middle><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt"><IMG height=242 src="http://www.graphixer.com.cn/ArtImages/TerrainQuad/Terrai14.jpg" width=327 border=0></SPAN></P></TD></TR><TR><TD align=middle><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">索引贴图（R=沙滩，G=草地,B=岩石）</SPAN></P></TD><TD align=middle><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">渲染效果</SPAN></P></TD></TR></TBODY></TABLE></DIV><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 原理很简单，下面讲解一下具体的实现过程。首先，我们准备4个纹理，其中1个纹理索引贴图，它将被拉伸覆盖整个地形，然后3张细节贴图，并将它们绑定到相应的纹理通道上。然后使用Vertex Shader为每个顶点自动计算索引贴图的纹理坐标，在Fragment Shader里，对索引贴图进行纹理查找，使用查找得到的颜色值的RGB颜色信息混合3张细节贴图，得到当前像素的颜色。最后还应该把这个颜色和光照贴图中的值相乘，得到最终的结果。下面是相关的Shader代码，使用GLSL编写。</SPAN></P><DIV align=center><TABLE id=table3 width="68%" border=1><TBODY><TR><TD><FONT face="Courier New"><STRONG><SPAN style="FONT-SIZE: 10.5pt">Vertex Shader:</SPAN></STRONG></FONT> <P><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">uniform float TexInc;&nbsp;&nbsp; //纹理缩放值,用于查找索引纹理<BR>void main()<BR>{<BR>&nbsp; gl_TexCoord[6] = gl_Vertex;<BR>&nbsp; gl_TexCoord[0] = gl_MultiTexCoord0;<BR>&nbsp; gl_TexCoord[2] = TexInc*vec4(gl_Vertex.xz,0.0,0.0);<BR>&nbsp; gl_Position = ftransform();<BR>}</SPAN></FONT></P></TD></TR><TR><TD><FONT face="Courier New"><STRONG><SPAN style="FONT-SIZE: 10.5pt">Fragment Shader:</SPAN></STRONG></FONT> <P><FONT face="Courier New"><SPAN style="FONT-SIZE: 10.5pt">uniform sampler2D IndexMap;<BR>uniform sampler2D LightMap;<BR>uniform sampler2D texR,texG,texB,texA;<BR>void main()<BR>{<BR>&nbsp; vec4 idx,lm,r,g,b,color;<BR>&nbsp; idx = texture2D(IndexMap,gl_TexCoord[0].xy); //索引值<BR>&nbsp; lm = texture2D(LightMap,gl_TexCoord[0].xy);&nbsp; //光照度<BR>&nbsp; r = texture2D(texR,gl_TexCoord[2].xy);&nbsp;&nbsp; //R通道纹理<BR>&nbsp; g = texture2D(texG,gl_TexCoord[2].xy);&nbsp;&nbsp; //G通道纹理<BR>&nbsp; b = texture2D(texB,gl_TexCoord[2].xy);&nbsp;&nbsp; //B通道纹理 <BR>&nbsp; color = lm*(idx.x*r + idx.y*g+idx.z*b);&nbsp; //混合颜色<BR>&nbsp; gl_FragColor = color;<BR>}</SPAN></FONT></P></TD></TR></TBODY></TABLE></DIV><P style="MARGIN-TOP: 9px; MARGIN-BOTTOM: 9px; LINE-HEIGHT: 18px"><SPAN style="FONT-SIZE: 10.5pt">&nbsp;&nbsp;&nbsp; 最后，如果你对本文有不解之处，欢迎和我共同讨论。</SPAN></P></SPAN></CITE>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=974</link><title><![CDATA[Linux环境进程间通信-共享内存[收集]]]></title><author>admin</author><category>Linux学习</category><pubDate>2008-6-6 15:54:59</pubDate><guid>http://h31home.com/h312005/blogview.asp?ID=974</guid><description><![CDATA[<P>共享内存可以说是最有用的进程间通信方式，也是最快的IPC形式。两个不同进程A、B共享内存的意思是，同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新，反之亦然。由于多个进程共享同一块内存区域，必然需要某种同步机制，互斥锁和信号量都可以。<BR>采用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次数据[1]：一次从输入文件到共享内存区，另一次从共享内存区到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域。而是保持共享区域，直到通信完毕为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。</P><P>Linux的2.2.x内核支持多种共享内存方式，如mmap()系统调用，Posix共享内存，以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存，但还没实现Posix共享内存，本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。</P><P>一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面</P><P>1、page cache及swap cache中页面的区分：一个被访问文件的物理页面都驻留在page cache或swap cache中，一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ，它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。</P><P>2、文件与address_space结构的对应：一个具体的文件在打开后，内核会在内存中为之建立一个struct inode结构，其中的i_mapping域指向一个address_space结构。这样，一个文件就对应一个address_space结构，一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此，当要寻址某个数据时，很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。</P><P>3、进程调用mmap()时，只是在进程空间内新增了一块相应大小的缓冲区，并设置了相应的访问标识，但并没有建立进程空间到物理页面的映射。因此，第一次访问该空间时，会引发一个缺页异常。</P><P>4、对于共享内存映射情况，缺页异常处理程序首先在swap cache中寻找目标页（符合address_space以及偏移量的物理页），如果找到，则直接返回地址；如果没有找到，则判断该页是否在交换区(swap area)，如果在，则执行一个换入操作；如果上述两种情况都不满足，处理程序将分配新的物理页面，并把它插入到page cache中。进程最终将更新进程页表。 <BR>注：对于映射普通文件情况（非共享映射），缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到，则说明文件数据还没有读入内存，处理程序会从磁盘读入相应的页面，并返回相应地址，同时，进程页表也会更新。 </P><P>5、所有进程在映射同一个共享内存区域时，情况都一样，在建立线性地址与物理地址之间的映射之后，不论进程各自的返回地址如何，实际访问的必然是同一个共享内存区域对应的物理页面。 <BR>注：一个共享内存区域可以看作是特殊文件系统shm中的一个文件，shm的安装点在交换区上。 </P><P>上面涉及到了一些数据结构，围绕数据结构理解问题会容易一些。</P><P>&nbsp;</P><P>二、mmap()及其相关系统调用</P><P>mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后，进程可以向访问普通内存一样对文件进行访问，不必再调用read()，write（）等操作。</P><P>注：实际上，mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式，进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的，当然mmap()实现共享内存也是其主要应用之一。</P><P>1、mmap()系统调用形式如下：</P><P><BR>void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )&nbsp;&nbsp;&nbsp;&nbsp; <BR>void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )</P><P>参数fd为即将映射到进程空间的文件描述字，一般由open()返回，同时，fd可以指定为-1，此时须指定flags参数中的MAP_ANON，表明进行的是匿名映射（不涉及具体的文件名，避免了文件的创建及打开，很显然只能用于具有亲缘关系的进程间通信）。len是映射到调用进程地址空间的字节数，它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或：PROT_READ（可读） , PROT_WRITE （可写）, PROT_EXEC （可执行）, PROT_NONE（不可访问）。flags由以下几个常值指定：MAP_SHARED , MAP_PRIVATE , MAP_FIXED，其中，MAP_SHARED , MAP_PRIVATE必选其一，而MAP_FIXED则不推荐使用。offset参数一般设为0，表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址，一般被指定一个空指针，此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址，进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数，读者可参考mmap()手册页获得进一步的信息。 </P><P>2、系统调用mmap()用于共享内存的两种方式：</P><P><BR>（1）使用普通文件提供的内存映射：适用于任何进程之间；此时，需要打开或创建一个文件，然后再调用mmap()；典型调用代码如下：&nbsp;&nbsp; </P><P>fd=open(name, flag, mode);&nbsp;&nbsp;&nbsp; <BR>if(fd&lt;0)&nbsp;&nbsp;&nbsp; <BR>&nbsp; ...&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp; <BR>ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);&nbsp;&nbsp;&nbsp;&nbsp; <BR>fd=open(name, flag, mode);<BR>if(fd&lt;0)<BR>&nbsp; ...<BR>&nbsp; <BR>ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);</P><P>通过mmap()实现共享内存的通信方式有许多特点和要注意的地方，我们将在范例中进行具体说明。 </P><P>（2）使用特殊文件提供匿名内存映射：适用于具有亲缘关系的进程之间；由于父子进程特殊的亲缘关系，在父进程中先调用mmap()，然后调用fork()。那么在调用fork()之后，子进程继承父进程匿名映射后的地址空间，同样也继承mmap()返回的地址，这样，父子进程就可以通过映射区域进行通信了。注意，这里不是一般的继承关系。一般来说，子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址，却由父子进程共同维护。 <BR>对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时，不必指定具体的文件，只要设置相应的标志即可，参见范例2。 </P><P>3、系统调用munmap()</P><P><BR>int munmap( void * addr, size_t len )&nbsp;&nbsp;&nbsp;&nbsp; <BR>int munmap( void * addr, size_t len )</P><P>该调用在进程地址空间中解除一个映射关系，addr是调用mmap()时返回的地址，len是映射区的大小。当映射关系解除后，对原来映射地址的访问将导致段错误发生。 </P><P>4、系统调用msync()</P><P><BR>int msync ( void * addr , size_t len, int flags)&nbsp;&nbsp;&nbsp;&nbsp; <BR>int msync ( void * addr , size_t len, int flags)</P><P>一般说来，进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中，往往在调用munmap（）后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。 </P><P><BR>三、mmap()范例</P><P>下面将给出使用mmap()的两个范例：范例1给出两个进程通过映射普通文件实现共享内存通信；范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方，下面是通过mmap（）映射普通文件实现进程间的通信的范例，我们通过该范例来说明mmap()实现共享内存的特点及注意事项。</P><P>范例1：两个进程通过映射普通文件实现共享内存通信</P><P>范例1包含两个子程序：map_normalfile1.c及map_normalfile2.c。编译两个程序，可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件，把该文件映射到进程的地址空间，并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间，然后对映射后的地址空间执行读操作。这样，两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。</P><P>下面是两个程序代码：</P><P>/*-------------map_normalfile1.c-----------*/&nbsp;&nbsp; <BR>#include &lt;sys/mman.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/types.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;fcntl.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;unistd.h&gt;&nbsp;&nbsp;&nbsp; <BR>typedef struct{&nbsp;&nbsp;&nbsp; <BR>&nbsp; char name[4];&nbsp;&nbsp;&nbsp; <BR>&nbsp; int&nbsp; age;&nbsp;&nbsp;&nbsp; <BR>}people;&nbsp;&nbsp;&nbsp; <BR>main(int argc, char** argv) // map a normal file as shared mem:&nbsp;&nbsp;&nbsp; <BR>{&nbsp;&nbsp;&nbsp; <BR>&nbsp; int fd,i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; people *p_map;&nbsp;&nbsp;&nbsp; <BR>&nbsp; char temp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);&nbsp;&nbsp;&nbsp; <BR>&nbsp; lseek(fd,sizeof(people)*5-1,SEEK_SET);&nbsp;&nbsp;&nbsp; <BR>&nbsp; write(fd,"",1);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );&nbsp;&nbsp;&nbsp; <BR>&nbsp; close( fd );&nbsp;&nbsp;&nbsp; <BR>&nbsp; temp = 'a';&nbsp;&nbsp;&nbsp; <BR>&nbsp; for(i=0; i&lt;10; i++)&nbsp;&nbsp;&nbsp; <BR>&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; temp += 1;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; memcpy( ( *(p_map+i) ).name, &amp;temp,2 );&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; ( *(p_map+i) ).age = 20+i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf(" initialize over \n ")；&nbsp;&nbsp;&nbsp; <BR>&nbsp; sleep(10);&nbsp;&nbsp;&nbsp; <BR>&nbsp; munmap( p_map, sizeof(people)*10 );&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf( "umap ok \n" );&nbsp;&nbsp;&nbsp; <BR>}&nbsp;&nbsp;&nbsp; <BR>/*-------------map_normalfile1.c-----------*/<BR>#include &lt;sys/mman.h&gt;<BR>#include &lt;sys/types.h&gt;<BR>#include &lt;fcntl.h&gt;<BR>#include &lt;unistd.h&gt;<BR>typedef struct{<BR>&nbsp; char name[4];<BR>&nbsp; int&nbsp; age;<BR>}people;<BR>main(int argc, char** argv) // map a normal file as shared mem:<BR>{<BR>&nbsp; int fd,i;<BR>&nbsp; people *p_map;<BR>&nbsp; char temp;<BR>&nbsp; <BR>&nbsp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);<BR>&nbsp; lseek(fd,sizeof(people)*5-1,SEEK_SET);<BR>&nbsp; write(fd,"",1);<BR>&nbsp; <BR>&nbsp; p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );<BR>&nbsp; close( fd );<BR>&nbsp; temp = 'a';<BR>&nbsp; for(i=0; i&lt;10; i++)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; temp += 1;<BR>&nbsp;&nbsp;&nbsp; memcpy( ( *(p_map+i) ).name, &amp;temp,2 );<BR>&nbsp;&nbsp;&nbsp; ( *(p_map+i) ).age = 20+i;<BR>&nbsp; }<BR>&nbsp; printf(" initialize over \n ")；<BR>&nbsp; sleep(10);<BR>&nbsp; munmap( p_map, sizeof(people)*10 );<BR>&nbsp; printf( "umap ok \n" );<BR>}</P><P><BR>/*-------------map_normalfile2.c-----------*/&nbsp;&nbsp; <BR>#include &lt;sys/mman.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/types.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;fcntl.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;unistd.h&gt;&nbsp;&nbsp;&nbsp; <BR>typedef struct{&nbsp;&nbsp;&nbsp; <BR>&nbsp; char name[4];&nbsp;&nbsp;&nbsp; <BR>&nbsp; int&nbsp; age;&nbsp;&nbsp;&nbsp; <BR>}people;&nbsp;&nbsp;&nbsp; <BR>main(int argc, char** argv)&nbsp; // map a normal file as shared mem:&nbsp;&nbsp;&nbsp; <BR>{&nbsp;&nbsp;&nbsp; <BR>&nbsp; int fd,i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; people *p_map;&nbsp;&nbsp;&nbsp; <BR>&nbsp; fd=open( argv[1],O_CREAT|O_RDWR,00777 );&nbsp;&nbsp;&nbsp; <BR>&nbsp; p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);&nbsp;&nbsp;&nbsp; <BR>&nbsp; for(i = 0;i&lt;10;i++)&nbsp;&nbsp;&nbsp; <BR>&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );&nbsp;&nbsp;&nbsp; <BR>&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp; munmap( p_map,sizeof(people)*10 );&nbsp;&nbsp;&nbsp; <BR>}&nbsp;&nbsp;&nbsp; <BR>/*-------------map_normalfile2.c-----------*/<BR>#include &lt;sys/mman.h&gt;<BR>#include &lt;sys/types.h&gt;<BR>#include &lt;fcntl.h&gt;<BR>#include &lt;unistd.h&gt;<BR>typedef struct{<BR>&nbsp; char name[4];<BR>&nbsp; int&nbsp; age;<BR>}people;<BR>main(int argc, char** argv)&nbsp; // map a normal file as shared mem:<BR>{<BR>&nbsp; int fd,i;<BR>&nbsp; people *p_map;<BR>&nbsp; fd=open( argv[1],O_CREAT|O_RDWR,00777 );<BR>&nbsp; p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);<BR>&nbsp; for(i = 0;i&lt;10;i++)<BR>&nbsp; {<BR>&nbsp; printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );<BR>&nbsp; }<BR>&nbsp; munmap( p_map,sizeof(people)*10 );<BR>}</P><P>&nbsp;</P><P>map_normalfile1.c首先定义了一个people数据结构，（在这里采用数据结构的方式是因为，共享内存区的数据往往是有固定格式的，这由通信的各个进程决定，采用结构的方式有普遍代表性）。map_normfile1首先打开或创建一个文件，并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始，设置了10个people结构。然后，进程睡眠10秒钟，等待其他进程映射同一个文件，最后解除映射。</P><P>map_normfile2.c只是简单的映射一个文件，并以people数据结构的格式从mmap()返回的地址处读取10个people结构，并输出读取的值，然后解除映射。</P><P>分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后，在一个终端上先运行./map_normalfile2 /tmp/test_shm，程序输出结果如下：</P><P>initialize over<BR>umap ok</P><P>&nbsp;</P><P>在map_normalfile1输出initialize over 之后，输出umap ok之前，在另一个终端上运行map_normalfile2 /tmp/test_shm，将会产生如下输出(为了节省空间，输出结果为稍作整理后的结果)：</P><P><BR>引用<BR>name: b&nbsp; age 20;&nbsp; name: c&nbsp; age 21;&nbsp; name: d&nbsp; age 22;&nbsp; name: e&nbsp; age 23;&nbsp; name: f&nbsp; age 24;<BR>name: g&nbsp; age 25;&nbsp; name: h&nbsp; age 26;&nbsp; name: I&nbsp; age 27;&nbsp; name: j&nbsp; age 28;&nbsp; name: k&nbsp; age 29;</P><P>&nbsp;</P><P><BR>在map_normalfile1 输出umap ok后，运行map_normalfile2则输出如下结果：</P><P><BR>引用<BR>name: b&nbsp; age 20;&nbsp; name: c&nbsp; age 21;&nbsp; name: d&nbsp; age 22;&nbsp; name: e&nbsp; age 23;&nbsp; name: f&nbsp; age 24;<BR>name:&nbsp; age 0;&nbsp; name:&nbsp; age 0;&nbsp; name:&nbsp; age 0;&nbsp; name:&nbsp; age 0;&nbsp; name:&nbsp; age 0;</P><P>&nbsp;</P><P><BR>从程序的运行结果中可以得出的结论 </P><P>1、 最终被映射文件的内容的长度不会超过文件本身的初始大小，即映射不能改变文件的大小；</P><P>2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小，但不完全受限于文件大小。打开文件被截短为5个people结构大小，而在map_normalfile1中初始化了10个people数据结构，在恰当时候（map_normalfile1输出initialize over 之后，输出umap ok之前）调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值，后面将给出详细讨论。 <BR>注：在linux中，内存的保护是以页为基本单位的，即使被映射文件只有一个字节大小，内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时，进程可以对从mmap()返回地址开始的一个页面大小进行访问，而不会出错；但是，如果对一个页面以外的地址空间进行访问，则导致错误发生，后面将进一步描述。因此，可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。 </P><P>3、 文件一旦被映射后，调用mmap()的进程对返回地址的访问是对某一内存区域的访问，暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义，只有在调用了munmap()后或者msync()时，才把内存中的相应内容写回磁盘文件，所写内容仍然不能超过文件的大小。</P><P>范例2：父子进程通过匿名映射实现共享内存</P><P>#include &lt;sys/mman.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/types.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;fcntl.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;unistd.h&gt;&nbsp;&nbsp;&nbsp; <BR>typedef struct{&nbsp;&nbsp;&nbsp; <BR>&nbsp; char name[4];&nbsp;&nbsp;&nbsp; <BR>&nbsp; int&nbsp; age;&nbsp;&nbsp;&nbsp; <BR>}people;&nbsp;&nbsp;&nbsp; <BR>main(int argc, char** argv)&nbsp;&nbsp;&nbsp; <BR>{&nbsp;&nbsp;&nbsp; <BR>&nbsp; int i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; people *p_map;&nbsp;&nbsp;&nbsp; <BR>&nbsp; char temp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);&nbsp;&nbsp;&nbsp; <BR>&nbsp; if(fork() == 0)&nbsp;&nbsp;&nbsp; <BR>&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; sleep(2);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; for(i = 0;i&lt;5;i++)&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; (*p_map).age = 100;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; munmap(p_map,sizeof(people)*10); //实际上，进程终止时，会自动解除映射。&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; exit();&nbsp;&nbsp;&nbsp; <BR>&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp; temp = 'a';&nbsp;&nbsp;&nbsp; <BR>&nbsp; for(i = 0;i&lt;5;i++)&nbsp;&nbsp;&nbsp; <BR>&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; temp += 1;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; memcpy((*(p_map+i)).name, &amp;temp,2);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; (*(p_map+i)).age=20+i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp; sleep(5);&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf( "parent read: the first people,s age is %d\n",(*p_map).age );&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf("umap\n");&nbsp;&nbsp;&nbsp; <BR>&nbsp; munmap( p_map,sizeof(people)*10 );&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf( "umap ok\n" );&nbsp;&nbsp;&nbsp; <BR>}&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/mman.h&gt;<BR>#include &lt;sys/types.h&gt;<BR>#include &lt;fcntl.h&gt;<BR>#include &lt;unistd.h&gt;<BR>typedef struct{<BR>&nbsp; char name[4];<BR>&nbsp; int&nbsp; age;<BR>}people;<BR>main(int argc, char** argv)<BR>{<BR>&nbsp; int i;<BR>&nbsp; people *p_map;<BR>&nbsp; char temp;<BR>&nbsp; p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);<BR>&nbsp; if(fork() == 0)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; sleep(2);<BR>&nbsp;&nbsp;&nbsp; for(i = 0;i&lt;5;i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);<BR>&nbsp;&nbsp;&nbsp; (*p_map).age = 100;<BR>&nbsp;&nbsp;&nbsp; munmap(p_map,sizeof(people)*10); //实际上，进程终止时，会自动解除映射。<BR>&nbsp;&nbsp;&nbsp; exit();<BR>&nbsp; }<BR>&nbsp; temp = 'a';<BR>&nbsp; for(i = 0;i&lt;5;i++)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; temp += 1;<BR>&nbsp;&nbsp;&nbsp; memcpy((*(p_map+i)).name, &amp;temp,2);<BR>&nbsp;&nbsp;&nbsp; (*(p_map+i)).age=20+i;<BR>&nbsp; }<BR>&nbsp; sleep(5);<BR>&nbsp; printf( "parent read: the first people,s age is %d\n",(*p_map).age );<BR>&nbsp; printf("umap\n");<BR>&nbsp; munmap( p_map,sizeof(people)*10 );<BR>&nbsp; printf( "umap ok\n" );<BR>}</P><P><BR>考察程序的输出结果，体会父子进程匿名共享内存：</P><P><BR>引用<BR>child read: the 1 people's age is 20<BR>child read: the 2 people's age is 21<BR>child read: the 3 people's age is 22<BR>child read: the 4 people's age is 23<BR>child read: the 5 people's age is 24<BR>parent read: the first people,s age is 100<BR>umap<BR>umap ok</P><P>&nbsp;</P><P>四、对mmap()返回地址的访问</P><P>前面对范例运行结构的讨论中已经提到，linux采用的是页式管理机制。对于用mmap()映射普通文件来说，进程会在自己的地址空间新增一块空间，空间大小由mmap()的len参数指定，注意，进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说，能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始，能够有效访问的地址空间大小。超过这个空间大小，内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明：</P><P>&nbsp;</P><P><BR>注意：文件被映射部分而不是整个文件决定了进程能够访问的空间大小，另外，如果指定文件的偏移部分，一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例：</P><P>#include &lt;sys/mman.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/types.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;fcntl.h&gt;&nbsp;&nbsp;&nbsp; <BR>#include &lt;unistd.h&gt;&nbsp;&nbsp;&nbsp; <BR>typedef struct{&nbsp;&nbsp;&nbsp; <BR>&nbsp; char name[4];&nbsp;&nbsp;&nbsp; <BR>&nbsp; int&nbsp; age;&nbsp;&nbsp;&nbsp; <BR>}people;&nbsp;&nbsp;&nbsp; <BR>main(int argc, char** argv)&nbsp;&nbsp;&nbsp; <BR>{&nbsp;&nbsp;&nbsp; <BR>&nbsp; int fd,i;&nbsp;&nbsp;&nbsp; <BR>&nbsp; int pagesize,offset;&nbsp;&nbsp;&nbsp; <BR>&nbsp; people *p_map;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; pagesize = sysconf(_SC_PAGESIZE);&nbsp;&nbsp;&nbsp; <BR>&nbsp; printf("pagesize is %d\n",pagesize);&nbsp;&nbsp;&nbsp; <BR>&nbsp; fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);&nbsp;&nbsp;&nbsp; <BR>&nbsp; lseek(fd,pagesize*2-100,SEEK_SET);&nbsp;&nbsp;&nbsp; <BR>&nbsp; write(fd,"",1);&nbsp;&nbsp;&nbsp; <BR>&nbsp; offset = 0;&nbsp; //此处offset = 0编译成版本1；offset = pagesize编译成版本2&nbsp;&nbsp;&nbsp; <BR>&nbsp; p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);&nbsp;&nbsp;&nbsp; <BR>&nbsp; close(fd);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp; for(i = 1; i&lt;10; i++)&nbsp;&nbsp;&nbsp; <BR>&nbsp; {&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; printf("access page %d over\n",i);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; printf("access page %d edge over, now begin to access page %d\n",i, i+1);&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i)).age = 100;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; printf("access page %d over\n",i+1);&nbsp;&nbsp;&nbsp; <BR>&nbsp; }&nbsp;&nbsp;&nbsp; <BR>&nbsp; munmap(p_map,sizeof(people)*10);&nbsp;&nbsp;&nbsp; <BR>}&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>#include &lt;sys/mman.h&gt;<BR>#include &lt;sys/types.h&gt;<BR>#include &lt;fcntl.h&gt;<BR>#include &lt;unistd.h&gt;<BR>typedef struct{<BR>&nbsp; char name[4];<BR>&nbsp; int&nbsp; age;<BR>}people;<BR>main(int argc, char** argv)<BR>{<BR>&nbsp; int fd,i;<BR>&nbsp; int pagesize,offset;<BR>&nbsp; people *p_map;<BR>&nbsp; <BR>&nbsp; pagesize = sysconf(_SC_PAGESIZE);<BR>&nbsp; printf("pagesize is %d\n",pagesize);<BR>&nbsp; fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);<BR>&nbsp; lseek(fd,pagesize*2-100,SEEK_SET);<BR>&nbsp; write(fd,"",1);<BR>&nbsp; offset = 0;&nbsp; //此处offset = 0编译成版本1；offset = pagesize编译成版本2<BR>&nbsp; p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);<BR>&nbsp; close(fd);<BR>&nbsp; <BR>&nbsp; for(i = 1; i&lt;10; i++)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;<BR>&nbsp;&nbsp;&nbsp; printf("access page %d over\n",i);<BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;<BR>&nbsp;&nbsp;&nbsp; printf("access page %d edge over, now begin to access page %d\n",i, i+1);<BR>&nbsp;&nbsp;&nbsp; (*(p_map+pagesize/sizeof(people)*i)).age = 100;<BR>&nbsp;&nbsp;&nbsp; printf("access page %d over\n",i+1);<BR>&nbsp; }<BR>&nbsp; munmap(p_map,sizeof(people)*10);<BR>}</P><P><BR>如程序中所注释的那样，把程序编译成两个版本，两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间（大小为：pagesize*2-99），版本1的被映射部分是整个文件，版本2的文件被映射部分是文件大小减去一个页面后的剩余部分，不到一个页面大小(大小为：pagesize-99)。程序中试图访问每一个页面边界，两个版本都试图在进程空间中映射pagesize*3的字节数。</P><P>版本1的输出结果如下：</P><P><BR>引用<BR>pagesize is 4096<BR>access page 1 over<BR>access page 1 edge over, now begin to access page 2<BR>access page 2 over<BR>access page 2 over<BR>access page 2 edge over, now begin to access page 3<BR>Bus error&nbsp;&nbsp;&nbsp; //被映射文件在进程空间中覆盖了两个页面，此时，进程试图访问第三个页面</P><P>&nbsp;</P><P><BR>版本2的输出结果如下：</P><P><BR>引用<BR>pagesize is 4096<BR>access page 1 over<BR>access page 1 edge over, now begin to access page 2<BR>Bus error&nbsp;&nbsp;&nbsp; //被映射文件在进程空间中覆盖了一个页面，此时，进程试图访问第二个页面</P><P>&nbsp;</P><P><BR>结论：采用系统调用mmap()实现进程间通信是很方便的，在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容，可以参考一下相关重要数据结构来加深理解。在本专题的后面部分，将介绍系统v共享内存的实现。</P><P>//-----------------------------------------------------------------------------------------------------</P><P>在共享内存（上）中，主要围绕着系统调用mmap()进行讨论的，本部分将讨论系统V共享内存，并通过实验结果对比来阐述两者的异同。系统V共享内存指的是把所有共享数据放在共享内存区域（IPC shared memory region），任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域，用来映射存放共享数据的物理内存页面。<BR>系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说，每个共享内存区域对应特殊文件系统shm中的一个文件（这是通过shmid_kernel结构联系起来的），后面还将阐述。</P><P>1、系统V共享内存原理</P><P>进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方，所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域，并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区，初始化该共享内存区相应的shmid_kernel结构注同时，还将在特殊文件系统shm中，创建并打开一个同名文件，并在内存中建立起该文件的相应dentry及inode结构，新打开的文件不属于任何一个进程（任何进程都可以访问该共享内存区）。所有这一切都是系统调用shmget完成的。</P><P>注：每一个共享内存区都有一个控制结构struct shmid_kernel，shmid_kernel是共享内存区域中非常重要的一个数据结构，它是存储管理和文件系统结合起来的桥梁，定义如下：</P><P>struct shmid_kernel /* private to t