microsoftxiao

记忆 流逝

2008年9月13日 #

Windows位图文件格式[转]

     摘要: 位图概述位图格式位图文件头结构包含关于类型,大小以及与设备无关的文件层。typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; typedef struct tagBITMA...  阅读全文

posted @ 2008-09-13 13:37 龙巢NET刀 阅读(1273) | 评论 (2)编辑

2008年8月31日 #

Windows堆栈区别[转]

堆和栈的区别 (转贴)

非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥!

堆和栈的区别
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序 
这是一个前辈写的,非常详细 
//main.cpp 
int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 

int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456\0在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 

 


二、堆和栈的理论知识 
2.1申请方式 
stack: 
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
heap: 
需要程序员自己申请,并指明大小,在c中malloc函数 
如p1 = (char *)malloc(10); 
在C++中用new运算符 
如p2 = (char *)malloc(10); 
但是注意p1、p2本身是在栈中的。 


2.2 
申请后系统的响应 
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 

2.3申请大小的限制 
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 


2.4申请效率的比较: 
栈由系统自动分配,速度较快。但程序员是无法控制的。 
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。 

2.5堆和栈中的存储内容 
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 

2.6存取效率的比较 

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的; 
而bbbbbbbbbbb是在编译时就确定的; 
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇编代码 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。 


2.7小结: 
堆和栈的区别可以用如下的比喻来看出: 
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 



windows进程中的内存结构


在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。 

接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。 

首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码: 

#include <stdio.h> 

int g1=0, g2=0, g3=0; 

int main() 

static int s1=0, s2=0, s3=0; 
int v1=0, v2=0, v3=0; 

//打印出各个变量的内存地址 

printf("0x%08x\n",&v1); //打印各本地变量的内存地址 
printf("0x%08x\n",&v2); 
printf("0x%08x\n\n",&v3); 
printf("0x%08x\n",&g1); //打印各全局变量的内存地址 
printf("0x%08x\n",&g2); 
printf("0x%08x\n\n",&g3); 
printf("0x%08x\n",&s1); //打印各静态变量的内存地址 
printf("0x%08x\n",&s2); 
printf("0x%08x\n\n",&s3); 
return 0; 

编译后的执行结果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x004068d0 
0x004068d4 
0x004068d8 

0x004068dc 
0x004068e0 
0x004068e4 

输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。 


├———————┤低端内存区域 
│ …… │ 
├———————┤ 
│ 动态数据区 │ 
├———————┤ 
│ …… │ 
├———————┤ 
│ 代码区 │ 
├———————┤ 
│ 静态数据区 │ 
├———————┤ 
│ …… │ 
├———————┤高端内存区域 


堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码: 

#include <stdio.h> 

void __stdcall func(int param1,int param2,int param3) 

int var1=param1; 
int var2=param2; 
int var3=param3; 
printf("0x%08x\n",¶m1); //打印出各个变量的内存地址 
printf("0x%08x\n",¶m2); 
printf("0x%08x\n\n",¶m3); 
printf("0x%08x\n",&var1); 
printf("0x%08x\n",&var2); 
printf("0x%08x\n\n",&var3); 
return; 

int main() 

func(1,2,3); 
return 0; 

编译后的执行结果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x0012ff68 
0x0012ff6c 
0x0012ff70 


├———————┤<—函数执行时的栈顶(ESP)、低端内存区域 
│ …… │ 
├———————┤ 
│ var 1 │ 
├———————┤ 
│ var 2 │ 
├———————┤ 
│ var 3 │ 
├———————┤ 
│ RET │ 
├———————┤<—“__cdecl”函数返回后的栈顶(ESP) 
│ parameter 1 │ 
├———————┤ 
│ parameter 2 │ 
├———————┤ 
│ parameter 3 │ 
├———————┤<—“__stdcall”函数返回后的栈顶(ESP) 
│ …… │ 
├———————┤<—栈底(基地址 EBP)、高端内存区域 


上图就是函数调用过程中堆栈的样子了。首先,三个参数以从又到左的次序压入堆栈,先压“param3”,再压“param2”,最后压入“param1”;然后压入函数的返回地址(RET),接着跳转到函数地址接着执行(这里要补充一点,介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后,继续压入当前EBP,然后用当前ESP代替EBP。然而,有一篇介绍windows下函数调用的文章中说,在windows下的函数调用也有这一步骤,但根据我的实际调试,并未发现这一步,这还可以从param3和var1之间只有4字节的间隙这点看出来);第三步,将栈顶(ESP)减去一个数,为本地变量分配内存空间,上例中是减去12字节(ESP=ESP-3*4,每个int变量占用4个字节);接着就初始化本地变量的内存空间。由于“__stdcall”调用由被调函数调整堆栈,所以在函数返回前要恢复堆栈,先回收本地变量占用的内存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前压入参数占用的内存(ESP=ESP+3*4),继续执行调用者的代码。参见下列汇编代码: 

;--------------func 函数的汇编代码------------------- 

:00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间 
:00401003 8B442410 mov eax, dword ptr [esp+10] 
:00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
:0040100B 8B542418 mov edx, dword ptr [esp+18] 
:0040100F 89442400 mov dword ptr [esp], eax 
:00401013 8D442410 lea eax, dword ptr [esp+10] 
:00401017 894C2404 mov dword ptr [esp+04], ecx 

……………………(省略若干代码) 

:00401075 83C43C add esp, 0000003C ;恢复堆栈,回收本地变量的内存空间 
:00401078 C3 ret 000C ;函数返回,恢复参数占用的内存空间 
;如果是“__cdecl”的话,这里是“ret”,堆栈将由调用者恢复 

;-------------------函数结束------------------------- 


;--------------主程序调用func函数的代码-------------- 

:00401080 6A03 push 00000003 //压入参数param3 
:00401082 6A02 push 00000002 //压入参数param2 
:00401084 6A01 push 00000001 //压入参数param1 
:00401086 E875FFFFFF call 00401000 //调用func函数 
;如果是“__cdecl”的话,将在这里恢复堆栈,“add esp, 0000000C” 

聪明的读者看到这里,差不多就明白缓冲溢出的原理了。先来看下面的代码: 

#include <stdio.h> 
#include <string.h> 

void __stdcall func() 

char lpBuff[8]="\0"; 
strcat(lpBuff,"AAAAAAAAAAA"); 
return; 

int main() 

func(); 
return 0; 

