DirectX Tutorials Уроки по DirectX

Отрывок из книги "Программирование для DirectX 7.0: учебный курс"
© Денис Гончаров, Тимур Салихов, 2001
Книга будет издана издательством Питер-Пресс в 2001 году.

Инициализация DirectPlay

Начальный шаг при создании любого приложения — инициализация. Есть два способа создания интерфейса IDirectPlay4.

Первый способ заключается в использовании функции DirectPlayCreate, которая создает интерфейс IDirectPlay. Неудобство ее состоит в том, что для получения интерфейса IDirectPlay4 необходимо использовать функцию QueryInterface(), а затем удалить старый интерфейс функцией Release().

Второй метод заключается в использовании функции CoCreateInstance(). При этом вы непосредственно создаете интерфейс IDirectPlay4 без привлечения промежуточных интерфейсов.

Мы рассмотрим оба метода создания интерфейса IDirectPlay4.

Функция DirectPlayCreate создает интерфейс IDirectPlay. Ее прототип представлен ниже.

HRESULT WINAPI DirectPlayCreate(
        LPGUID lpGUIDSP,
        LPDIRECTPLAY FAR *lplpDP,
        IUnknown *lpUnk
);

Первым параметром является указатель на уникальный идентификатор системного провайдера, который будет использоваться для передачи сообщении. Если вы по какой-либо причине не желаете его указывать, можете передать нулевой идентификатор — GUID_NULL.

Следующий параметр — указатель на интерфейс DirectPlay, который будет создан. При использовании данной функции возможно создание только интерфейса IDirectPlay. Для получения более высоких версий необходимо воспользоваться функцией QueryInterface().

Последний параметр в данной версии библиотеки не используется и должен быть равным нулю, иначе функция возвратит код ошибки — DPERR_INVALIDPARAMS.

Если интерфейс по какой-либо причине не может быть создан, функция возвратит ошибку DPERR_UNAVAILABLE. В случае успешного завершения функция возвратит DP_OK.

Как уже говорилось, для получения интерфейса версии выше, чем IDirectPlay, необходимо использовать функцию QueryInterface(). Следующий код демонстрирует пример создания интерфейса IDirectPlay и последующее получение интерфейса IDirectPlay4.

//Создание интерфейса IDirectPlay
hRet = DirectPlayCreate(GUID_NULL, lpdp, NULL);
//Получение интерфейса необходимой версии
hr = lpdp->QueryInterface(IID_IDirectPlay4, (VOID**)&lpdp4);
//Уничтожение ненужного интерфейса
lpdp->Release();

Следующий метод мы не будем подробно описывать, рассмотрим лишь пример его использования.

CoCreateInstance(CLSID_DirectPlay, NULL, CLSCTX_ALL,
                 IID_IDirectPlay4A, (VOID**)&g_pDP)

В параметрах функции CoCreateInstance указываются идентификатор класса DirectPlay и идентификатор интерфейса IDirectPlay4. Последний параметр — указатель на интерфейс, который будет создан.

Следует заметить, что в обоих методах создания интерфейса IDirectPlay в начале вызывается функция инициализации интерфейса CoInitialize().

Следующий пример демонстрирует один из методов создания интерфейса IDirectPlay.

//Создание интерфейса IDirectPlay
CoInitialize(NULL);
hRet = DirectPlayCreate(provider, &lpdp, NULL);
if (hRet!=DP_OK)//==DPERR_INVALIDPARAMS)
{
        ErrorHandle(hMainWnd,hRet,"DirectPlayCreate error!");
        return (FALSE);
}

//Получение интерфейса необходимой версии
hRet = lpdp->QueryInterface(IID_IDirectPlay4A, (VOID**)&lpdp4);

//Уничтожение ненужного интерфейса
lpdp->Release();

Перечисление и инициализация системных провайдеров

Для сетевых соединений DirectPlay предоставляет четыре провайдера: TCP/IP, IPX, модем и серийное соединение. Все они являются стандартными и присутствуют в любом случае, даже если протокола, который они используют, нет в наличии. Но вполне возможно, что в будущем появятся новые провайдеры, использующие новые протоколы. Для универсальности программы необходимо, чтобы она знала обо всех провайдерах, присутствующих в системе. Это возможно при использовании функций перечисления провайдеров.

DirectPlay перечисляет системные провайдеры при помощи двух функций: IDirectPlay4::DirectPlayEnumerate и IDirectPlay4::EnumConnections(). Эти функции перечисляют все зарегистрированные провайдеры, вне зависимости от того, работают они или нет. Отличие их в том, что первая перечисляет только системные провайдеры, а вторая в дополнение к системным — еще и лобби. К тому же, функция DirectPlayEnumerate перечисляет только провайдеры, а EnumConnection в дополнение к провайдерам — еще и возможные подключения.

HRESULT WINAPI DirectPlayEnumerate(
        LPDPENUMDPCALLBACK lpEnumCallback,
        LPVOID lpContext
);
HRESULT EnumConnections(
        LPCGUID lpguidApplication,
        LPDPENUMCONNECTIONSCALLBACK lpEnumCallback,
        LPVOID lpContext,
        DWORD dwFlags
);

Функция DirectPlayEnumerate() имеет всего два параметра — указатель на функцию, которая будет вызываться каждый раз, когда будет найден зарегистрированный провайдер, и указатель на структуру, при помощи которой этой функции будут переданы параметры (обычно устанавливается в NULL). Данная функция является абсолютной и работает со всеми версиями DirectX. Так как она не привязана ни к какому интерфейсу, это позволяет перечислять системные провайдеры до создания IDirectPlay.

Функция EnumConnections() имеет более широкие возможности и, как следствие, большее количество параметров. Первым ее параметром является указатель на уникальный глобальный идентификатор приложения. При этом будут перечислены все провайдеры, которые использует указанное приложение. Если вместо идентификатора приложения указан NULL, будут перечислены все присутствующие в системе провайдеры. Вторым параметром следует указатель на функцию, которая будет вызываться каждый раз, когда будет найдет провайдер. Параметры для нее будут передаваться через структуру, указатель на которую следует третьим параметром (обычно установлен в NULL). Четвертым, и последним, параметром являются флаги. По умолчанию установлен флаг DPCONNECTION_DIRECTPLAY, указывающий, что перечисляются только системные провайдеры. Для перечисления еще и лобби-провайдеров следует добавлять флаг DPCONNECTION_DIRECTPLAYLOBBY.

