Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

这次主题是渲染管线。它是用来创建为3D世界进行几何描述的2D图形并设定一个虚拟照相机确定这个世界中哪一部分将被透视投影到屏幕上。

\

2.1表现模型

一个场景是多个物体或模型的集合。一个物体可以用三角形网格(triangle mesh)来近似表示,如图2.2所示。由三角形网格建立一个物体,我们称之为建模。3D世界中最基本的图元就是三角形,但是Direct3D也支持点图元和线图元但我们都不常用到。

一个多边形的两边相交的点叫做顶点。为了描述一个三角形,我们通常指定三个点的位置来对应三角形的三个顶点(如图2.3),这样我们就能够很明确的表示出这个三角形了。

2.1.1 顶点格式

我们以前定义的点在数学上来说是正确的,但是当我们在Direct3D环境中使用它的时候就会觉得很不完善。这是因为在Direct3D中的顶点包含了许多附加的属性,而不再单纯的只有空间位置的信息了。例如:一个顶点可以有颜色和法线向量属性。Direct3D让我们可以灵活的构造自己的顶点格式。换句话说,我们可以自己定义顶点的成员。

为了创建一个自定义的顶点结构,我们首先要创建一个包含能存放我们选择的顶点数据的结构。例如,下面我们举出两种不同顶点数据类型的例子,一种包含了位置和颜色信息,第二种则包含了位置,法线向量,纹理坐标信息。

struct ColorVertex

{

       float _x, _y, _z; // 位置

       DWORD _color; // 颜色

};

struct NormalTexVertex

{

       float _x, _y, _z; // 位置

       float _nx, _ny, _nz; // 法线向量

       float _u, _v; // 纹理坐标

};

一旦我们有了完整的顶点格式,我们就要使用灵活顶点格式(FVF)的组合标志来描述它。例如第一个顶点结构,我们要使用如下的顶点格式:

#define FVF_COLOR (D3DFVF_XYZ | D3DFVF_DIFFUSE)

上面的顶点结构表明它包含位置和颜色属性。

而第二种结构则要使用:

#define FVF_NORMAL_TEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

上面的顶点结构表明它包含了位置,法线向量,纹理坐标的属性(这些常量是D3D内置的)。

有一点要注意,你的标志的顺序必须要和你的顶点结构的顺序一一对应。如果想知道所有的D3DFVF标志,请查阅SDK文档。

2.1.2 三角形

三角形是构建3D物体的基本图形。为了构造物体,我们创建了三角形列表(triangle list)来描述物体的形状和轮廓。三角形列包含了我们将要画的每一个三角形的数据信息。例如为了构造一个矩形,我们把它分成两个三角形,如图2.4所示,最后指定每个三角形的顶点。

Vertex rect[6] = {v0, v1, v2, // 三角形0

                            v0, v2, v3}; // 三角形1

注意:指定三角形顶点的顺序是很重要的,将会按一定顺序环绕排列。

2.1.3 索引

3D物体中的三角形经常会有许多共用顶点。如图2.4所表示的矩形。虽然现在仅有两个点被重复使用,但是当要表现一个更精细更复杂的模型的时候,重复的顶点数将会变得很大。例如图2.5所示的立方体,仅有八个顶点,但是当用三角形列表示它的时候,所有的点都被重复使用。

为了解决这个问题,我们引入索引(indices)这个概念。它的工作方式是:我们创建一个顶点列表和一个索引列表(index list)。顶点列表包含所有不重复的顶点,索引列中则用顶点列中定义的值来表示每一个三角形的构造方式。回到那个矩形的示例上来,它的顶点列表的构造方式如下:

Vertex vertexList[4] = {v0, v1, v2, v3};

索引列表则定义顶点列中的顶点是如何构造这两个三角形的:

WORD indexList[6] = {0, 1, 2, //三角形0

                            0, 2, 3}; //三角形1

也就是说,用顶点列表中的0(vertexList[0])、1(vertexList[1])和2(vertexList[2])顶点构成三角形0;用顶点列表中的0(vertexList[0])、2(vertexList[2])和3(vertexList[3])顶点构成三角形1。