编译后执行一下回怎么样?哈,“"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。”,“非法操作”喽!"41"就是"A"的16进制的ASCII码了,那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节,算进结尾的\0,那strcat最多只能写入7个"A",但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图,多出来的4个字节正好覆盖了RET的所在的内存空间,导致函数返回到一个错误的内存地址,执行了错误的指令。如果能精心构造这个字符串,使它分成三部分,前一部份仅仅是填充的无意义数据以达到溢出的目的,接着是一个覆盖RET的数据,紧接着是一段shellcode,那只要着个RET地址能指向这段shellcode的第一个指令,那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置,那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令,使得exploit有更强的通用性。 


├———————┤<—低端内存区域 
│ …… │ 
├———————┤<—由exploit填入数据的开始 
│ │ 
│ buffer │<—填入无用的数据 
│ │ 
├———————┤ 
│ RET │<—指向shellcode,或NOP指令的范围 
├———————┤ 
│ NOP │ 
│ …… │<—填入的NOP指令,是RET可指向的范围 
│ NOP │ 
├———————┤ 
│ │ 
│ shellcode │ 
│ │ 
├———————┤<—由exploit填入数据的结束 
│ …… │ 
├———————┤<—高端内存区域 


windows下的动态数据除了可存放在栈中,还可以存放在堆中。了解C++的朋友都知道,C++可以使用new关键字来动态分配内存。来看下面的C++代码: 

#include <stdio.h> 
#include <iostream.h> 
#include <windows.h> 

void func() 

char *buffer=new char[128]; 
char bufflocal[128]; 
static char buffstatic[128]; 
printf("0x%08x\n",buffer); //打印堆中变量的内存地址 
printf("0x%08x\n",bufflocal); //打印本地变量的内存地址 
printf("0x%08x\n",buffstatic); //打印静态变量的内存地址 

void main() 

func(); 
return; 

程序执行结果为: 

0x004107d0 
0x0012ff04 
0x004068c0 

可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC编译器是通过windows下的“堆(heap)”来实现new关键字的内存动态分配。在讲“堆”之前,先来了解一下和“堆”有关的几个API函数: 

HeapAlloc 在堆中申请内存空间 
HeapCreate 创建一个新的堆对象 
HeapDestroy 销毁一个堆对象 
HeapFree 释放申请的内存 
HeapWalk 枚举堆对象的所有内存块 
GetProcessHeap 取得进程的默认堆对象 
GetProcessHeaps 取得进程所有的堆对象 
LocalAlloc 
GlobalAlloc 

当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间: 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,8); 

其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧: 

#pragma comment(linker,"/entry:main") //定义程序的入口 
#include <windows.h> 

_CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf 
/*--------------------------------------------------------------------------- 
写到这里,我们顺便来复习一下前面所讲的知识: 
(*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。 
由函数定义可见,printf的参数个数是可变的,函数内部无法预先知道调用者压入的参数个数,函数只能通过分析第一个参数字符串的格式来获得压入参数的信息,由于这里参数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是__stdcall调用形式,只有一个API例外,那就是wsprintf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个数是可变的缘故。 
---------------------------------------------------------------------------*/ 
void main() 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,0x10); 
char *buff2=HeapAlloc(hHeap,0,0x10); 
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); 
printf=(void *)GetProcAddress(hMsvcrt,"printf"); 
printf("0x%08x\n",hHeap); 
printf("0x%08x\n",buff); 
printf("0x%08x\n\n",buff2); 

执行结果为: 

0x00130000 
0x00133100 
0x00133118 

hHeap的值怎么和那个buff的值那么接近呢?其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构,这个结构中存放着一些有关进程的重要信息,其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址,而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的,同一时刻只能有一个线程访问堆中的数据,当多个线程同时有访问要求时,只能排队等待,这样便造成程序执行效率下降。 

最后来说说内存中的数据对齐。所位数据对齐,是指数据所在的内存地址必须是该数据长度的整数倍,DWORD数据的内存起始地址能被4除尽,WORD数据的内存起始地址能被2除尽,x86 CPU能直接访问对齐的数据,当他试图访问一个未对齐的数据时,会在内部进行一系列的调整,这些调整对于程序来说是透明的,但是会降低运行速度,所以编译器在编译程序时会尽量保证数据对齐。同样一段代码,我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果: 

#include <stdio.h> 

int main() 

int a; 
char b; 
int c; 
printf("0x%08x\n",&a); 
printf("0x%08x\n",&b); 
printf("0x%08x\n",&c); 
return 0; 

这是用VC编译后的执行结果: 
0x0012ff7c 
0x0012ff7b 
0x0012ff80 
变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。 

这是用Dev-C++编译后的执行结果: 
0x0022ff7c 
0x0022ff7b 
0x0022ff74 
变量在内存中的顺序:c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。 

这是用lcc编译后的执行结果: 
0x0012ff6c 
0x0012ff6b 
0x0012ff64 
变量在内存中的顺序:同上。 

三个编译器都做到了数据对齐,但是后两个编译器显然没VC“聪明”,让一个char占了4字节,浪费内存哦。 


基础知识: 
堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针,EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。 


参考:《Windows下的HEAP溢出及其利用》by: isno 
《windows核心编程》by: Jeffrey Richter 



摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)

前言
您是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了“自动化”?您的程序是否因堆分配而运行起来很慢?不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。

什么是堆?
(如果您已经知道什么是堆,可以跳到“什么是常见的堆性能问题?”部分)

在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作: 

事先不知道程序所需对象的数量和大小。


对象太大而不适合堆栈分配程序。
堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。

GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。

LocalAlloc/LocalFree:Win32 堆调用(为了与 Microsoft Windows NT 兼容),这些调用直接与每个进程的默认堆进行对话。

COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用“组件对象模型 (COM)”的分配程序,而申请的程序使用每个进程堆。

C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留在 Win32 堆的顶部。

Windows NT 中,Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。

Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。

在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。

分配和释放块不就那么简单吗?为何花费这么长时间?

堆实现的注意事项
传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”。如果没有其他堆可使用,则块的分配使用“进程堆”。语言运行时也能在进程内创建单独的堆。(例如,C 运行时创建它自己的堆。)除这些专用的堆外,应用程序或许多已载入的动态链接库 (DLL) 之一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。

当应用程序或 DLL 创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)

在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。

