使用DirectPlay进行网络互联(1)

了解网络互联

网络是指多台计算机互联以进行数据传输及通信的系统。除了两个或更多的计算机之外,网路还需要有网络互联软件(或一个网路操作系统)、网络适配器以及电缆。网络适配器有各种形状和大小,但是一般都采用调制解调器的形状。实际上,调制解调器就是一个网路适配器,它能够将一台计算机通过世界上最大的网络---互联网连接到数百万台计算机上。

网路模型

网络互联模型有三种基本类型:服务器端、客户端以及点对点。

使用服务器端模型,可以建立一个中央网络互联系统。其他计算机使用客户端模型连接到服务器端后,就可以向服务器端发送数据以及从服务器端接收数据。客户端没有其他客户端的信息,不直接与它们连接,客户端都只知道服务器端的信息,而服务器端则拥有所有客户端的信息以及适合这些客户端之间的路由信息。服务器端和客户端常常成对进行描述,即服务器端/客户端(C/S)模型,但是在使用DirectPlay时,将二者分开是有必要的,因为服务器端和客户端是由两个独立的组件组成的。

点对点(peer-to-peer)这种网络互联模型与服务器端模型或客户端模型正好相反,计算机相互之间直接进行连接。每一台新的计算机加入网络会话中,都会建立一个新的连接,所以每台计算机都能直接连接到其他计算机。连接到网络的时段称为会话,一次会话会有与之相关的属性,如密码、最大连接数等。

游戏厅

可以将游戏厅服务器看作在线玩家的会议大厅,一个游戏厅允许所有玩家登录、通信以及加入他们喜爱的一些游戏。一旦游戏厅服务器连满了玩家,就停止对游戏厅的循环(这样做是为了节省网络带宽)。网络带宽(network bandwidth)指的是一个网络连接能够轻松处理的数据量,高网络带宽连接能比低网路带宽连接更快地处理大量的网络数据。

响应时间和延迟

带宽引出了两个术语:响应时间和延迟。响应时间是完成一个操作所花的时间(越低越好)的量化。延迟是用来描述网络通信的迟滞的术语,即数据从发送到它被接收到所花的时间。

低延迟表示网络数据迅速地被接收。高延迟(最不希望出现的事情)表示网络数据被延迟或者根本没有被发送到。延迟是一个主要问题,特别是使用了互联网时,就必须处理这个问题。

通信协议

网络可以有各种方式进行相互通信,但是要连接到另一个,两个系统都必须采用相同的协议。目前最流行的协议是TCP/IP协议(传输控制协议/Internet协议),它被广泛应用于互联网。通信协议也被称作服务器提供者,无论服务器提供者是一种诸如IPX、TCP/IP的协议,还是一种诸如调制解调器或者串行电缆的设备,都可以把它看作是网络互联的连接体。

TCP/IP协议是一种在网络上传输数据包的方法。它将数据分割成很多小数据包,再加上发送方和接收方的地址以及包的数目,便于重组数据。信息在传输过程中丢失(频繁发生的事情),TCP/IP允许网络重发数据包。当出现延迟时,这些数据包可能以错误的顺序被接收,比如旧的数据包在新的数据包之后到达。好在无需担心,因为TCP/IP会重发丢失的包并且重组无序的包。

寻址

在TCP/IP协议下,一个系统被分配一个由4个数字(0 -255之间)组成的网络地址(IP地址),数字之间用点分割。一个IP地址就像下面这样:

64.120.53.2

IP地址对我们来说不好辨认,但是网络却可以根据每一个数值成功地传递数据。稍加运算就会发现,这4个数字的组合总共可以提供4294967296个可能的地址。为了增加地址数量,网络使用附加的称为端口的地址值,数据被传送到其上。

如果将IP地址比喻成一个邮政室(mailroom),那么该邮政室(IP地址)描述了与网络相连的单个计算机系统,并且该计算机系统只被分配了惟一的IP地址。在邮政室中有很多箱柜(端口),邮件被分类到其中。每一个箱柜(端口)属于一个特定的工作人员(一个特定的应用程序)。一些应用程序可以拥有多个端口,数据只能被一个同相应IP地址以及相应端口所匹配的系统接收。一种称为数据路由器(data router)的设备将接收到的网络数据传送到它所知道的系统,或者将数据传送到另一个网络连接。