Для каждой из этих функций существует своя функция обратного вызова, которая будет вызываться каждый раз, когда будет найден провайдер. Эту функцию должен определить программист, DirectPlay предоставляет лишь ее прототип. Для начала разберем функцию EnumDPCallback(), используемую совместно с DirectPlayEnumerate().

BOOL FAR PASCAL EnumDPCallback(
        LPGUID lpguidSP,
        LPSTR/LPWSTR lpSPName,
        DWORD dwMajorVersion,
        DWORD dwMinorVersion,
        LPVOID lpContext
);

В ее параметрах передаются: указатель на уникальный идентификатор провайдера, имя провайдера (обратите внимание, что этот параметр, в зависимости от типа используемого интерфейса, может принимать значения как в стандарте Unicode, так и в кодировке ANSI), старшая и младшая составляющие версии провайдера.

Функция обратного вызова EnumConnectionCallback получает большее количество данных, так как вызывающая ее функция в дополнение к системным провайдерам перечисляет еще и возможные подключения.

BOOL FAR PASCAL EnumConnectionsCallback(
        LPCGUID lpguidSP,
        LPVOID lpConnection,
        DWORD dwConnectionSize,
        LPCDPNAME lpName,
        DWORD dwFlags,
        LPVOID lpContext
);

Первым параметром функции передается уникальный идентификатор провайдера. Затем следует указатель на буфер, содержащий информацию о подключении. Эта информация необходима при вызове функции IDirectPlay4::InitializeConnection и содержит DirectPlay-адрес сеанса. Размер этого буфера задается в третьем параметре функции. Четвертым параметром следует указатель на структуру типа DPNAME, в которой содержится имя соединения. В зависимости от типа интерфейса имя соединения может быть двух типов — Unicode и ANSI. Параметр dwFlags содержит флаги, которые были указаны при вызове EnumConnections().

При использовании функции DirectPlayEnumerate() инициализация системного провайдера происходит при создании интерфейса IDirectPlay с помощью функции DirectPlayCreate, в параметрах которой указывается, какой провайдер используется.

Функция EnumConnections() принадлежит интерфейсу IDirectPlay и поэтому не может быть вызвана до его создания. Чтобы использовать эту функцию, при создании интерфейса IDirectPlay необходимо вместо указателя на идентификатор провайдера передать NULL, а затем при помощи функции InitializeConnection(), прототип которой приведен ниже, указать, какой провайдер используется.

HRESULT InitializeConnection(
        LPVOID lpConnection,
        DWORD dwFlags
);

Первым параметром передается указатель на буфер, содержащий информацию о соединении. Вторым параметром следуют флаги, которые в данный момент не используются и предназначены для будущих версий.

Управление сеансом

Сеансом в DirectPlay называют канал связи между двумя или более приложениями. Вполне возможно, что на одном компьютере несколько приложений откроют несколько сеансов.

Открыть сеанс можно разными способами. Например, приложение может выполнить перечисление всех существующих сеансов и присоединяется к одному их них. Другая возможность заключается в создании собственного сеанса.

После того как приложение стало частью сеанса, появляется возможность создавать игроков и обмениваться сообщениями.

Каждый сеанс имеет сервер. Сервер является владельцем сеанса и только он может изменять настройки сеанса.

DirectPlay предоставляет набор функций для управлением сеансом. Рассмотрим их подробнее.

Перечисление сеансов

Функция IDirectPlay4::EnumSession() перечисляет все существующие сеансы. Ниже представлен ее прототип.

HRESULT EnumSessions(
        LPDPSESSIONDESC2 lpsd,
        DWORD dwTimeout,
        LPDPENUMSESSIONSCALLBACK2 lpEnumSessionsCallback2,
        LPVOID lpContext,
        DWORD dwFlags
);

Первый параметр функции — указатель на структуру DPSESSIONDESC2, в которой описываются параметры перечисляемых сеансов. При перечислении будут найдены только сеансы, соответствующие описанию, представленному в этой структуре. Поле guidApplication этой структуры указывает на приложение, с которым требуется установить сеанс. Если необходимость в определенном приложении отсутствует, указывается GUID_NULL. Если пользователь желает присоединиться к сеансу с определенным паролем, то этот пароль необходимо указать в поле lpszPassword. Все остальные поля структуры при перечислении сеансов игнорируются.

Следующий параметр — время ожидания ответа. При синхронном соединении этот параметр, задаваемый в миллисекундах, задает время ожидания ответа о существовании сеанса. При перечислении сеансов приложение блокируется на указанный в этом параметре промежуток времени, по истечении которого все ответы будут игнорироваться. При асинхронном соединении этот параметр определяет частоту рассылки широковещательных сообщений с запросом о существовании сеанса. Если этот параметр равен 0, время ожидания будет установлено в соответствии с параметрами используемого провайдера.

Третий параметр — указатель на функцию обратного вызова, которая будет вызываться каждый раз при обнаружении сеанса.

Четвертый параметр — указатель на структуру, через которую могут быть переданы данные функции обратного вызова. Обычно такие данные отсутствуют, и этот параметр устанавливается в NULL.

Последний параметр функции — флаги. По умолчанию он равен 0, что является равнозначным флагу DPENUMSESSIONS_AVAILABLE.

Если сеанс уже создан, функция возвратит ошибку DPERR_GENERIC. Ошибка DPERR_USERCANCEL означает, что пользователь прервал перечисление сеансов.

Функция EnumSessions() имеет два режима работы — синхронный и асинхронный.

По умолчанию EnumSessions() работает в синхронном режиме. При этом перед каждым перечислением функция очищает кэш и выполняет запрос о существовании сеансов. Ответы на запрос принимаются только в период, указанный в параметре dwTimeout. По истечении этого времени все ответы игнорируются. При перечислении сеансов приложение блокируется на время ожидания ответов.

При работе в асинхронном режиме кэш сеансов не очищается, а лишь обновляется. Обновление кэша происходит через промежутки времени, указанные в параметре dwTimeout. При этом происходит удаление сеансов, не отвечающих на запросы, и добавление в кэш новых, только что появившихся. Статус каждого сеанса, находящегося в кэше, будет обновлен.