典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。

Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理内存” (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实现和设计的详细讨论也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个“大块”列表。“大块”列表(空闲列表[0]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,“进程堆”执行收集操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。

单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Workshop”上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。

什么是常见的堆性能问题?
以下是您使用堆时会遇到的最常见问题: 

分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。


释放操作造成的速度减慢。释放操作耗费较多周期,主要是启用了收集操作。收集期间,每个释放操作“查找”它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表。在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低。


堆竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。 
竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。

堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题。(破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节,请参见 Microsoft Visual C++(R) 调试文档 。)


频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配,随重分配增长和释放。不要这样做,如果可能,尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。
竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。

在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。

尽量减少堆的使用
现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略,情况将会大大好转。调整使用堆的方法,减少对堆的操作是提高性能的良方。

如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例:

struct ObjectA {
   // objectA 的数据 
}

struct ObjectB {
   // objectB 的数据 
}

// 同时使用 objectA 和 objectB

//
// 使用指针 
//
struct ObjectB {
   struct ObjectA * pObjA;
   // objectB 的数据 
}

//
// 使用嵌入
//
struct ObjectB {
   struct ObjectA pObjA;
   // objectB 的数据 
}

//
// 集合 – 在另一对象内使用 objectA 和 objectB
//

struct ObjectX {
   struct ObjectA  objA;
   struct ObjectB  objB;
}

避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。


把带指针的子对象嵌入父对象。当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。


合并小对象形成大对象(聚合)。聚合减少分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。


内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针。这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能。


在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪,例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。 
块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。

正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。
使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。

其他提高性能的技术
下面是一些提高速度的技术: 

使用 Windows NT5 堆 
由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:

改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。


使用 “Lookaside”列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节(以 8-字节递增)的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。


内部数据结构算法也得到改进。
这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每个进程堆的通用机制。如果希望获得高的局部性能,则使用 Heap(R) API 来存取每个进程堆,或为分配操作创建自己的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

上述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)

使用分配高速缓存 
分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。

典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息”。 

与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。

需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。

可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。

分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。

MP 堆 
MP 堆是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。

本程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。

重新思考算法和数据结构 
要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。

减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。

如果大量使用“Automation”结构,请考虑从主线代码中删除“Automation BSTR”,或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。)

摘要
对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。 

评价您的代码中堆的使用。


改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。


在实现自定义的包装程序之前使用量化堆调用成本的方法。


如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。


要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。


操作系统(Windows NT 家族)正在不断改进堆。请随时关注和利用这些改进。
Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。工作之外,他喜欢阅读、打排球和家庭烹饪。



http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中,如下  
CObject  object;  
还有一种是在堆(heap)中  如下  
CObject*  pobject=new  CObject();  
 
请问  
(1)这两种方式有什么区别?  
(2)堆栈与堆有什么区别??  
 
 
---------------------------------------------------------------  
 
1)  about  stack,  system  will  allocate  memory  to  the  instance  of  object  automatically,  and  to  the
 heap,  you  must  allocate  memory  to  the  instance  of  object  with  new  or  malloc  manually.  
2)  when  function  ends,  system  will  automatically  free  the  memory  area  of  stack,  but  to  the 
heap,  you  must  free  the  memory  area  manually  with  free  or  delete,  else  it  will  result  in  memory
leak.  
3)栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。  
4)堆上分配的内存可以有我们自己决定,使用非常灵活。  
---------------------------------------------------------------  

posted @ 2008-08-31 01:14 龙巢NET刀 阅读(201) | 评论 (1)编辑

2008年7月9日 #

聚焦3D地形编程第五章GeomipMapping for the CLOD

     摘要: Woohoo!你现在将要学习地形编程的核心部分了,它们是由难以置信的复杂算法。实际上,那是谎话。本章我将解释三个算法,因为它们的简单和高效所以选择了它们。And , for once in this book, 下面的列表是本章将要讲述的内容:

n 解释持续层次细节的意思

n geomipmapping的背后理论

n 实现geomipmapping的方法

  阅读全文

posted @ 2008-07-09 00:15 龙巢NET刀 阅读(642) | 评论 (0)编辑

2008年7月5日 #

聚焦3D地形编程第四章光照化地形

     摘要: 纹理化地形致使我们的地形进入一个新的细节等级,而光照化纹理将使整体进入新的真实感境界。问题是这样的:我们如何快速的光照我们的地形同时有保持高度的真实感?好,所有的技术让我将所有快速光照化地形的技术传授给你。我不会讲述复杂的全局光照算法(虽然我将为你指出一些获取这些信息的地方)因为地形真实感大概需要整本书来讲述。好的,下面是本章的议程:
  阅读全文

posted @ 2008-07-05 22:59 龙巢NET刀 阅读(288) | 评论 (0)编辑

聚焦3D地形编程第三章纹理化地形

     摘要: 现在你已经可以制作简单的地形网格, 你需要知道怎样如何使用纹理贴图来为那令人讨厌的网格添加细节。我将继续讨论简单的纹理并且一直到我们可以使用真正有趣的片段(地形算法)作为开始。我将停止浪费空间现在,并且仅仅告诉你将在本章学习什么:

n 怎样应用巨大的单模式纹理映射到地形网格上。

n 怎样以程序式手段来产生复杂的纹理地图使用各种各种的地形 tiles

n 怎样为地形添加纹理细节甚至以前产生的纹理的更多细节。

  阅读全文

posted @ 2008-07-05 16:42 龙巢NET刀 阅读(329) | 评论 (0)编辑

聚焦3D地形编程第一章 The Journey into the great Outdoors

 

聚焦3D地形编程第一章 The Journey into the great Outdoors

聚焦3D地形编程

翻译的烂,请见谅, 还请阅读原著 《Focus on 3D Terrain Programming》
译者: microsoftxiao@163.com 邵小宁 神杀中龙
第一部分
地形编程入门

 第一章室外旅行 The Journey into the great outdoors

欢迎来到奇妙的3D地形编程世界!我将指导你愉快的阅读地形书籍,并且我们一起渲染最高的山脉,低的山谷,甚至一些草地。总之,本书你将学到关于地形编程和它的游戏开发应用的很酷的东西。所以,装好你必要的背包(你知道,音乐, 咖啡, 零食, 还有一点teddy bear在你的卧室中)因为我们将要开始了。

地形? 谢谢, 我已经吃了。

我知道第一个问题你会问这个: “什么是地形, anyway?” 好,我将立刻回答它。地形是陆地: 多岩石的山脉,绿色的平原,起伏的小山,所有形态组合成美丽的风景。地形渲染领域关心怎样实时的渲染所有这些宏伟的自然风貌。之后你要作出地形的计算,你需要解决怎样渲染其他的自然风貌,像水,云,太阳,雾还有其他有趣的素材。

通过这本书,你至少可以理解怎样创建一个不能相信的真实的室外场景,有非常多的细节且速度极快。要通过一些通常的地形信息,从地形(不在游戏开发方面)的一些通常的应用开始。

常规应用