DirectPlay概述

要在工程中使用DirectPlay,必须包含DPlay8.h和DPAddr.h,还要链接DPlayX.lib和 DXGuid.lib。

网络对象

使用DirectPlay,需要使用前面提到的网络模型:客户端、服务器端和点对点。每一种网络模型都有自己的接口对象,如下所示:

IDirectPlay8Client:客户端网络对象,连接到一个服务器端。
IDirectPlay8Server:服务器端网络对象,连接多个客户端。
IDirectPlay8Peer:   点对点网络对象,连接客户端于其他客户端。
IDirectPlay8Address:包含(以及构造)网络地址的对象。

要连接到远程网络系统(或主持一次会话),需要使用 IDirectPlay8Address构造一个网络地址,IDirectPlay8Address的惟一用途就是构造并包含单个网络地址。会话指的是主持或加入一个可连接网路系统的时段,终止连接时,会话也就终止了。要主持游戏会话或连接到远程系统,首先需要创建网络对象并给它分配地址。要主持游戏会话,只需要等待其他系统(也就是使用这些系统的人)连接上来即可。连接建立之后,系统就可以和远程系统开始相互传输游戏相关的网络消息, DirectPlay把这些远程系统称作玩家。

将玩家进行分组

在DirectPlay术语中,“玩家”是指通过网络连接到其他计算机的单个连接(通常是一个游戏玩家)。一台计算机可以有多个玩家,但一般只有一个。实际上,服务器端也被标识为玩家,以便于识别。每一个玩家都会接收到一个标识号码(玩家ID),系统使用这个标识号码来将消息传递到每个玩家。这些号码是追踪玩家的惟一可信任方法,所以需要让程序对它们进行相应地处理。

对于大型游戏,可能有成千上万已连接的玩家。为了在游戏中更好地处理这些玩家,可以将一些或所有玩家编制到一些组中。采用组的概念可以减少编程的繁琐,主要原因在于可以将一个游戏区域(比如一张地图或某个级别)中的多个玩家划分为一组,并且一次向整个组发送网络数据,而不是单独发送给各个玩家。使用组还有很多其他原因,但这个原因是最主要的。一个组中包含多少玩家以及创建多少个组完全没有限制,组也可以属于其他组。

带消息的网络互联

消息是分了类的数据包,其中包含简单的结构。每一个消息都有特定的含义,且都有一个与之对应的宏,而且还和所使用的网络模型有关。要接收消息,网络对象必须给自己指定一个回调函数,以便于每次消息到达时进行调用。为了确保得到平滑的数据流,该函数根据数据类型进行分析并尽快地返回。发送消息,需要使用各个网络对象的发送函数。这些函数易于使用,并且提供了许多发送选项,包括保证发送(guaranteed delivery)、安全编码(secure encryption)以及同步或异步发送。

同步与异步

DirectPlay提供的第一个发送选项是同步或异步发送消息的功能,也就是说系统在发出发送数据指定之后交回控制权(异步),还是直到所有的数据都成功发送之后再交回控制权(同步)。很多时候使用异步方法,因为它不会像同步方法那样阻塞系统。

安全性

要引起注意的是任何时候都可能有人在截取并记录游戏的网络数据。因此,就需要采用一种安全的方式编码消息数据,使得那些解码的黑客非常难读懂先前的信息。使用安全网络发送的不好之处在于它会稍稍减慢系统的系统,因为必须在数据发送之前先编码信息,然后在接收到之后进行解码。

保证发送

就像一些快递公司保证送到包裹一样, DirectPlay也能保证送到消息。可以将一组信息标识为保证的,其余确定的就是DirectPlay将一直执行发送操作直到发送成功,以保证将其发送到目标地址(除非断线),使用保证发送是通过在调用函数时指定一个惟一的标志来实现的。保证发送的不好之处在于速度,保证发送在实际的游戏状况下非常慢,游戏采用UDP(用户数据报协议)发送方法,就不用关心数据是否被接收到(和TCP发送方法相反,它保证数据的发送)。