Одним из параметров функции EnumSessions() является указатель на функцию обратного вызова. Эта функция определяется программистом, и будет вызываться каждый разпри обнаружении необходимого сеанса.

BOOL FAR PASCAL EnumSessionsCallback2(
        LPCDPSESSIONDESC2 lpThisSD,
        LPDWORD lpdwTimeOut,
        DWORD dwFlags,
        LPVOID lpContext
);

Первым параметром передается указатель на структуру DPSESSIONDESC2, в которой размещена информация о найденном сеансе. Если данный параметр равен NULL, это означает, что сеанс не был найден в течение отведенного на ожидание ответа промежутка времени. Если был найден сеанс с повышенной безопасностью, то такие поля как dwMaxPlayers и dwCurrentPlayers будут равны нулю.

Вторым параметром функции является указанный при вызове функции EnumSessions() период ожидания ответа от сеанса. Третий параметр — флаги. На данный момент существует только один флаг, эквивалентный нулю — DPESC_TIMEDOUT. Он указывает на то, что функция перечисления сеансов использует заданный период ожидания.

Открытие сеанса

Открытие сеанса подразумевает под собой как создание нового сеанса, так и подключение к уже существующему. Для открытия сеанса используется функция IDirectPlay4::Open, прототип которой представлен ниже.

HRESULT Open(
        LPDPSESSIONDESC2 lpsd,
        DWORD dwFlags
);

Первым параметром этой функции передается указатель на структуру типа DPSESSIONDESC2, в которой описываются установки сеанса. Если происходит присоединение к уже существующему сеансу, то заполняются только поля dwSize, guidInstance и lpszPassword. Если же создается новый сеанс, то обязательно заполнение всех полей этой структуры за исключением guidInstanse, которое заполняет DirectPlay.

Вторым параметром функции Open() следуют флаги., описанные в табл. 12.4

Таблица 12.4. Флаги метода Open()

Флаг Описание
DPOPEN_CREATE Создается новый сеанс. Локальный компьютер при этом объявляется как сервер имен.
DPOPEN_JOIN Присоединение к уже существующему сеансу. После этого появляется возможность создавать игроков и обмениваться сообщениями.
DPOPEN_RETURNSTATUS Использование данного флага указывает на то, что в момент присоединения не будет выведено диалоговое окно с текущим статусом. Если соединение не состоялось, функция возвратит ошибку DPERR_CONNECTING. При использовании данного флага необходимо выполнять вызов функции Open() до тех пор, пока она не возвратит DP_OK.

После присоединения к сеансу появляется возможность создания игроков и обмена сообщениями. Но следует заметить, что до тех пор, пока игрок не будет создан, приложение не сможет отправлять и принимать сообщения, так как информация об инициаторе действия отсутствует.

Для создания сеанса необходимо заполнить все поля структуры DPSESSIONDESC2 кроме поля guidInstance. Рассмотрим эту структуру подробнее.

typedef struct {
        DWORD dwSize;
        DWORD dwFlags;
        GUID guidInstance;
        GUID guidApplication;
        DWORD dwMaxPlayers;
        DWORD dwCurrentPlayers;
        union {
                LPWSTR lpszSessionName;
                LPSTR lpszSessionNameA;
        };
        union {
                LPWSTR lpszPassword;
                LPSTR lpszPasswordA;
        };
        DWORD dwReserved1;
        DWORD dwReserved2;
        DWORD dwUser1;
        DWORD dwUser2;
        DWORD dwUser3;
        DWORD dwUser4;
} DPSESSIONDESC2, FAR *LPDPSESSIONDESC2;

Первое поле структуры — ее размер. Он необходим для определения версий структуры и заполняется простым оператором sizeof().

Поле dwFlags содержит флаги, указывающие на параметры сеанса.

Поле guidInstance содержит уникальный идентификатор сеанса. При создании сеанса это значение автоматически генерируется DirectPlay, а при присоединении его необходимо знать.

Поле guidApplication содержит уникальный идентификатор приложения, открывшего сеанс. Если необходимо присоединиться к сеансам, созданным только определенными приложениями, это поле необходимо заполнить. В противном случае оно должно содержать GUID_NULL.

Поле dwMaxPlayers показывает максимальное количество игроков, которые могут быть созданы в данном сеансе. Значение 0 указывает на то, что ограничения отсутствуют. При открытии сеанса с повышенной безопасностью для неприсоединившихся к сеансу приложений значение данного поля также равно 0.

Поле dwCurrentPlayers показывает количество игроков, существующих в сеансе на данный момент. При открытии сеанса с повышенной безопасностью для неприсоединившихся к сеансу приложений значение данного поля равно 0.

Поле lpszSessionName является указателем на строку Unicode с именем сеанса. Используется только в интерфейсах Unicode.

Поле lpszSessionNameA является указателем на строку ANSI с именем сеанса. Используется только в интерфейсах ANSI.

Поле lpszPassword является указателем на строку Unicode, в которой хранится пароль сеанса. Используется только в интерфейсах Unicode.

Поле lpszPasswordA является указателем на строку ANSI, в которой хранится пароль сеанса. Используется только в интерфейсах ANSI.

Поля dwReserved1 и dwReserved2 являются зарезервированными и не используются.

Поля dwUser1, dwUser2, dwUser3, dwUser4 содержат пользовательские данные.

Закрытие сеанса

После окончания работы с сеансом его необходимо закрыть. Это делается для того, чтобы игроки получили информацию именно о закрытии соединения, а не об обрыве связи.

Для закрытия сеанса используется функция IDirectPlay4::Close().

HRESULT Close( );

При вызове этой функции происходит удаление всех локальных игроков. После этого происходит информирование всех остальных игроков о происшедшем событии при помощи системных сообщений DPMSG_DELETEPLAYERFROMGROUP и DPMSG_DESTROYPLAYERORGROUP.

Получение и информации о сеансе

Каждый сеанс характеризуется определенной информацией. Это могут быть сведения об аппаратных возможностях сеанса или такие параметры как имя сеанса и т. п.

Получение и установка свойств сеанса