在我们非常酷的游戏开发应用之前, 让我们看下它的其他应用。我从Virtual Terrain Project(http://www.vterrain.org/)找到了一些信息,这是一个伟大的站点,有所有关于地形的应用。下面是地形的一些应用:

n        虚拟游览(travel planning)

n        可视化天气和环境拓扑

n        Real estate walkthroughs

n        军事用途,像飞行模拟(以训练为目的)

有许多地形的应用,正如你看到的,地形可视化和渲染是很重要的一个领域。使用工具来制作地形, 它有足够的细节和每帧平滑的速率。(缓慢的应用将扰乱任何地形场景,真实是最重要的。)这些信息仅仅是冰山一角; 如果你对地形感兴趣但是不在游戏开发,看一看上述站点。

地形和游戏开发

3D地形在游戏开发界是巨大的应用,尤其是出现了最新的连续等级细节(CLOD)算法后。(CLOD的定义将在第5章得到解释), “Geomipmapping for the CLOD impaired”)3D游戏以前在游戏内削弱了大量室外图形。他们更趋向于室内在小的卧室和不透气的走廊。(这在第一人称设计类型里尤其明显。)

在过去的几年内,作为玩家,已经见到一系列基于室外的游戏,跨越了各种类型: 策略,动作和第一人称设计。像Black and White(1.1)Starsiege: 部落是两个最主要的基于室外的游戏使用了广阔的地形。

所有的室外游戏最近两年内发布的,一个特殊的游戏可以代表了3D地形在游戏内的特殊应用和一些话题: Treadmarks

Treadmarks

发行于2000年一月, Seamus McNallyTreadmarks在人们回想的地形引擎领域是十分具有革命性的。游戏,如图1.2, 是一个基于坦克战斗和围绕着一种基于ROAM地形风景的竞赛游戏(这些细节将在第7章讲述, “Wherever You May ROAM”)还有包括一些大的爆炸。最好的部分是关于在游戏每个子弹爆炸将影响到地形。例如,正常情况下炮轰会留下洞在地形上,然而大量武器创造了更大的弹坑。

甚至现在, 这个游戏已经3岁了,它仍然跟人的印象很深刻。这要归功于McNallyROAM实现算法,显示出了新思想和改变算法的更多可应性到a fast-paced 图形应用像Treadmarks或者任何其他游戏。

不幸的是,Seamus McNallyHodgkin’s Lymphoma抗争了3年死于2000330日,享年21岁。

虽然我不了解Seamus或他的家人(创建Treadmarks的全体工作人员), 但我想要感谢他和他难以置信的主意和想法在地形可视化方面并且希望他静静的安眠。为了纪念他在GameDev.net创建了