节流

有时系统会因为试图处理流数据而过载,尽管这样, DirectPlay有一个内置的消息节流器,它丢弃了发送队列中低优先级的信息。

使用GUID识别应用程序

如何从众多的网络应用程序中区分出自己的网络应用程序呢?解决这个问题的方法就是给自己的应用程序指定一个惟一的号码,并且只允许使用相同号码的应用程序相互进行连接。这个特殊的号码就是 Windows程序员熟悉的GUID(全局惟一标识符)。创建应用程序之前,花一点时间给它设置一个惟一的GUID,并且保证所有通过网络进行连接的该应用程序使用相同的GUID。

初始化网络对象

无论是服务器端、客户端还是单点对象,使用DirectPlay的第一步都是创建网络对象。要初始化每一个网络模型接口,必须使用CoCreateInstance函数,可能用到的类ID和引用标识符如下所示:

CLSID_DirectPlay8Address  IID_IDirectPlay8Address
CLSID_DirectPlay8Client IID_IDirectPlay8Client
CLSID_DirectPlay8Peer IID_IDirectPlay8Peer
CLSID_DirectPlay8Server IID_IDirectPlay8Server

无论创建的是什么网络对象(客户端、单点或服务器端),都需要创建与之匹配的网络回调函数,在网络消息被接收到的时候会调用该回调函数。

该回调函数使用说明如下:

PFNDPNMESSAGEHANDLER is an application-defined callback function used by the IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server IDirectPlay8LobbyClient and IDirectPlay8LobbiedApplication interfaces to process messages.

typedef HRESULT (WINAPI *PFNDPNMESSAGEHANDLER)( 
PVOID pvUserContext,
DWORD dwMessageType,
PVOID pMessage
);

Parameters

pvUserContext
Pointer to the application-defined structure that will be passed to this callback function. This is defined in the pvUserContext parameter of the Initialize method.
dwMessageType
One of the following message types that are generated by the IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server interfaces. Each interface uses a different subset of the available messages. Refer to the interface documentation for details.
  • DPN_MSGID_ADD_PLAYER_TO_GROUP
  • DPN_MSGID_ASYNC_OP_COMPLETE
  • DPN_MSGID_CLIENT_INFO
  • DPN_MSGID_CONNECT_COMPLETE
  • DPN_MSGID_CREATE_GROUP
  • DPN_MSGID_CREATE_PLAYER
  • DPN_MSGID_DESTROY_GROUP
  • DPN_MSGID_DESTROY_PLAYER
  • DPN_MSGID_ENUM_HOSTS_QUERY
  • DPN_MSGID_ENUM_HOSTS_RESPONSE
  • DPN_MSGID_GROUP_INFO
  • DPN_MSGID_HOST_MIGRATE
  • DPN_MSGID_INDICATE_CONNECT
  • DPN_MSGID_INDICATED_CONNECT_ABORTED
  • DPN_MSGID_PEER_INFO
  • DPN_MSGID_RECEIVE
  • DPN_MSGID_REMOVE_PLAYER_FROM_GROUP
  • DPN_MSGID_RETURN_BUFFER
  • DPN_MSGID_SEND_COMPLETE
  • DPN_MSGID_SERVER_INFO
  • DPN_MSGID_TERMINATE_SESSION

    Additionally, if the application supports Microsoft® DirectPlay® lobby functionality, this parameter can specify one of the following message types that are generated by the IDirectPlay8LobbyClient and IDirectPlay8LobbiedApplication interfaces. Each interface uses a different subset of the available messages. Refer to the interface documentation for details.

  • DPL_MSGID_CONNECT
  • DPL_MSGID_CONNECTION_SETTINGS
  • DPL_MSGID_DISCONNECT
  • DPL_MSGID_RECEIVE
  • DPL_MSGID_SESSION_STATUS