Каждый сеанс имеет набор свойств, характеризующих его. Эти свойства задаются при открытии сеанса. Любой игрок в сеансе может получить их, а объект, открывший сеанс, еще и изменить.

Получение свойств сеанса осуществляется при помощи функции IDirectPlay4::GetSessionDesc().

HRESULT GetSessionDesc(
        LPVOID lpData,
        LPDWORD lpdwDataSize
);

Параметр lpData является указателем на буфер, хранящий структуру DPSESSIONDESC2, в которую будет помещена информация о сеансе.

В параметре lpdwDataSize передается указатель на переменную, в которую будет помещен размер созданного буфера.

Для изменения информации о сеансе используется функция IDirectPlay4::SetSessionDesc().

HRESULT SetSessionDesc(
        LPDPSESSIONDESC2 lpSessDesc,
        DWORD dwFlags
);

В параметре lpSessDesc передается указатель на структуру типа DPSESSIONDESC2, в которой хранятся новые настройки сеанса. Параметр dwFlags указывает флаги, но в данный момент не используется.

Дополнительная информация о сеансе

Кроме информации о текущих настройках сеанса, существует информация о его возможностях. Получить эти сведения можно при помощи функции IDirectPlay4::GetCaps().

HRESULT GetCaps(
        LPDPCAPS lpDPCaps,
        DWORD dwFlags
);

В параметре lpDPCaps передается указатель на структуру типа DPCAPS, в которую будет помещена информация о возможностях текущего сеанса.

Параметр dwFlags задает флаги и имеет одно возможное значение — DPGETCAPS_GUARANTED, указывающее на то, что запрос о возможностях сеанса будет производиться с гарантией доставки сообщений.

Структура DPCAPS имеет следующий вид:

typedef struct {
        DWORD dwSize;
        DWORD dwFlags;
        DWORD dwMaxBufferSize;
        DWORD dwMaxQueueSize;
        DWORD dwMaxPlayers;
        DWORD dwHundredBaud;
        DWORD dwLatency;
        DWORD dwMaxLocalPlayers;
        DWORD dwHeaderLength;
        DWORD dwTimeout;
} DPCAPS, FAR *LPDPCAPS;

Поле dwFlags содержит флаги, характеризующие объект DirectPlay.

В поле dwMaxBufferSize указывается максимальный размер одного пакета, который может отправить текущий провайдер. Если сообщение имеет большой размер, оно разбивается на несколько пакетов, не превышающих максимальной величины.

Поле dwMaxQueueSize в настоящий момент не используется. В предыдущих версиях оно означало максимальный размер очереди сообщений.

Поле dwMaxPlayers задает максимальное количество игроков, которые могут принимать участие в сеансе.

Поле dwHundredBaud содержит величину, указывающую пропускную способность канала, единица измерения которой равна 100 битам в секунду. Например, если это поле равно 14, то пропускная способность канала — 14000 бит в секунду.

Поле dwLatency показывает время в миллисекундах, за которое пакет доходит до своего адресата. Если поле равно 0, значит DirectPlay не смог определить это значение.

Поле dwMaxLocalPlayer содержит максимально возможное количество локальных игроков в сеансе.

Поле dwHeaderLength указывает размер заголовка, который DirectPlay присоединяет к каждому отправляемому сообщению. Эта величина зависит от типа используемого провайдера.

Поле dwTimeout содержит время, в течение которого происходит ожидание подтверждения доставки отправленного сообщения.

Управление игроками

Игрок в терминологии DirectPlay представляет собой логический объект, имеющий возможность отправлять и принимать сообщения. Любой сеанс может содержать в себе столько игроков, сколько позволяет пропускная способность сети. Одно приложение может создавать такое количество игроков, какое допускают настройки сеанса.

Каждый игрок при создании получает уникальный идентификационный номер. Данный номер принадлежит ему до тех пор, пока существует сеанс. Если сеанс был прерван и заново открыт, идентификационные номера игроков генерируются повторно.

Все игроки подразделяются на локальные и удаленные в зависимости от того, на локальном или удаленном компьютере они были созданы. Для отправки и приема сообщений компьютер должен иметь хотя бы одного созданного игрока. Также вполне возможно создание нескольких игроков на одном компьютере.

Любое сообщение, отправляемое игроком, предназначается не компьютеру, а конкретному игроку. Сообщение может быть адресовано как удаленному игроку, так и локальному, причем в последнем случае оно не пересылается по сети. Каждое сообщение, полученное приложением, передается игроку с указанием отправителя. При передаче системных сообщений отправитель указывается как DPID_SYSMSG.

Библиотека DirectPlay предоставляет набор функций управления игроками. Эти функции используются для создания игрока, его удаления, перечисления существующих игроков, получения и установки различных данных.

Перечисление существующих игроков

Иногда возникают ситуации, когда необходимо получить список всех игроков, присутствующих в сеансе. Для решения этой задачи библиотека DirectPlay предлагает функцию IDirectPlay4::EnumPlayers(). Данная функция занимается поиском всех пользователей в сеансе. Если в момент ее вызова нет открытых сеансов, возможно перечисление пользователей в удаленном сеансе, для чего необходимо только знать его идентификационный номер — guidInstance. Если удаленный сеанс имеет пароль, перечисление пользователей не представляется возможным. Прототип функции перечисления игроков следующий:

HRESULT EnumPlayers(
        LPGUID lpguidInstance,
        LPDPENUMPLAYERSCALLBACK2 lpEnumPlayersCallback2,
        LPVOID lpContext,
        DWORD dwFlags
);

Параметр lpguidInstance является указателем на уникальный идентификационный номер сеанса, игроки которого должны быть перечислены. При установке этого параметра в NULL происходит перечисление игроков текущего открытого сеанса. Если идентификационный номер указан, перечисляются игроки удаленного сеанса. Определение номера удаленного сеанса возможно при использовании функции EnumSessions(). Этот параметр игнорируется, если при вызове функции EnumSessions() не указан флаг DPENUMPLAYER_SESSION.

Параметр lpEnumPlayersCallback2 является указателем на функцию обратного вызова, обращение к которой происходит при нахождении игрока, удовлетворяющего указанным во флагах функции критериям.