2.2虚拟照相机

照相机确定3D世界中的哪部分是可见的,因而需要将哪部分转换为2D图形。在3D世界中照相机被放置和定向,并且定义其可视体,图2.6展示了我们的照相机模型。

可视体是由可视角度和前裁剪面(Near Plane)与后裁剪面(Far Plane)定义的一个平截头体。之所以要选择平截头体构造可视体,是因为我们的显示器都是矩形的。在可视体中不能被看见的物体都会被删除,删除这种数据的过程就叫做“裁剪”。

投影窗口(Projection Window)是可视体内的3D几何图形投影生成的用来显示3D场景的2D图像的2D区域。重要的是要知道,我们使用min=(-1,-1)和max=(1,1)来定义投影窗口的大小。

为了简化绘制,我们使前裁剪面与投影窗口在同一平面上。并且,注意Direct3D中定义的投影平面(即投影窗口所在的平面)是Z = 1的平面。

2.3 渲染管线

一旦我们描述几何学上的3D场景和设置了虚拟照相机,我们要把这个场景转换成2D图象显示在显示器上。这一系列必须完成的操作就叫做渲染管线。图2.7展示了一个简化的渲染管线,随后将详细解释图中的每一部分。

渲染管线中的许多级都是从一个坐标系到另一个坐标的几何变换。这些变换都通过矩阵变换来实现。Direct3D为我们进行变换计算并且如果显卡支持硬件变换的话那就更有利了。使用Direct3D进行矩阵变换,我们唯一要做的事就是提供从一个系统变换到另一个系统的变换矩阵就可以了。我们使用IDirect3DDevice9::SetTranform方法提供变换矩阵。它输入一个表示变换类型的参数和一个变换矩阵。如图2.7所示,为了进行一个从自身坐标系到世界坐标系的变换,我们可以这样写:

Device->SetTransform(D3DTS_WORLD, &worldMatrix);

2.3.1自身坐标系(Local Space)

自身坐标系又叫做建模空间,这是我们定义物体的三角形列的坐标系。自身坐标系简化了建模的过程。在物体自己的坐标系中建模比在世界坐标系中直接建模更容易。例如,在自身坐标系中建模不像在世界坐标系中要考虑本物体相对于其他物体的位置、大小、方向关系。图 2.8所示是一个在自身局部坐标系中定义的茶壶。

2.3.2世界坐标系(World Space)

一旦我们构造了各种模型,它们都在自己的自身坐标系中,但是我们需要把它们都放到同一个世界坐标系中。物体从自身坐标系到世界坐标系中的变换叫做世界变换。世界变换通常是用平移、旋转、缩放操作来设置模型在世界坐标系中的位置、大小、方向。世界变换就是通过各物体在世界坐标系中的位置、大小和方向等相互之间的关系来建立所有物体。图2.9所示是相对于世界坐标系描述的几个3D物体。

世界变换由一个矩阵表示,并且在Direct3D中调用IDirect3DDevice9::SetTransform方法设置它,记住将转换类型设为D3DTS_WORLD。例如我们要在世界坐标系中放置一个立方体定位在(-3,2,6)和一个球体定位在(5,0,-2),我们可以这样写程序:

//创建立方体的世界矩阵(一个平移矩阵)

D3DXMATRIX cubeWorldMatrix;

D3DXMatrixTranslation(&cubeWorldMatrix, -3.0f, 2.0f, 6.0f);

//创建球体的世界矩阵(一个平移矩阵)

D3DXMATRIX sphereWorldMatrix;

D3DXMatrixTranslation(&sphereWorldMatrix, 5.0f, 0.0f, -2.0f);

// 变换立方体,然后绘制它

Device->SetTransform(D3DTS_WORLD, &cubeWorldMatrix);

drawCube(); // draw the cube

// 因为球体使用一个不同的世界变换,我们必须更改世界矩阵为球体的,

// 如果不更改,球体将绘制在上一个世界矩阵的位置上(立方体的世界矩阵)