pMessage
Structure containing message information.

Return Values

See the documentation for the individual messages for appropriate return values. Unless otherwise noted, this function should return S_OK.

Remarks

This function must be threadsafe because it might be called reentrantly through multiple threads.

Callback messages from the same player are serialized. Once you receive a message from a player, you will not receive another until you have handled the first message, and the callback function has returned.

The message structures have the same name as the message type except the "DPN_MSGID" is replaces with "DPNMSG". For example, the DPN_MSGID_CONNECTION_TERMINATED message type uses the DPNMSG_CONNECTION_TERMINATED message structure to convey the actual message information.

When implementing this callback function, first look at the message type returned in the dwMessageType parameter and then cast the message structure (pMessage) to that type to obtain message information. Some messages don't have a defined structure because they have no parameters. For these messages, the pMessage parameter is NULL.


要初始化网络对象,需要调用Initialize函数。

Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client interface and from the server. This method must be called before calling any other methods of this interface.

HRESULT Initialize(
PVOID const
pvUserContext,
const PFNDPNMESSAGEHANDLER pfn,
const DWORD dwFlags
);

Parameters

pvUserContext
[in] Pointer to the user-provided context value in calls to the message handler. Providing a user-context value can be useful to differentiate messages coming from multiple interfaces to a common message handler.
pfn
[in] Pointer to a PFNDPNMESSAGEHANDLER callback function that receives all messages from the server, and receives indications of session changes from the IDirectPlay8Client interface.
dwFlags
[in] You may specify the following flag.
DPNINITIALIZE_DISABLEPARAMVAL
Disable parameter validation for the current object.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_INVALIDFLAGS
DPNERR_INVALIDPARAM

Remarks

This is the first method you should call after using CoCreateInstance to obtain the IDirectPlay8Client interface.


下面这个函数创建了DirectPlay Client对象并且初始化该对象。

//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
    
// create DirectPlay client component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client, 
                               (
void**)&g_dp_client)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

指定设备

虽然已经选择了一个服务提供者,但系统中可能有一个以上的设备使用它。当一个网络适配器和调制解调器都连接到互联网,并且都使用TCP/IP协议时,就会出现这种情况,这是就必须对设备进行枚举并进行选择。

枚举函数获取了所有可用的服务提供者,并将它们以可用的方式组织在一起。DirectPlay枚举和典型的 Windwos枚举方法有所不同,在查询过程中找到实例对象并不是每次都调用回调函数,而是从包含了以数组形式存储的每个服务提供者的缓冲区中寻找。

处理枚举可以使用EnumServiceProviders函数。

Enumerates the registered service providers available to the application.

HRESULT EnumServiceProviders(
const GUID *const
pguidServiceProvider,
const GUID *const pguidApplication,
DPN_SERVICE_PROVIDER_INFO *const pSPInfoBuffer,
PDWORD const pcbEnumData,
PDWORD const pcReturned,
const DWORD dwFlags
);

Parameters

pguidServiceProvider
[in] Pointer to a variable of type GUID that specifies a service provider. This optional parameter forces the enumeration of subdevices for the specified service provider. You should normally set this value to NULL, to enumerate all available service providers.
pguidApplication
[in] Pointer to a variable of type GUID that specifies an application. If a pointer is passed in this parameter, only service providers who can be connected to the application are enumerated. You can also pass NULL to enumerate the registered service providers for the system.
pSPInfoBuffer
[out] Pointer to an array of DPN_SERVICE_PROVIDER_INFO structures that will be filled with service provider information.
pcbEnumData
[out] Pointer to DWORD, which is filled with the size of the pSPInfoBuffer array, in bytes, if the buffer is too small.
pcReturned
[out] Pointer to a variable of type DWORD that specifies the number of DPN_SERVICE_PROVIDER_INFO structures returned in the pSPInfoBuffer array.
dwFlags
[in] The following flag can be specified.
DPNENUMSERVICEPROVIDERS_ALL
Enumerates all the registered service providers for the system, including those that are not available to the application or do not have devices installed.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_BUFFERTOOSMALL
DPNERR_INVALIDPARAM