Параметр dwFlags задает флаги, определяющие параметры поиска игроков. Только если игроки удовлетворяют критериям, указанным при помощи этих флагов, будет произведен вызов функции, адрес которой указан в параметре lpEnumPlayersCallback2.

Следует заметить, что совместное использование некоторых флагов лишено смысла. Так, например, если в качестве последнего параметра указать DPENUMPLAYERS_LOCAL | DPENUMPLAYERS_REMOTE, это будет означать, что ни локальные, ни удаленные игроки перечислены не будут. Вместо этого необходимо употребить значение по умолчанию.

Все параметры найденного игрока передаются функции обратного вызова. Ее прототип представлен ниже.

BOOL FAR PASCAL EnumPlayersCallback2(
        DPID dpId,
        DWORD dwPlayerType,
        LPCDPNAME lpName,
        DWORD dwFlags,
        LPVOID lpContext
);

Параметр dpId содержит уникальный идентификационный номер игрока или группы игроков, который присваивается им при создании.

Параметр dwPlayerType указывает, кто был найден — игрок (DPPLAYERTYPE_PLAYER) или группа игроков (DPPLAYERTYPE_GROUP).

Параметр lpName является указателем на структуру DPNAME, содержащую имя найденного пользователя.

Параметр dwFlags является флагами и описывает параметры игрока или группы игроков, которые были найдены.

Если при выходе из функции возвращается значение TRUE, перечисление игроков продолжается, а если FALSE, то прекращается.

Создание игрока

Для обмена сообщениями необходимо наличие игроков, которые будет их отправлять и получать. Каждый игрок при создании указывает свое имя и тип, а в ответ получает номер, позволяющий его однозначно идентифицировать.

Создание игрока производится при помощи функции IDirectPlay4::CreatePlayer().

HRESULT CreatePlayer(
        LPDPID lpidPlayer,
        LPDPNAME lpPlayerName,
        HANDLE hEvent,
        LPVOID lpData,
        DWORD dwDataSize,
        DWORD dwFlags
);

Параметр lpidPlayer является указателем на переменную, которая будет заполнена уникальным идентификационным номером, позволяющим опознать игрока. Если игрок по какой-либо причине покинул сеанс, его номер не будет использоваться до тех пор, пока сеанс не прекратится и не откроется заново.

В параметре lpPlayerName передается указатель на структуру типа DPNAME, содержащую имя создаваемого игрока. Если этот параметр равен NULL, создается игрок без имени. Имя не является обязательным параметром, поскольку никак не используется механизмами DirectPlay и не обязательно должно быть уникальным.

Параметр hEven является событийным объектом, которому будут передаваться уведомления о получении сообщений, предназначенных создаваемому игроку.

Параметр lpData является указателем на область данных, принадлежащих игроку. Если параметр равен NULL, это указывает на то, что игрок не имеет собственных данных. При создании, изменении или установке области данных ее содержимое будет передано всем игрокам текущего сеанса.

Параметр dwDataSize задает размер области данных, принадлежащих игроку.

В параметре dwFlags передаются флаги, определяющие тип создаваемого игрока. Если указан флаг DPPLAYER_SERVERPLAYER, игрок является серверным, а если использован флаг DPPLAYER_SPECTATOR, игрок является наблюдателем и не участвует в игровом процессе.

Удаление игрока

Если пользователь желает покинуть сеанс, его игрока необходимо удалить. При этом происходит снятие с очереди получения всех сообщений, адресованных этому игроку, а также исключение его из групп, в которые он входил. Идентификационный номер игрока не освобождается, но и не будет больше использоваться в текущем сеансе.

Удалить игрока может либо то приложение, которое его создало, либо сервер сеанса. Удаление игрока производится при помощи функции IDirectPlay4::DestroyPlayer().

HRESULT DestroyPlayer(
        DPID idPlayer
);

Единственный параметр этой функции — идентификационный номер игрока, который будет удален. Если игрок имеет в очереди отправления сообщения, он не может быть удален, пока не отправит их либо не отменит.

При вызове функции DestroyPlayer() производится рассылка системных сообщений DPMSG_DELETEPLAYERFROMGROUP и DPMSG_DESTROYPLAYERORGROUP.

Получение учетной записи игрока

При сеансах с повышенной безопасностью каждый игрок имеет учетную запись, которая необходима для аутентификации. Эта информация может быть получена сервером сеанса при помощи функции GetPlayerAccount().

HRESULT GetPlayerAccount(
        DPID idPlayer,
        DWORD dwFlags,
        LPVOID lpData,
        LPDWORD lpdwDataSize
);

Параметр idPlayer является идентификатором игрока, информацию о котором необходимо получить.

Параметр dwFlags указывает флаги и в данный момент не используется.

Параметр lpData является указателем на буфер, в который будет размещена информация об игроке. Чтение данных из буфера производится при помощи структуры DPACCOUNTDESC.

В параметре lpdwDataSize передается указатель на переменную, в которую будет занесен размер буфера для данных из учетной записи игрока. Если буфер окажется не достаточного размера, функция возвратит ошибку DPERR_BUFFERTOOSMALL. Для того чтобы узнать требуемый размер буфера, необходимо в параметре lpData передать NULL.

Получение адреса игрока

Каждый игрок имеет адрес, совпадающий с сетевым адресом компьютера, на котором игрок был создан. В некоторых случаях игрок может иметь несколько адресов, например, если на компьютере установлены сетевая карта и модем.

Получить адрес игрока можно при помощи функции IDirectPlay4::GetPlayerAddress().

HRESULT GetPlayerAddress(
        DPID idPlayer,
        LPVOID lpData,
        LPDWORD lpdwDataSize
);

Параметр idPlayer является идентификационным номером игрока, адрес которого необходимо получить. Установка этого параметра в 0 позволяет получить список возможных опций системного провайдера.

Параметр lpData является указателем на буфер, в который будет помещен адрес. Для того чтобы узнать размер буфера, необходимый для размещения в нем адреса, этот параметр устанавливается в NULL.

Параметр lpdwDataSize является указателем на переменную, в которую будет помещен размер созданного буфера. Если буфер окажется слишком мал, функция вернет ошибку PDERR_BUFFERTOOSMALL.

Получение данных игрока

Каждый игрок может иметь какие-либо данные. Это могут быть данные, определяемые приложением, дополнительные данные о типе или адресе игрока.