Device->SetTransform(D3DTS_WORLD, &sphereWorldMatrix);

drawSphere(); // 绘制球体

这是个非常简单的实例,没有用到矩阵的旋转和缩放。但是一般很多物体都需要进行这些变换,不过这个例子也还是展示了世界变换是怎样进行的。

2.3.3视图坐标系(View Space)

世界坐标系中的几何图与照相机是相对于世界坐标系而定义的,如图2.10所示。然而在世界坐标系中当照相机是任意放置和定向时,投影和其它一些操作会变得困难或低效。为了使事情变得更简单,我们将照相机平移变换到世界坐标系的原点并把它的方向旋转至朝向Z轴的正方向,当然,世界坐标系中的所有物体都将随着照相机的变换而做相同的变换。这个变换就叫做视图坐标系变换(view space transformation)。

视图坐标的变换矩阵可以通过如下的D3DX函数计算得到:

D3DXMATRIX *D3DXMatrixLookAtLH(

       D3DXMATRIX* pOut, // 指向返回的视图矩阵

       CONST D3DXVECTOR3* pEye, // 照相机在世界坐标系的位置

       CONST D3DXVECTOR3* pAt, // 照相机在世界坐标系的目标点

       CONST D3DXVECTOR3* pUp // 世界坐标系的上方向(0, 1, 0)

);

pEye参数指定照相机在世界坐标系中的位置,pAt参数指定照相机所观察的世界坐标系中的一个目标点,pUp参数指定3D世界中的上方向,通常设Y轴正方向为上方向,即取值为(0,1,0)。

例如:假设我们要把照相机放在点(5,3,-10),并且目标点为世界坐标系的中点(0,0,0),我们可以这样获得视图坐标系变换矩阵:

D3DXVECTOR3 position(5.0f, 3.0f, –10.0f);

D3DXVECTOR3 targetPoint(0.0f, 0.0f, 0.0f);

D3DXVECTOR3 worldUp(0.0f, 1.0f, 0.0f);

D3DXMATRIX V;

D3DXMatrixLookAtLH(&V, &position, &targetPoint, &worldUp);

视图坐标系变换也是通过IDirect3DDevice9::SetTransform来实现的,只是要将变换类型设为D3DTS_VIEW,如下所示:

Device->SetTransform(D3DTS_VIEW, &V);

2.3.4背面消除(Backface Culling)

一个多边形有两个表面,我们将一个标为正面,一个为背面。通常,后表面总是不可见的,这是因为场景中大多数物体是密封的。例如盒子、圆柱体、箱子、角色等,并且我们也不能把照相机放入物体的内部。因此照相机永不可能看到多边形的背面。这是很重要的,如果我们能看背面,那么背面拣选就不可能工作。

图2.11表示了一个物体在视图坐标系中的正面。一个多边形的边都是面向照相机叫正面多边形,而一个多边形的边都背对照相机叫背面多边形。

由图2.11可知,正面多边形挡住了在它后面的背面多边形,Direct3D将通过消除(即删除多余的处理过程)背面多边形来提高效率,这种方法就叫背面拣选。图2.12展示了背面拣选之后的多边形,从照相机的观察点来看,仍将绘制相同的场景到后备表面,那些被遮住的部分无论如何都永远不会被看见的。

当然,为了完成这项工作,Direct3D需要知道哪个多边形是正面,哪个是背面。Direct3D中默认顶点以顺时针方向(在观察坐标系中)形成的三角形为正面,以逆时针方向形成的三角形为背面。

如果我们不想使用默认绘制状态,我们可以通过改变D3DRS_CULLMODE来改变渲染状态:

Device->SetRenderState(D3DRS_CULLMODE, Value);

Value可以是如下一个值:

D3DCULL_NONE——完全不使用背面消除

D3DCULL_CW——消除顺时针方向环绕的三角形

D3DCULL_CCW——消除逆时针方向环绕的三角形,这是默认值。

2.3.5光照(Lighting)
光照定义在世界坐标系中,但必须变换到视图坐标系才可使用。视图坐标系中光源给物体施加的光照大大增加了场景中物体的真实性。
2.3.6裁剪(Clipping)