Remarks

Call this method initially by specifying NULL in the pguidServiceProvider parameter to determine the base service providers available to the system. Specific devices for a service provider can be obtained by passing a pointer to a service provider GUID in the pguidServiceProvider. This is useful, for example, when using the Modem Connection for Microsoft® DirectPlay® service provider. You can choose among different modems for dialing out and select specific modems for hosting.

If the pEnumData buffer is not big enough to hold the requested service provider information, the method returns DPNERR_BUFFERTOOSMALL and the cbEnumData parameter contains the required buffer size. Typically, the best strategy is to call the method once with a zero-length buffer to determine the required size. Then call the method again with the appropriate-sized buffer.

Normally, this method will return only those service providers that can be used by the application. For example, if the IPX networking protocol is not installed, DirectPlay will not return the IPX service provider. To have DirectPlay return all service providers, even those that cannot be used by the application, set the DPNENUMSERVICEPROVIDERS_ALL flag in dwFlags.


pSPInfoBuffer是指向DPN_SERVICE_PROVIDER_INFO结构体数组的指针,该函数将用枚举的信息填充该结构体。

Used when enumerating information for a specific service provider.

typedef struct _DPN_SERVICE_PROVIDER_INFO{
DWORD dwFlags;
GUID guid;
WCHAR* pwszName;
PVOID pvReserved;
DWORD dwReserved;
} DPN_SERVICE_PROVIDER_INFO, *PDPN_SERVICE_PROVIDER_INFO;

Members

dwFlags
Reserved. Must be 0.
guid
GUID for the service provider.
pwszName
Name of the service provider.
pvReserved
Reserved. Must be 0.
dwReserved
Reserved. Must be 0.

下面这个函数演示了如何枚举系统中的服务提供者:

HWND g_hwnd;    // window handles

IDirectPlay8Client
* g_dp_client;    // directplay client

// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO*  g_sp_list;  

DWORD g_sp_number;      
// service provider number

//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
    
// return is no server object
    if(g_dp_client == NULL)
        
return;

    
// get a handler to the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    
// release service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    
// query the required size of the data buffer
    
    DWORD sp_list_size 
= 0;

    
// enumerates the registerd service providers available to the application
    HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[sp_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
    {
        
// enumeration is complete, scan through entries.

        
char sp_name[1024];

        DPN_SERVICE_PROVIDER_INFO
* sp_ptr = g_sp_list;
        
        
for(DWORD i = 0; i < g_sp_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(sp_name, sp_ptr->pwszName, 1024);

            
// Add the service provider into box
            
//
            
// An application sends an LB_ADDSTRING message to add a string to a list box. 
            
// If the list box does not have the LBS_SORT style, the string is added to the end of the list. 
            
// Otherwise, the string is inserted into the list and the list is sorted.  
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);

            
// go to next service provider in buffer
            sp_ptr++;
        }
    }
}

下面给出一个完整实例来运用刚才介绍的知识,由于DirectX9 SDK已不包含DirectPlay,所以需要安装DirectX8 SDK,并在Visual Studio中进行相应设置。

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    Enum Network Adapters Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<dplay8.h>
#include 
<dpaddr.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dplayx.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "EnumClass";

IDirectPlay8Client
* g_dp_client;    // directplay client

// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO*  g_sp_list;  

DWORD g_sp_number;      
// service provider number

//----------------------------------------------------------------------------------------
// callback function that receives all messages from the server, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
    
// create DirectPlay client component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client, 
                               (
void**)&g_dp_client)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
    
// return is no server object
    if(g_dp_client == NULL)
        
return;

    
// get a handler to the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    
// release service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    
// query the required size of the data buffer
    
    DWORD sp_list_size 