Данные, определяемые приложением

Эти данные хранятся в буфере, который создается при инициализации игрока. При одноранговых соединениях существует возможность получения данных любого игрока текущего сеанса. Для этого используется функция IDirectPlay4::GetPlayerData().

HRESULT GetPlayerData(
        DPID idPlayer,
        LPVOID lpData,
        LPDWORD lpdwDataSize,
        DWORD dwFlags
);

Параметр idPlayer является идентификационным номером игрока, данные которого будут получены. Игрок, данные которого запрашиваются, обязательно должен принадлежать тому же сеансу, что и игрок, получающий данные.

Параметр lpData является указателем на буфер, который будет заполнен запрашиваемыми данными игрока. Для того чтобы узнать размер буфера, необходимый для заполнения данными, этот параметр устанавливается в NULL.

Параметр lpdwDataSize является указателем на переменную, в которую будет помещен размер созданного буфера. Если буфер окажется слишком мал, функция вернет ошибку PDERR_BUFFERTOOSMALL.

Параметр dwFlags является флагами, указывающими, данные какого игрока будут получены — локального (DPGET_LOCAL) или удаленного (DPGET_REMOTE).

Дополнительные данные об игроке

Игроки бывают различных типов — локальные, серверные или наблюдатели. При помощи функции IDirectPlay4::GetPlayerFlags() возможно получение информации о типе игрока.

HRESULT GetPlayerFlags(
        DPID idPlayer,
        LPDWORD lpdwFlags
);

Параметр idPlayer является идентификационным номером игрока, тип которого необходимо узнать.

Параметр lpdwFlags является флагами, описывающими тип игрока. Всего на данный момент существует три флага: PDPLAYER_LOCAL, указывающий на то, что игрок является локальным, DPLAYER_SERVERPLAYER, сообщающий о том, что игрок создан на сервере, и DPPLAYER_SPECTATOR, указывающий, что игрок является наблюдателем и не участвует в игровом процессе.

Информация о возможностях игрока

Каждый игрок обладает возможностями, обусловленными как используемыми программными средствами, так и аппаратной частью компьютера. Узнать о возможностях игрока можно при помощи функции IDirectPlay4::GetPlayerCaps().

HRESULT GetPlayerCaps(
        DPID idPlayer,
        LPDPCAPS lpPlayerCaps,
        DWORD dwFlags
);

Параметр idPlayer является уникальным идентификационным номером игрока, информация о котором будет получена.

Параметр lpPlayerCaps является указателем на структуру типа DPCAPS, описывающую возможности игрока.

Параметр dwFlags является флагами и может принимать одно из двух значений: 0 (принятое по умолчанию), указывающее на то, что будут получены значения для сообщений без гарантии доставки, и DPGETCAPS_GUARANTEED, которое говорит о том, что будут получены значения для сообщений с гарантией доставки.

Получение и изменение имени игрока

Каждый игрок при создании указывает свое имя. Имя не является уникальным параметром и никак не используется механизмами DirectPlay. Для получения имени игрока применяется функция IDirectPlay4::GetPlayerName().

HRESULT GetPlayerName(
        DPID idPlayer,
        LPVOID lpData,
        LPDWORD lpdwDataSize
);

Параметр idPlayer является идентификационным номером игрока, имя которого необходимо узнать.

Параметр lpData является указателем на буфер, в который будет помещена информация об имени игрока, представленная в виде структуры DPNAME.

Параметр lpdwDataSize является указателем на переменную, в которую будет помещен размер созданного буфера.

Получить имя игрока может любой компьютер в сеансе. Изменить же его может только компьютер, на котором этот игрок был создан. Для изменения имени игрока используется функция IDirectPlay4::SetPlayerName().

HRESULT SetPlayerName(
        DPID idPlayer,
        LPDPNAME lpPlayerName,
        DWORD dwFlags
);

Параметр idPlayer является идентификационным номером игрока, имя которого будет изменено. Игрок обязательно должен быть локальным.

Параметр lpPlayerName является указателем на структуру DPNAME, хранящую имя, которое будет присвоено игроку. Если в этом параметре передать NULL, игрок будет без имени.

В параметре dwFlags передаются флаги, указывающие, каким образом игроки будут оповещаться об изменении имени. Всего существует три флага. Флаг DPSET_GUARANTEED указывает на то, что оповещение по возможности будет производиться с использованием гарантированной доставки. Флаг DPSET_LOCAL указывает на то, что об изменении имени будут оповещены только локальные игроки, те есть те, которые были созданы на том же компьютере, что и игрок, имя которого изменяется. Значение DPSET_REMOTE является флагом по умолчанию и указывает на то, что об изменении имени будут оповещены все игроки в текущем сеансе.

При изменении имени игрока производится рассылка системного сообщения DPMSG_SETPLAYERORGROUPENAME всем игрокам сеанса.

Сообщения

После того как будет открыт сеанс и созданы игроки, появляется возможность обмена данными между компьютерами посредством сети. Данные, передаваемые по сети между игроками, называются сообщениями.

Существуют сообщения двух типов: сообщения игроков и системные. Сообщения игроков — это данные, которыми игроки или группы обмениваются в процессе сеанса. Системные сообщения являются оповестительными и информируют о произошедших в сеансе изменениях.

Все сообщения, полученные компьютером, помещаются в очередь, из которой приложение производит выборку.

Из очереди сообщения могут быть получены двумя способами. Первый заключается в проверке очереди сообщений в главном цикле приложения. Обычно этот метод используют для однопоточных приложений.

Второй метод состоит в использовании различных потоков для получения сообщения и его обработки.

Системные и пользовательские сообщения

Как уже было сказано, сообщения подразделяются на два типа — пользовательские и системные. Системные сообщения распознаются по полю lpidFrom, которое в этом случае должно быть равно DPID_SYSMSG. Если же сообщение было послано игроком, это поле содержит идентификатор отправителя.