我们删除那些超出了可视体范围的几何图形的过程就叫做裁剪。这会出现三种情况:

完全包含——三角形完全在可视体内,这会保持不变,并进入下一级。

完全在外——三角形完全在可视体外部,这将被删除。

部分在内(部分在外)——三角形一部分在可视体内,一部分在可视体外,则三角形将被分成两部分,可视体内的部分被保留,可视体之外的则被删除。

图2.13展示了上面三种情况:

2.3.7投影(Projection)

视图坐标系的主要任务就是将3D场景转化为2D图像表示。这种从n维转换成n-1维的过程就叫做投影。投影的方法有很多种,但是我们只对一种特殊的投影感兴趣,那就是透视投影。因为透视投影可以使离照相机越远的物体投影到屏幕上后就越小,这可以使我们把3D场景更真实的转化为2D图像。图2.14展示了一个3D空间中的点是如何通过透视投影到投影窗口上去的。

投影变换的实质就是定义可视体并将可视体内的几何图形投影到投影窗口上去。投影矩阵的计算太复杂了,这里我们不会给出推导过程,而是使用如下的Direct3D函数通过给出平截头体的参数来求出投影矩阵。

D3DXMATRIX *D3DXMatrixPerspectiveFovLH(

       D3DXMATRIX* pOut, // 返回的投影矩阵

       FLOAT fovY, // 用弧度表示的视野角度vertical field of view angle in radians

       FLOAT Aspect, // 宽高比

       FLOAT zn, // 前裁剪面距离

       FLOAT zf // 后裁剪面距离

);