= 0;

    
// enumerates the registerd service providers available to the application
    HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[sp_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
    {
        
// enumeration is complete, scan through entries.

        
char sp_name[1024];

        DPN_SERVICE_PROVIDER_INFO
* sp_ptr = g_sp_list;
        
        
for(DWORD i = 0; i < g_sp_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(sp_name, sp_ptr->pwszName, 1024);

            
// Add the service provider into box
            
//
            
// An application sends an LB_ADDSTRING message to add a string to a list box. 
            
// If the list box does not have the LBS_SORT style, the string is added to the end of the list. 
            
// Otherwise, the string is inserted into the list and the list is sorted.  
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);

            
// go to next service provider in buffer
            sp_ptr++;
        }
    }
}

//--------------------------------------------------------------------------------
// Enumerate all adapters which specified by sp_guid.
//--------------------------------------------------------------------------------
void Enum_Adapters(GUID* sp_guid)
{
    
// return if no server object or GUID
    if(g_dp_client == NULL || sp_guid == NULL)
        
return;

    
// get a handle of the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_ADAPTERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    DPN_SERVICE_PROVIDER_INFO
* adapter_list = NULL;
    DWORD adapter_number 
= 0;
    DWORD adapter_list_size 
= 0;

    
// query the required size of the data buffer
    HRESULT rv = g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((adapter_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[adapter_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0)))
    {
        
char adapter_name[1024];

        
// enumeration is complete, scan through entries.
        DPN_SERVICE_PROVIDER_INFO* adapter_ptr = adapter_list;

        
for(DWORD i = 0; i < adapter_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(adapter_name, adapter_ptr->pwszName, 1024);

            
// add the adapter name int listbox
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)adapter_name);

            
// go to next servicec provider
            adapter_ptr++;
        }
    }
    
    
// delete the list memory resources
    delete[] adapter_list;
}

//--------------------------------------------------------------------------------
// Release all resource which allocated for DirectPlay.
//--------------------------------------------------------------------------------
void Release_DirectPlay()
{
    
// release client service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    g_sp_number 
= 0;

    
// release client component
    if(g_dp_client != NULL)
    {
        
// Closes the open connection to a session. This method must be called on any object that is successfully 
        
// initialized with a call to the IDirectPlay8Client::Initialize method.
        g_dp_client->Close(0);

        g_dp_client
->Release();

        g_dp_client 
= NULL;
    }
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_COMMAND:
        
// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a 
        
// notification message to its parent window, or when an accelerator keystroke is translated. 
        
//
        
// wParam:
        
//    The high-order word specifies the notification code if the message is from a control. 
        
//    If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. 
        
//    The low-order word specifies the identifier of the menu item, control, or accelerator. 
        
//
        
// lParam:
        
//     Handle to the control sending the message if the message is from a control. 
        
//     Otherwise, this parameter is NULL. 

        
// An application sends the LBN_SELCHANGE notification message when the selection in a list box is about to 
        
// change. The parent window of the list box receives this notification message through the WM_COMMAND message. 
        if(LOWORD(wParam) == IDC_SERVICE_PROVIDERS && HIWORD(wParam) == LBN_SELCHANGE)
        {
            
// Get the selection from the list
            
//
            
// Send an LB_GETCURSEL message to retrieve the index of the currently selected item, 
            
// if any, in a single-selection list box.         
            unsigned int selected = (unsigned int) SendMessage(GetDlgItem(hwnd, IDC_SERVICE_PROVIDERS), LB_GETCURSEL, 00);

            
// enumerate the adapters
            DPN_SERVICE_PROVIDER_INFO* sp_ptr = g_sp_list;
            sp_ptr 
+= selected;
            
            
// enumerate adapter with specified guid
            Enum_Adapters(&sp_ptr->guid);
        }

        
return 0;

    
case WM_DESTROY:
        Release_DirectPlay();
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_ENUM), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// Initialzie DirectPlay and enumerate service providers.
    if(! Init_DirectPlay_Client())
    {
        MessageBox(NULL, 
"Error initializing DirectPlay.""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
return 0;
    }

    
// enumerate all available service provider
    Enum_Service_Provider();

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

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

    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

运行截图:



posted @ 2007-08-06 19:50  至尊王者  阅读(2897)  评论(0编辑  收藏  举报