Все сообщения содержат какие-либо данные. В системных сообщениях — это информация о произошедшем событии. Она может быть определена при помощи констант, имеющих префикс DPSYS. Все эти константы описаны в табл. 12.10. Чтобы привести буфер сообщения к необходимому виду, используется структура DPMSG_GENERIC. Системное сообщение также содержит дополнительную информацию о произошедшем событии. Для ее получения буфер приводится к типу структуры, описывающей данное событие. Имя этой структуры совпадает с именем константы, полученной при помощи DPMSG_GENERIC, за исключение префикса, который заменяется на DPMSG. Следует заметить, что на самом деле буфер системного сообщения содержит именно структуру, хранящую данные о событии, а при помощи DPMSG_GENERIC мы просто получаем ее первое поле с информацией о типе этой структуры.

Таблица 12.10. Системные сообщения

Константа Описание
DPSYS_ADDGROUPTOGROUP Существующая группа была включена в другую группу в качестве подгруппы.
DPSYS_ADDPLAYERTOGROUP Существующий игрок был включен в группу.
DPSYS_CHAT Было получено текстовое сообщение (используется только в лобби).
DPSYS_CREATEPLAYERORGROUP Был создан новый игрок или группа.
DPSYS_DELETEGROUPFROMGROUP Присоединенная подгруппа была отсоединена от главной группы.
DPSYS_DELETEPLAYERFROMGROUP Игрок был исключен из состава группы.
DPSYS_DESTROYPLAYERORGROUP Произошло удаление игрока или группы.
DPSYS_HOST Текущий сервер имен покинул сеанс и был выбран новый сервер.
DPSYS_SECUREMESSAGE Было получено сообщение с цифровой подписью или зашифрованное сообщение.
DPSYS_SENDCOMPLETE Асинхронная пересылка завершена.
DPSYS_SESSIONLOST Связь с сеансом была потеряна.
DPSYS_SETGROUPOWNER Был установлен новый владелец группы (только в лобби).
DPSYS_SETPLAYERORGROUPDATA Была произведена установка определяемых приложением данных игрока или группы.
DPSYS_SETPLAYERORGROUPNAME Произошла смена имени игрока или группы.
DPSYS_SETSESSIONDESC Было изменено описание сеанса.
DPSYS_STARTSESSION Запрос лобби серверу на открытие сеанса (только в лобби).

Вид данных, представляющих сообщения игроков, определяется только тем приложением, которое отправило эту информацию. Обычно первым элементом этих данных является идентификатор сообщения. Это позволяет проверить очередность получения сообщений. Буфер после приема должен быть приведен к тому же типу данных, который использовался при пересылке сообщения.

Синхронные сообщения

За отправку сообщений отвечают две функции: IDirectPlay4::Send() и IDirectPlay4::SendEx(). Первая из них является более простой в использовании и отправляет сообщения только синхронно. При ее вызове происходит блокировка процессов приложения до тех пор, пока не будет получено подтверждение доставки. Прототип функции Send() показан ниже.

HRESULT Send(
        DPID idFrom,
        DPID idTo,
        DWORD dwFlags,
        LPVOID lpData,
        DWORD dwDataSize
);

Параметр idFrom определяет идентификационный номер игрока, отправляющего сообщение.

Параметр idTo задает идентификационный номер игрока, получающего сообщение. Если сообщение является широковещательным, вместо номера игрока указывают флаг DPID_ALLPLAYERS.

В параметре dwFlags передаются флаги, уточняющие особенности доставки сообщения. Значение DPSEND_GUARANTEED указывает, что доставка сообщения гарантируется. Флаг DPSEND_ENCRYPTED указывает, что сообщение зашифровано и будет оправлено как системное (DPSYS_SECUREMESSAGE). Флаг DPSEND_SIGNED указывает на то, что сообщение имеет цифровую подпись.

Параметр lpData является указателем на буфер, содержащий данные для отправки.

Параметр dwDataSize задает размер буфера, который будет отправлен.

Функция Send() может отправлять сообщения как с гарантией доставки, так и без. Отправка с гарантией доставки требует использования протокола DirectPlay или системного провайдера, поддерживающего такую возможность.

Если сообщение рассылается всем игрокам при помощи флага DPID_ALLPLAYERS, отправитель не получает его копии. Исключение составляет случай, когда указан флаг DPSESSION_NOMESSAGEID, при котором сообщения не имеют заголовка и их владельца невозможно определить (в качестве владельца указывается DPID_UNKNOWN).

Ниже показан пример использования функции Send().


lpdp4->Send(PlayerID, DPID_ALLPLAYERS, DPSEND_GUARANTEED,
          string, strlen(string));

В приведенном примере сообщение, содержащее текстовую строку, рассылается всем игрокам сеанса с использованием гарантии доставки.

Асинхронные сообщения

Возможность асинхронной связи освобождает приложение от блокировки во время отправки каждого сообщения. Также это позволяет приложению использовать мониторинг очереди сообщений и возможность отмены отправки сообщений.

Асинхронные сообщения возможны, только если сеанс использует протокол DirectPlay или при поддержке асинхронных сообщений системным провайдером. Проверить эту возможность системного провайдера можно с помощью функции GetCaps, указав при ее вызове флаг DPCAPS_ASYNCSUPPORTED.

При отправке асинхронного сообщения с использованием функции SendEx() происходит возврат в программу без ожидания уведомления об отправке сообщения и, если указана гарантированная доставка, о его получении. Позже, когда сообщение достигнет своего адресата, приложение получит системное сообщение PDMSG_SENDCOMPLETE. Если в получении этого системного сообщения нет необходимости, при отправке асинхронного сообщения нужно указать флаг DPSEND_NOSENDCOMPLETEMSG. Ниже приводится прототип функции SendEx() с описанием параметров.

HRESULT SendEx(
        DPID idFrom,
        DPID idTo,
        DWORD dwFlags,
        LPVOID lpData,
        DWORD dwDataSize,
        DWORD dwPriority,
        DWORD dwTimeout,
        LPVOID lpContext,
        LPDWORD lpdwMsgID
);

Параметры функций Send() и SendEx() схожи, поэтому мы опишем только те параметры, которые отличаются.

Параметр dwFlags имеет два дополнительных флага. Значение DPSEND_NOSENDCOMPLETEMSG указывает на то, что нет необходимости в системном сообщении, подтверждающем доставку. Флаг DPSEND_ASYNC указывает на отправку сообщения асинхронным методом.