(fovY定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/2(90度角),那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值设为D3DX_PI(180度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为2 x D3DX_PI的话。。。我先编译一下试试(building…)。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊)

如图2.15所示视锥的描述参数。

Aspect参数为投影平面的宽高比例值,由于最后都为转换到屏幕上,所以这个比例一般设为屏幕分辨率的宽和高的比值。如果投影窗口是个正方形,而我们的显示屏一般都是长方形的,这样转换后就会引起拉伸变形。

aspectRation = screenWidth / screenHeight

我们还是通过调用IDirect3DDevice9::SetTranform方法来进行投影变换,当然,要把第一个投影类型的参数设为D3DTS_PROJECTION。下面的例子基于一个90度视角、前裁剪面距离为1、后裁剪面距离为1000的平截头体创建投影矩阵:

D3DXMATRIX proj;

D3DXMatrixPerspectiveFovLH(&proj, PI * 0.5f, (float)width / (float)height, 1.0, 1000.0f);

Device->SetTransform(D3DTS_PROJECTION, &proj);

2.3.8视口变换(Viewport Transform)

视口变换主要是转换投影窗口到显示屏幕上。通常一个游戏的视口就是整个显示屏,但是当我们以窗口模式运行的时候,也有可能只占屏幕的一部分或在客户区内。视口矩形是由它所在窗口的坐标系来描述的,如图2.16。

在Direct3D中,视口矩形通过D3DVIEWPORT9结构来表示。它的定义如下:

typedef struct _D3DVIEWPORT9 {

       DWORD X;

       DWORD Y;

       DWORD Width;

       DWORD Height;

       DWORD MinZ;

       DWORD MaxZ;

} D3DVIEWPORT9;

前四个参数定义了视口矩形与其所在窗口的关系。MinZ成员指定最小深度缓冲值,MaxZ指定最大深度缓冲值。Direct3D使用的深度缓冲的范围是0~1,所以如果不想做什么特殊效果的话,将它们分别设成相应的值就可以了。

一旦我们填充完D3DVIEWPORT9结构后,就可以如下设视口:

D3DVIEWPORT9 vp{ 0, 0, 640, 480, 0, 1 };

Device->SetViewport(&vp);

这样,Direct3D就会自动为我们处理视口变换。现在还是给出视口变换矩阵作为参考:

2.3.9光栅化(Rasterization)

在把三角形每个顶点转换到屏幕上以后,我们就画了一个2D三角形。光栅化是计算需要显示的每个三角形中每个点颜色值(如图2.17)。

光栅化过程是非常繁重的计算,它应该通过硬件图形处理来完成。它的处理结果就是把2D图象显示在显示器上。

本文主要介绍并行端口的结构以及简单的对并口的读、写并以及如何获得端口的状态。   

并行接口的分类: SPP(标准并行接口) ,EPP(增强型并行接口),ECP(扩展型并行端口)

  标准并行端口(SPP)也是最早的端口定义,主要功能如下,1:并行端口提供了8个数据线以进行并行的字节传输,2:计算机能够通过数据线向打印机发送选能信号,以通知打印机已经准备好接收数据,3:打印机招收到数据后,向计算机发送一个回应信号(NACK)。其各位信号线所代表的意义详见下表。

@echo off

setlocal enabledelayedexpansion

set b=/-\ /-\ **

set 速度=1

set 退格=

:b

for /l %%i in (0,1,200) do call :a %%i

goto :b

:a

set/a a=%1%%10

set/a c=%a%%%4

if %a% EQU 0 set/p=▌<nul

//以下代码来自网上,需DDK、SDK支持





#include <windows.h>

#include <Setupapi.h>

#include <winioctl.h>

#include <cfgmgr32.h>

#include <regstr.h>  

#include <initguid.h>  

#include <stdio.h>
extern   "C"   
{    
 #include   "hidsdi.h"    
}  
DEFINE_GUID(GUID_DEVINTERFACE_DISK,                   0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
DEFINE_GUID(GUID_DEVINTERFACE_CDROM,                  0x53f56308L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
//-------------------------------------------------
DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber, UINT DriveType);
//-------------------------------------------------
//-------------------------------------------------
int main(int argc, char* argv[])
{
 if ( argc != 2 ) {
  return 1;
 }
 
 //printf("in \n\n");
 
 char DriveLetter = argv[1][0];
 DriveLetter &= ~0x20; // uppercase
 
 if ( DriveLetter < 'A' || DriveLetter > 'Z' )
 {
  return 1;
 }
 
 char szRootPath[] = "X:\\";   // "X:\"
 szRootPath[0] = DriveLetter;
 
 char szVolumeAccessPath[] = "
\\\\.\\X:";   // "\\.\X:"
 szVolumeAccessPath[4] = DriveLetter;
 
 long DiskNumber = -1;
 
 HANDLE hVolume = CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
 if (hVolume == INVALID_HANDLE_VALUE) {
  return 1;
 }
 
 STORAGE_DEVICE_NUMBER sdn;
 DWORD dwBytesReturned = 0;
 long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
 if ( res ) {
  DiskNumber = sdn.DeviceNumber;
 }
 CloseHandle(hVolume);
 
 if ( DiskNumber == -1 ) {
  return 1;
 }
 
 UINT DriveType = GetDriveType(szRootPath);
 
 DEVINST DevInst = GetDrivesDevInstByDiskNumber(DiskNumber, DriveType);
 
 if ( DevInst == 0 ) {
  return 1;
 }
 
 ULONG Status = 0;
 ULONG ProblemNumber = 0;
 PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
 WCHAR VetoNameW[MAX_PATH];
 bool bSuccess = false;
 
 res = CM_Get_Parent(&DevInst, DevInst, 0);  // disk's parent, e.g. the USB bridge, the SATA port....
 res = CM_Get_DevNode_Status(&Status, &ProblemNumber, DevInst, 0);
 bool IsRemovable = ((Status & DN_REMOVABLE) != 0);
 
 
 for ( long tries=1; tries<=3; tries++ ) { // sometimes we need some tries...
  VetoNameW[0] = 0;
  if ( IsRemovable ) {
   res = CM_Request_Device_EjectW(DevInst, &VetoType, VetoNameW, sizeof(VetoNameW), 0);
   //res = CM_Request_Device_EjectW(DevInst, &VetoType, NULL, 0, 0);  // with MessageBox or 'bubble'
  } else {
   res = CM_Query_And_Remove_SubTreeW(DevInst, &VetoType, VetoNameW, sizeof(VetoNameW), 0); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
  }
  bSuccess = (res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown);
  if ( bSuccess )  {
   break;
  } else {
   Sleep(200); // required to give the next tries a chance!
  }
 }
 
 if ( bSuccess ) {
  printf("Success\n\n");
  return 0;
 }
 
 printf("failed\n");
 
 printf("Result=0x%2X\n", res);
 
 if ( VetoNameW[0] ) {
  printf("VetoName=%ws)\n\n", VetoNameW);
 }
 return 1;
}
//-----------------------------------------------------------
 

//-----------------------------------------------------------
DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber, UINT DriveType) {
 
 GUID* guid;
 
 switch (DriveType) {
 case DRIVE_REMOVABLE:
  //break;
 case DRIVE_FIXED:
  guid = (GUID*)(void*)&GUID_DEVINTERFACE_DISK;
  break;
 case DRIVE_CDROM:
  guid = (GUID*)(void*)&GUID_DEVINTERFACE_CDROM;
  break;
 default:
  return 0;
 }
 
 // Get device interface info set handle for all devices attached to system
 HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 
 if (hDevInfo == INVALID_HANDLE_VALUE){
  return 0;
 }
 
 // Retrieve a context structure for a device interface of a device
 // information set.
 DWORD dwIndex = 0;
 SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
 devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
 BOOL bRet = FALSE;
 
 BYTE Buf[1024];
 PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
 SP_DEVICE_INTERFACE_DATA         spdid;
 SP_DEVINFO_DATA                  spdd;
 DWORD                            dwSize;
 
 spdid.cbSize = sizeof(spdid);
 
 while ( true ){
  bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &devInterfaceData);
  if (!bRet) {
   break;
  }
  
  SetupDiEnumInterfaceDevice(hDevInfo, NULL, guid, dwIndex, &spdid);
  
  dwSize = 0;
  SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
  
  if ( dwSize!=0 && dwSize<=sizeof(Buf) ) {
   //pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
   //if ( pspdidd == NULL ) {
   //return 0; // damn
   //}
   pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
   
   ZeroMemory((PVOID)&spdd, sizeof(spdd));
   spdd.cbSize = sizeof(spdd);
   
   long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
   if ( res ) {
    
    // the device instance id string contains the serial number if the
    // device has one...
    // char szDevInstId[260] = {0};
    // SetupDiGetDeviceInstanceId(hDevInfo, &spdd, szDevInstId, 260, NULL);
    // printf("DevInstId=%s\n", szDevInstId);
    
    HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
    if ( hDrive != INVALID_HANDLE_VALUE ) {
     STORAGE_DEVICE_NUMBER sdn;
     DWORD dwBytesReturned = 0;
     res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
     if ( res ) {
      if ( DiskNumber == (long)sdn.DeviceNumber ) {
       CloseHandle(hDrive);
       //HeapFree(GetProcessHeap(), 0, pspdidd);
       SetupDiDestroyDeviceInfoList(hDevInfo);
       return spdd.DevInst;
      }
     }
     CloseHandle(hDrive);
    }
   }
   //HeapFree(GetProcessHeap(), 0, pspdidd);
  }
  dwIndex++;
 }
 
 SetupDiDestroyDeviceInfoList(hDevInfo);
 
 return 0;
}

首先得弄清楚同步、异步、阻塞、非阻塞的概念。

同步和异步是针对通讯的工作模式,阻塞和非阻塞是指socket的I/O操作。

实际上对于socket,只存在阻塞和非阻塞,同步与异步是在程序实现上有所不同。

在C++中,有三种类型的循环语句:for, while, 和do…while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do…while相对不受重视。

软考室的烟味弥漫 坐满了程序员

室里面的监考官 系分 已三年

出上午试题的老师 练CPU 耍单片机

硬件功夫最擅长 还会逻辑门三极管

他们学生我习惯 从小就耳濡目染

什么软件跟网络我都耍的有摸有样