(http://www.gamedev.net/community/memorial/seumas)

因为Treadmarks 是地形编程世界的里程碑,我可以包含这个dmo(来自于http://www.treadmarks.com/)在本书的附件CD(Demos/TM_16_Demo.exe).我强烈的推荐给你。这个游戏有很多伟大的地形效果, 然而更伟大的是我们将在本书讨论它。甚至it’s just such an addicting game!

Demo Building Made Easy!

这本书的demos被分成了三个组: 主要章节demos, 预备的主要章节demos和各种无偿的任意的demos包含在内。所有这些可以在本书的极好的伴随CD内。捐献的demos将不被包含; they will be left as a project for you to figure out.

The Main Demos

主要的demos是官方附加demos在每章的代码,提供给你。这些demos使用了OpenGL渲染API和自定义的Windows代码,所以你可以仅仅运行它们在Windwos操作系统。主要的demos也使用C++Microsoft Visual C++ 6.0

每个章节的代码被划分了两个部分: demo代码和基础代码。Demo代码被所有书的理论和内容的实现,而base代码包含了应用程序的初始化,摄影机例程(routine), 数学操作等。这所有在VC++(Microsoft Visual C++)工作区被命名为demoXX_YY.dsw, XX是章节的号,YYdemo的当前章节编号。

Note 注意

It is import to note that although this book’s accompanying demos stick to a certain API,

虽然这本书附加的demos关联的API是重要的,实际的文本API和操作系统独立的。无论你使用OpenGL, Direct3D, 或者其他API, 你将可以理解本书的内容。

当你用VC++打开工作区时, 你可以构造应用程序,并且被平稳的编译。让我们一步步的实现这些,你可以在附加CDCode\Chapter 1\demo1_1.First内找到,打开Microsoft Visual C++demo1_1.dsw(文件, 打开工作区). 之后你照着做,如图1.3

从那里,你可以简单的构造(Build, Build demo1_1.exe)demo和可执行的EXEDemo几乎精确的demo7_1的复制品(7章的第一个demo), 所有你可以期望看到本书后面相似的东西。For now though, 将快速的解释。Demo展示了看起来像ROAM算法的tessellation例子。现在有点是在戏弄你,直到你看到第7!如果你正确的Build demo1_1,它将像如图1.4。也可以看表1.1的控制。

Table 1.1 Controls for demo 1_1()

这不是一个美丽的截图么?我认为。无论如何,在这本书将完成的例子中这是没有说服力的,但是它的工作只是戏弄别人了点。

The Book at an Itty-Bitty Glance

本书将关于地形,地形,地形,喘口气甚至更多地形! 我们将包含从不规则高度图的产生到三种不同的CLOD算法。我们将以可怕的特效结束以增加3D地形场景的真实感。这些效果由像雾, 云渲染, 镜头光晕,和其他各种各样的技巧方法和效果组成。所以,without further ado, 让我们开始摘要。

我们将以一部分一部分的代替一章一章的。

第一部分: 地形编程入门 ()

第二部分: 高级地形编程 ()

The Demos

我编制了在本书讨论的每个主要主题的demo程序。As we proceed through the book, 作为贯彻本书始终,这需要你不断留意我提供的仅仅是你自己实现的一些步骤。It is imperative that you keep in mind the demos I provide are only to be used as a stepping stone for your own implementation. 我的实现制作成好的教学指南给你;不要仅仅复制粘贴demo代码到你自己的项目内。Demos提供的不是高度优化的,没有提供最佳的细节,并且所有各式我们讨论技术的bellswhistles没有被实现。

因为我是个和蔼的家伙,我决定帮助你个小忙。地形是一个动态问题: 技术导致工作一天也许需要更多天来检查。因此,我决定奉献我的站点(http://trent.codershq.com/)为地形研究和实现,我将保留一些不变的数据库在3D地形编程领域。我将尝试开发大量细节和我可以实现的最快的实现,我将保留一份开发日志。如果demos在本书CD没有提供,那么到我的站点来看一系列demos和将供应这些无价的信息。

摘要

本章包含了地形的基本常识和它的应用。它也说明了怎样编译和执行不同类型的demos和提供整本书的概述。准备好: 你的旅行将通向奇妙的地形渲染世界。

 

posted @ 2008-07-05 02:59 龙巢NET刀 阅读(499) | 评论 (0)编辑

聚焦3D地形编程第二章Terrain 101

 

聚焦3D地形编程第二章Terrain 101

翻译的烂,请见谅,期望深刻理解还请阅读原著,本译文仅供参考。
译:microsoftxiao@163.com 邵小宁 神杀中龙

好,在这里——你将进入3D地形编程的世界!本章将包括地形渲染的所有方面,在你开始有趣的纹理化/光照化技术之前,你需要知道这些,各种各样的硬编码地形算法。在本章,你将学习到下面的关键概念:

n         什么是高度图,怎样创建它,并且怎样加载它。

n         怎样使用burte force算法渲染地形

n         怎样使用fault formationmidpoint displacement产生不规则地形。

那么,让我们开始吧!

Heightmaps – 高度图

假设你有一个规则的多边形格子沿XZ轴展开。这种情况下你不知道我要谈论什么,如图2.1可以更新你的内存。

现在那是一张漂亮但有令人讨厌的图像!我们怎样可以正确的制作它呢,好,terrain-ish? 答案是使用高度图。高度图,在我们这儿,是一系列unsigned char变量(让我们把值取在0-255, 这样在灰度图上产生一系列渐变灰度值)我们将实时创建且在绘图程序内。高度图为我们的地形提供高度值,所以如果我们沿着XZ轴展开的话,高度图定义的值将沿着Y轴展开。快举个例子, 看如图2.2 之后我们加载它并且把它运用到我们的地形中,在格子2.1的格子中将变换出美丽地形(虽然它非常的缺乏颜色和光照)你看如图2.3

必须承认,图2.3的地形开起来即漂亮又讨厌,没有非常酷的纹理和光照,但是我们需要从某处开始!同样我刚刚解释了,高度图给我们的格子的顶点提高到一个宏伟的风景,增添了力量。问题是,严密的说什么是高度图?通常,一个高度图是一个每像素描绘的灰度值。(在这里,高度范围从0-255, 一系列渐变的灰度图。)暗色描绘低海拔,那么亮色描绘高海拔。再看如图2.22.3; notice how the 3D terrain(Figure 2.3) corresponds exactly to the heightmap in Figure 2.2, 从山顶任何一点,到山谷,是平滑的颜色么?那么我们想要我们高度图做什么呢:作为我们地形顶点的模型。

在这里,我们高度图的格式将是RAW格式。(虽然大部分demos动态创建高度图,我包含了加载和保存RAW格式的选项。) 我选择这个格式因为它难以置信的简单容易使用。另外,因为RAW格式仅仅包含纯数据,它更容易加载高度图。(我们也要加载灰度的RAW图,更容易制作。)在我们加载RAW图像前,我们需要几个东西。首先,我们需要创建一个简单的可以描绘高度图的数据结构。我们这个结构是一个unsigned char类型的缓冲区(我们需要可以动态分配内存)并且变量可以存储高度图的大小。这足够了么, 恩?好,这里:

struct SHEIGHT_DATA

{

        unsigned char* m_pucData; // the height data

      int           m_iSize;     // the height size(power of 2)

};

创建地形基础类 The Creation of a Base Terrain Class

我们需要从所有特定的地形引擎中(brute force, geomipmapping)创建基础类将被继承。我们不希望用户创建这类的实例;我们仅仅想让这个类成为我们今后开发特定实现的父类。看图2.4在我们头脑获得一个可视化概念。

CTERRAIN

CBRUTE_FORCE

CGEOMIPMAPPING

CROAM

CQUADTREE

继承自

2.4 CTERRAIN和四个地形实现类的关系

注意: CTERRAIN类是我们C++内的一种抽象类。抽象类是给所有它的子类提供公共的接口。用途是: 一个目前有红色头发但是性格另人讨厌。虽然所有它的孩子继承了母亲的红色头发,但每个有独特的性格。同样一个抽象类;虽然一个抽象类令人讨厌,its traits carry on to its children, 并且那些孩子可以被定义更多它们自己的另人激动的行为。(即通过覆写虚函数)

至此我们的类需要三个变量: 一个SHEIGHT_DATA的实例,一个高度缩放比列变量(将让我们可以为地形动态缩放高度), 和一个大小变量(可以存储SHEIGHT_DATA的大小)。至于函数,我们需要一些操作高度图的函数和设置高度缩放变量的函数。这里我们展示出:

class CTERRAIN

{

       protected:

              SHEIGHT_DATA  m_heightData;       // the height data

              Float                    m_fHeightScale;     // scaling variable

public:

    int                        m_iSize;            // must be a power of two

    virtual void Render(void) = 0;

   bool LoadHeightMap(char* szFilename, int iSize);

    bool SaveHeightMap(char* szFilename);

    bool UnloadHeightMap(void);

//-------------------------------------------------------------------------------------

// Name:                 SetHeightScale – public

// Description:             Set the height scaling variable

// Arguments:              -fScale: how much to scale the terrain

// Return Value: None

//---------------------------------------------------------------------------------------

inline void SetHeightScale(float fScale)

{ m_fHeightScale = fScale; }

//------------------------------------------------------------------------------------------

// Name:                   SetHeightAtPoint – public

// Description:              Set the true height value at the given point

// Arguments:               -unHeight: the new height value for the point

//                         -iX, iZ: which height value to retrieve

// Return Value:             None

//-------------------------------------------------------------------------------------------

inline void SetHeightAtPoint(unsigned char ucHeight, int iX, int iZ)

{ m_heightData.m_pucData[( iZ*m_iSize )+iX] = ucHeight; }

//---------------------------------------------------------------------------------------------

// Name:                      GetTrueHeightAtPoint – public

// Description:      A function to get the true height value(0-255) at a point

// Arguments:       -iX, iZ: which height value to retrieve

// Returen Value:    An unsigned char value: the true height at

//                 the given point

//----------------------------------------------------------------------------------------------

inline unsigned char GetTrueHeightAtPoint(int iX, int iZ)

{ return ( m_heightData.m_pucData[( iZ*m_iSize )+iX ]; }

//-------------------------------------------------------------------------------------------------

// Name:                  GetScaledHeightAtPoint – public

// Description:              Retrieve the scaled height at a given point

// Arguments:               -iX, iZ: which height value to retrieve

// Return Value:             A float value: the scaled height at the given point

//--------------------------------------------------------------------------------------------------

inline float GetScaledHeightAtPoint( int iX, int iZ )

{ return ( ( m_heightData.m_pucData[( iZ*m_iSize )+iX])*m_fHeightScale); }

CTERRAIN(void)

{    }

~CTERRAIN(void)

{    }

};

Not too shabby if I do say so myself! 好那是我们的地形父类!每个我们开发的其他实现从这个类派生。我为用户添加了两个容易使用的操作高度图的函数。然而我们,作为开发者,将使用true函数,用户将使用被缩放了的函数来执行碰撞检测(我们将在第八章做, “封装它: 特效和其他”)

加载和卸载高度图 Loading and Unloading a Heightmap

我已经谈论了这些例程,并且我们最后要使用它们。这些例程是简单的,所以对比他们没有任何难度。我们仅仅使用一些C风格的文件I/O来做。

注意: 我趋向于使用严格的C风格I/O因为它比C++风格的更容易阅读。如果你已经是真正的C++死忠,并且完全厌恶C做事的方式,那么可以自由改变例程为C++式的。另一方面,我确实喜欢C++风格的内存操作,所以,如果你是C死忠,那么你就那么做。

我需要谈论怎样加载,保存和卸载高度图。最好的地方是在开始时加载例程因为你不能在没有加载前卸载任何事物。我们需要两个参数:文件名和地图大小。在函数内,我们想创建FILE的实例来加载高度图。那么我们想确认高度图类的实例是否已经加载了信息;如果是这样,那么我们需要调用卸载例程并继续我们操作。我们讨论的代码像这样:

bool CTERRAIN::LoadHeightMap(char* szFilename, int iSize)

{

       FILE* pFile;

//check to see if the data has been set

if( m_heightData.m_pucData)

    UnloadHeightMap();

}

其次, 我们需要打开文件并为我们的高度图实例数据缓冲区分配内存(m_heightData.m_pucData)我们需要确认内存是否被正确的分配,是否没有发生可怕的错误。

// allocate the memory for our height data

m_heightData.m_pucData = new unsigned char [ iSize*iSize];

// check to see wether the memory was successfully allocated

if (m_heightData.m_pucData == NULL)

{

// the memory could not bel allocated

// something is seriously wrong here

printf(“Could not allocate memory for%s"n”, szFilename);

return false;
}

我们的加载过程继续,我们将加载实际的数据把它们放置在高度图实例的数据缓冲区内。然后我们将关闭文件,设置一些类成员变量,然后输出成功消息。

// read the heightmap into context

fread(m_heightData.m_pucDat, 1, iSize*iSize, pFile);

// close the file

fclose(pFile);

// set the size data

m_heightData.m_iSize = iSize;

m_iSize = m_heightData.m_iSize;

// Yahoo! The height has been successfully loaded!

Printf(“Loaded %s"n”, szFilename);

Return true;

}

注意: 高度图保存例程几乎是和加载同样的东西。基本上,我们仅仅需要替换freadfwriteThat’s all there is to it!

那就是加载例程。让我们在我们注意力被分散前转到卸载例程。卸载程序是简单的。我们仅仅必须检查内存是否已经被分配,然后如果分配了,我们需要删除它。

bool CTERRAIN::UnloadHeightMap(void)

{

// check to see if the data has been set

if(m_heightData.m_pucData)

{

    // delete the data

    delete[] m_heightData.m_pucData;

    // reset the map dimensions, also

    m_heightData.m_iSize = 0;

}

// the heightmap has been unloaded

printf(“Successfully unloaded the heightmap"n”);

return true;

}

我真的不需要检查数据缓冲是一个NULL指针(指针是否为NULL在中心会检查) , 所有我的检查有点是多余的。这个检查已经成为了习惯,然而,这本书就是这么做的。你可以不检查是否为NULL指针就删除它。现在我将展示给你我们已经讨论的渲染方法。

The Brute Force of the Matter 硬渲染

渲染地形使用brute force算法直接而简单,而且它提供了最大化的细节。不幸的是,它是这本书里讲的最慢的算法。基本上,如果你有一个64x64像素的高度图,那么地形,当使用brute force渲染时,由64x64个顶点组成,规则的重复模式。如图(2.5)

这种情况下你不能立即重新组织它,我们将每行的顶点作为三角形带渲染因为这是大部分渲染顶点的方式。你不能单独的渲染一个三角形或使用像图2.5那样的方式渲染三角扇形,would you?

这章的demo, 我留着它作为一种简单的可能。顶点的颜色基于它的高度,所以所有顶点将利用灰色着色。并且所有这些都使用brute force渲染地形。这里快速的摘录一小片OpenGL来展示怎样渲染地形:

void CBRUTE_FORCE::Render(void)

{

unsigned char unColor;

int iZ;

int iX;

// loop throught the Z axis of the terrain

for (iZ = 0; iZ<m_iSize-1; iZ++)

{

       // begin a new triangle strip

       glBegin(GL_TRIANGLE_STRIP);

       //loop through the X axis of the terrain

       //this is where the triangle strip is constructed

       for(iX=0; iX<m_iSize-1;iX++)

       {

              //Use height-based coloring. (High-points are

        //light, and low points are dark.)

        ucColor = GetTrueHeightAtPoint(iX, iZ);

              // set the color with OpenGL, and reader the point

        glColor3ub(ucColor, ucColor, ucColor);

        glVertex3f(iX, GetScaledHeightAtPoint(iX, iZ), iZ);

              // Use height-based coloring. (High-points are

        // light, and low points are dark.)

        ucColor = GetTrueHeightAtPoint(iX, iZ+1);

              // set the color with OpenGL, and render the point

        glColor3ub(ucColor, ucColor, ucColor);

        glVertex3f(iX,

GetScaledHeightAtPoint(iX, iZ+1),

iZ+1);

       }

    // end the triangle strip

    glEnd();

}
}

现在到了创建实际demo的时候了!拿出在CD上的demo2_1。到Cod"Chapter 2"demo2_1, Microsoft Visual C++打开工作区, 然后开始娱乐!这个demo展示了我们刚刚讨论的梭鱼东西。如图2.6展示了demo的截图,如表2.1提供了控制demo的描述。移动你的视点,仅仅可以使鼠标向左,右和拖拽。

Woohoo!现在,我说过我们将创建大量我们的动态高度图。你也许会问你自己,我该怎样做? 好的,我很高兴回答你。(甚至如果你不问,我仍然要解释它!)现在我们将学习怎样以程序的方式使用两种不规则地形产生技术生成高度图。准备!

Fractal Terrain Generatoin 不规则地形生成

Fractal terrain generation被用来产生地形的算法, 虽然这里,我们将高度图作为我们地形的蓝图。但是我们将通过这里的两个算法,第一个是fault formation和第二个midpoint displacement。我们将自始至终的使用fault formation算法在本书因为它不会被地点尺寸所限制,(如果用一般高度图将限制在0-255的高度) midpoint displacement需要2N次方才可以。(尺寸也必须是相等的,所以你可以产生1024x1024的高度图,你不能产生产生一个512x1024的高度图。)所以,不要再耽搁了,让我们从不规则地形生成算法开始!

Fault Formation缺点形成算法

一种不规则地形生成算法叫做fault formation. Fault formation是在生成地形过程中”faults”; 大部分时,它产生比较平滑的地形。基本上,所有我们做的随机线在blank高度区域, 而且然后我们添加随机高到一边。看图2.7如果你讨厌可视化或者如果你刚刚想要证实你脑中的图(或者, 如果你喜欢, 注意你的头脑我很奇怪)是正确的。

原版 43 fault-formation algorithm

这是整个过程的第一步,当然。在你提高到高级阶段之前,这里还有一些你需要知道的算法。首先,更早时我谈论过需要减少每次反复。你也许会问为什么?好,如果你不减少每个高度,你最后使用的高度将像2.8。看2.9的高度图。

注意, 在图2.8, /暗斑点是多么的不和谐就是这个原因;他们仅仅在所有地方被展开。这就好像一个混乱的地形,但是我们像创建一个平滑的,起伏的小山。不要担心;解决这个问题相当简单。我们想用线性递减高度值没有在0结束。这么做,我们将使用下面的等式(拿出demo2_2):

iHeight = iMaxDelta – ((iMaxDelta-iMinDelta)*iCurrentIteration)/iIterations;

iMinDelta, iMaxDelta, iIterations作为函数参数提供。 iMinDeltaiMaxDelta描绘了最低值和最高值,你想要当新faults时的高度。我趋向于严格的一个0作为iMinDelta255作为iMaxDeltaiIterations 我之前说过,描绘fault passes一系列过程(多么不同的时间被划分)。最后,but certainly not least, iCurrentIteration描绘了当前iteration值。

我早说过,我们就年斤毫年 想提升一边,然后我们想升起每个边线点的高度值。因此,我们将循环处理整个高度图的所有高度。所有这些容易实现;它仅仅是解决一个简单的数学问题。我有一个vector在我们线的方向上(我们之前创建了两个随机点,那么它的方向被存储在(iDirX1, iDirZ1)。下一个vector我们想创建一个从最初随机点(iRandX1, iRandZ1)到当前循环点(x, z) 之后完成,我们需要找到Z分量的叉乘, 然后如果它比0大,那么我们需要增加当前点。所有之前的解释都将从这的代码展示出来。

// iDirX1, iDirZ1 is a vector going the same direction as the line

iDirX1 = iRandX2 – iRandX1;

iDirZ1 = iRandZ2 – iRandZ1;

for(x = 0; x<m_iSize;x++)

{

       for(z=0; z<m_iSize; z++)

       {

              // iDirX2, iDirZ2 is a vector from iRandX1, iRandZ1 to the

              // current point (in the loop).

              iDirX2 = x-iRandX1;

             iDirZ2 = z-iRandZ1;

              // if the result of (iDirX2*iDirZ1 – iDirX1*iDirZ2) is “up”

              //(above 0), then raise this point by iHeight

              if((iDirX2*iDirZ1 – iDirX1*iDirZ2) > 0)

                     fTempBuffer[( z*m_iSize)+x]+=(float)iHeight;

       }

}

注意: demo2_2这两段你看到了fault formationmidpoint displacement代码在demo2_2内的两个片段,你也许注意到我怎样创建临时缓冲区,fTempBuffer, 所有的高度值严格用浮点表示。如果你记得,虽然,我谈论过我们的高度图是一个unsigned char类型的数组。为什么我在这种情形使用浮点变量?我这么做是因为算法需要比我们的默认unsigned char高度缓冲区有更高的精确性。之后我们创建整个高度图并规格化,我从fTempBuffer传送所有信息到CTERRAIN类内的高度缓冲区, m_heightData

检查图2.9看一些使用fault formation产生的高度图,和各种fault-line iterations. 紧接着, 我们也还没有完成这个算法!万一你没注意, 地图看起来像之前的图(-terrainish)(新世界)。我们需要经过一个腐蚀(erosion)过滤器来过滤整个地图直到我们形成一个新的平滑的值的。这个过程非常好, 如果不精确, 像经过污点过滤器通过你喜欢的绘图程序来处理下。 如果它帮助你理解了下面的解释, 正好是这样的理解。

我们将要应用一个简单的FIR过滤器, 作为Jason Shankel的建议。 这个过滤器意味着模拟地形侵蚀(erosion),就像自然界频繁发生的那种。你曾经在自然界里看到过的一系列的高山看起来如图2.9?) 我们将获得波形(bands)数据,胜于立刻过滤整个高度图。过滤函数看起来像这样:

void CTERRAIN::FilterHeightBand( float* fpBand, int iStride, int iCount, float fFilter)

{

       float v = ucpBand[0];

       int j = iStride;

       int i;

       // Go through the height band and apply the ersion filter

       for(i = 0; i < iCount-1; i++ )

       {

              ucpBand[j] = fFilter*v + (1-fFilter)*ucpBand[j];

              v = ucpBand[j];

              j+= iStride;

       }

}

这个函数获取高度值的单个边并且goes through them value by value, 通过iStride规定在每次循环内的向前的值日。iStride也规定出我们过滤整个高度图从上到下的方向,从下到上,从坐到右,从右到左。整个函数最重要的是这行:

ucpBand[j] = fFilter*v + (1-fFilter)*ucpBand[j];

这行是涂污/侵蚀。 各种各样的值为了fFilter影响模糊。0.0f是根本不模糊, 1.0f是最模糊。通常,我们想要值在0.3f0.6f之间,这依赖于你想要地形的平滑程度。现在,例如, 我们说出过滤器的值0.25f, 且当前边值为0.9f。前一个等式看起来像这样:

ucpBand[j] = 0.25f*v + (1-0.25f)*0.9f;

之后我们执行初始化计算, 之前的等式将简单化为这样:

ucpBand[j] = 0.25f*v + 0.675f;

0.675f是高度图像素被模糊的新值, 但是现在它需要被和之前的像素值进行插值。(我们将给出像素值为0.87f)。我们应用0.25模糊过滤器到该像素且加上非插值的像素值到此像素,以致于我们有这样的计算。

ucpBand[j] = 0.25f*0.87f + 0.675f;

执行最后的计算, 我们得到0.8925f的最终值。 所以,你看, 所有我们真实的行动这里混合成了当前像素到前一像素间的值。拿出图2.10看我们之前讨论的每像素过滤看起来是非常大攀登。

玩弄下demo2_2. 我为高度图操作制作了菜单, 并且现在你可以动态创建新的高度图。如果你找到了,仅仅选择保存当前选项, 那么高度图将被保存到程序目录下。当你选择Fault Formation选项时,弹出一个对话框你可以输入细节值。这个值是一个整数,取值范围为1-100。现在该介绍些有趣的midpoint displacement(中点位移)的时候了。

Midpoint Displacement 中点位移算法

Fault formation在一些小场景组成一些小山工作的非常好,但是如果你想产生一些比这混乱的,甚至像山脉那样的地貌, Fault formation就不行了。好,继续看。Midpoint displacement将可以满足你的期待!这个算法也被认为是plasma fractaldiamond-square算法。然而,midpoint displacement发出的声音更酷,并且它提供给读者(就是你)一个继续整个过程更好的观点,所以我将坚持大部分时间使用这一术语。

注意: 重要的是注意midpoint displacement算法有一个轻微的缺点: 算法仅可以生成方形的高度图,并且尺寸必须似乎2N次方。这不像fault formation算法,你可以指定任何你想要的尺寸。

我们将完成这个算法,本质上,它是对单条边的中点进行位移。让我给你一个一维空间的概念。如果我们有一条线,像如图2.11 AB 我们找到它的重点,标记出来为C。并且移动它。现在,我们将位移线中点的高度值,我们叫fHeight吧。(看图2.12)我们将使得产生的两条线相等,并且我们将在-fHeight/2fHeight/2到范围内位移中点。(我们想要每次细分出(subdivide)两条线,而且我们将要将其位移到线的某个高度在一定范围内。

之后我们需要递减fHeight的直到我们期望的粗糙程度。就这么做,我们简单的用2-fRoughness来进行乘法, fRoughness是未加工地形的一个常量值。用户将指定该值存储到fRoughtness内,所以你需要知道一点关于你可以设置各种值的信息。这个值是可以的,从技术上讲,任何可以是你任何期望的浮点值,但是最好的结果应该是0.25f1.5f。看图2.13,可视化的指示出各种可以达到的粗糙程度的情形。

正如你看到的,这个值即fRoughness对高度图的影响相当大。值小于1.0f将创建一个无序地形,值正好为1.0将是平行的,大于1.0f将创建一个平滑的地形。现在让我们继续深入解释二维的情形。

1D的解释留在你的大脑中,我们将改变到2D因为你刚刚学习了单条线的相关概念。有个例外是这样的,代替单线的中点计算,我们现在必须计算四条不同边的中点,平均它们,然后为正方形的中心的高度值增加这个值。如图2.14所示的正方形(ABCD)开始。

像我之前说的第二点,我们必须计算所有四边的中点(AB, BD, DC, CA)结果点为E, 将直接在正方形的中心。然后位移EABCD高度值的平均值,并加上在-fHeight/2fHeight/2范围内的随机值。结果将如图2.15所示。

这还仅仅是第一次位移的一半阶段。现在我们必须计算出每个中点的高度值,是我们先前找到的那个。跟我们之前做的是相似的,;我们仅仅平均围绕顶点的高度值并加上-fHeight/2fHeight/2范围内随机值日。最后将如图2.6所示。

然后你可以继续找到下一个矩形执行同样的处理。如果你理解了1D解释,然而,你确定理解了2D解释并实习那代码,demo2_2, 找到CDCode"Chapter 2"demo2_2

编译信息,照常,提供了文本文件在demo的目录下。去查看这个demo。控制与最后一次的(2.1提示)的相同,但是这次,当你点下细节区域的重点时,你想要的值范围为0(真是无序的地形)150(简单地形 ). 真有趣。

摘要

本章,你收到了进入地形编程的入门级训练。你学到了所有关于高度图的信息: 它们是,怎样产生它们,还有怎样加载/卸载它们。然后你学习了怎样使用burte force渲染那些高度图,是市面上最简单的地形渲染算法。最后,你学习两种程序式产生高度图的算法。下两章,我们将学习所有和地形的纹理化和光照化的有趣技术。

参考

1 Shankel, Jason, “Fractal Terrain Generation – Fault Formation. “

Game Programming Gems. Rockland, Massachusetss: Charles River Media, 2000. 499-502.

2. Shankel, Jason. “Fractal Terrain Generation – Midpoint Displacement. “ Game Programming Gems. Rockland, Massachusetts:

Charles River Media, 2000. 503-507.

posted @ 2008-07-05 02:52 龙巢NET刀 阅读(619) | 评论 (1)编辑

2007年5月24日 #

枚举驱动器

//测试 含有空格的文件名, 检测, 文件名内是否有空格。 //另外就是使用 SEH异常处理 #include #include #include using namespace std; int RetDriveTypeStr(char* szTypeStr, int nType) { char szNoType[] = "未知类型"; char szLogical[] = "硬盘驱动器"; char szCDROM[] = "光盘驱动器"; char szRemovable[] = "移动设备"; //是否是软驱 U盘也属于 switch(nType) { case DRIVE_UNKNOWN: lstrcpyn(szTypeStr, szNoType, lstrlen(szNoType)+1); break; case DRIVE_FIXED: lstrcpyn(szTypeStr, szLogical, lstrlen(szLogical)+1); break; case DRIVE_CDROM: lstrcpyn(szTypeStr, szCDROM, lstrlen(szCDROM)+1); break; case DRIVE_REMOVABLE: lstrcpyn(szTypeStr, szRemovable, lstrlen(szRemovable)+1); break; default: lstrcpyn(szTypeStr, szNoType, lstrlen(szNoType)+1); break; } HANDLE hDisk = CreateFile("\\\\.\\D:", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(hDisk != INVALID_HANDLE_VALUE) { DISK_GEOMETRY dgDisk; DWORD dwNotUsed = 0; if(DeviceIoControl(hDisk, 0, 0, 0, &dgDisk, sizeof(dgDisk), &dwNotUsed, NULL)) { if(dgDisk.MediaType == RemovableMedia) { cout<<"你的是移动磁盘"< posted @ 2007-05-24 04:01 龙巢NET刀 阅读(147) | 评论 (0)编辑

2007年5月23日 #

文件遍历目录

     摘要: // 对 选择进入子目录后 增加了一句 SetCurrentDirectory , 这样才不致于 重复枚举文件。 //搜索和感染模块//内部检索所有文件, 在内部再判断扩展名bool __stdcall SearchEntry(HWND param_ListBoxHWND, const char* param_curDir, const char* param_extendName, ...  阅读全文

posted @ 2007-05-23 22:55 龙巢NET刀 阅读(129) | 评论 (0)编辑

2007年5月20日 #

和InjectFile配套InjectDLL

     摘要: // InjectDLL.cpp : Defines the entry point for the DLL application.//#include "stdafx.h"#include "InjectDLL.h"// This is an example of an exported variableINJECTDLL_API int nInjectDLL=0;// This is an ...  阅读全文

posted @ 2007-05-20 03:25 龙巢NET刀 阅读(131) | 评论 (0)编辑