Параметр dwPriority указывает на приоритетность сообщения. Приоритет влияет на очередность отправки сообщений, но никак не сказывается на его доставке. Самый высокий приоритет — 65535, самый низкий — 0. Оправка сообщений с использованием приоритета должна поддерживаться системным провайдером.

Параметр dwTimeout задает интервал ожидания отправки сообщения в миллисекундах. Если время, указанное в этом параметре прошло, а сообщение так и не достигло своего адресата, отправка автоматически отменяется. Если указан нулевой интервал ожидания, то значение этого параметра не используется.

Параметр lpdwMsgID является указателем на переменную, в которую будет помещен идентификационный номер сообщения, генерируемый DirectPlay. Этот номер может быть использован для отмены отправки сообщения. Если отменять сообщение не требуется, параметр может быть установлен в NULL.

При успешной доставке функция возвращает DP_OK для синхронных сообщений и DPERR_PENDING — для асинхронных.

Отмена сообщений и использование приоритетов возможны только при использовании асинхронного метода отправки. Причем отменить сообщение можно как при помощи идентификационного номера, генерируемого DirectPlay, так и при помощи приоритетов.

Для отмены сообщения необходимы несколько условий, таких как обязательное использование протокола DirectPlay и наличие отменяемого сообщения в очереди. Если сообщение является групповым и оно уже было получено хотя бы одним членом группы, отменить его нельзя.

Успешная отмена сообщения приводит к генерации системного сообщения PDMSG_SENDCOMPLETE, указывающего на завершение доставки.

Отмена сообщения с указанием его идентификационного номера возможна при использовании функции IDirectPlay4::CancelMessage():

HRESULT CancelMessage(
        DWORD dwMsgID,
        DWORD dwFlags
);

Параметр dwMsgID является идентификационным номером отменяемого сообщения, генерируемым DirectPlay.

Параметр dwFlags является флагами, которые в данной версии не используются.

Если удаление невозможно, функция возвращает код ошибки DPERR_CANCELFAILED. Если указан неверный идентификационный номер сообщения, возвращается код ошибки DPERR_UNKNOWNMESSAGE.

Кроме отмены сообщения по его идентификационному номеру, возможна отмена группы сообщений, имеющих определенный приоритет. Эту возможность реализует функция IDirectPlay4::CancelPriority():

HRESULT CancelPriority(
        DWORD dwMinPriority,
        DWORD dwMaxPriority,
        DWORD dwFlags,
);

Параметры dwMinPriority и dwMaxPriority указывают диапазон приоритетов сообщений, отправка которых будет отменена. Второй параметр обязательно должен быть больше, чем первый. Для указания максимального приоритета можно использовать константу MAXPRIORITY.

Параметр dwFlags является флагами и не используется в данной версии.

Получение сообщений

Все получаемые приложением сообщения помещаются в очередь для последующей выборки. Забрать сообщение из очереди можно при помощи функции IDirectPlay4::Receive(), прототип и описание которой представлены ниже.

HRESULT Receive(
        LPDPID lpidFrom,
        LPDPID lpidTo,
        DWORD dwFlags,
        LPVOID lpData,
        LPDWORD lpdwDataSize
);

Параметр lpidFrom является указателем на переменную, в которую будет помещен идентификационный номер игрока, отправившего сообщение. Если в качестве отправителя получено значение DPID_SYSMSG, сообщение является системным.

Параметр lpidTo является указателем на переменную, в которую будет помещен идентификационный номер игрока, которому предназначается сообщение.

Параметр dwFlags перечисляет флаги, уточняющие, как и какие именно сообщения будут получены. Флаг DPRECEIVE_ALL указывает на то, что будут получены все сообщения вне зависимости от получателя и отправителя. Флаг DPRECEIVE_PEEK указывает на то, что при получении сообщения оно не будет удалено из очереди. Флаги DPRECEIVE_TOPLAYER и DPRECEIVE_FROMPLAYER предписывают получить сообщения для игрока, указанного в параметре lpidTo, или от игрока, указанного в параметре lpidFrom соответственно.

Параметр lpData является указателем на буфер, в который будет помещено полученное сообщение.

Параметр lpdwDataSize является указателем на переменную, в которую будет помещен размер буфера, необходимый для получения сообщения.

Перед описанием работы функции Receive() мы приведем небольшой пример ее использования.

while (true)
{
        hRet=lpdp4->Receive(&recID, &fromID, DPRECEIVE_ALL,
                          message, &bufsize);
        if (hRet==DPERR_BUFFERTOOSMALL)
        {
                if (message!=NULL)
                {
                        delete message;
                        message=NULL;
                }
                message = new char [bufsize];
                if (message==NULL)
                {
                        MessageBox(NULL, "out of memory",
                         "error", MB_OK);
                        return (FALSE);
                }
                continue;
        };
        if (fromclass==DPID_SYSMSG)
                AddSysMessage(message)
        else
                AddMessage(playername->lpszShortNameA, message);
        if (hRet==DPERR_NOMESSAGES)
                return (TRUE);
        if (FAILED(hRet))
                return (FALSE);
}

Как вы уже заметили, выборка сообщений происходит в бесконечном цикле, который имеет два условия выхода из него — получение всех сообщений из очереди или возникновение ошибки.

Первый вызов функции Receive() практически всегда приводит к ошибке DPERR_BUFFERTOSMALL, так как мы не знаем необходимого размера буфера для получения сообщения. Повторный вызов функции должен увенчаться успехом, так как перед этим был создан буфер требуемого размера, полученного через параметр lpdwDataSize.

После получения сообщения необходимо проверить, не является ли оно системным. Если параметр lpidFrom равен DPID_SYSMSG, значит, получено системное сообщение. В противном случае это сообщением было отправлено другим игроком.

Получить из очереди сообщения для определенного игрока можно, указав флаг DPRECEIVE_TOPLAYER и поместив в переменную, на которую указывает параметр lpidTo, идентификационный номер получателя. Если же необходимо получить сообщение от определенного игрока, указывается флаг DPRECEIVE_FROMPLAYER, а идентификационный номер игрока помещается в переменную, на которую указывает параметр lpidFrom.

Отрывок из книги "Программирование для DirectX 7.0: учебный курс"
© Денис Гончаров, Тимур Салихов, 2001


 

PMG  1 Февраля 2001 (c)  Timur Salikhov