Оценка 215
[+1/-1]
Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|
| Введение Этапы создания приложения Реализация простейшей программы Заключение | ![]() |
Сейчас в Интернете, книгах, журналах и других разнообразных изданиях очень мало пишут о DirectX, поэтому желающий использовать возможности DirectX в своих программах попросту не может найти информации. Удается найти материалы, посвященные Direct3D и DirectInput, но о других частях DirectX, таких, скажем, как DirectPlay, нет практически ничего.
Цель этой статьи – хоть как-то поправить такое положение и описать основные аспекты создания сетевых приложений, использующих DirectX.
Перечислим основные этапы создания сетевого приложения на основе DirectPlay:
Последний этап – разрыв соединения, очищение памяти, корректное завершение программы.
Для примера, описанного в статье, взята архитектура peer-to-peer, по-другому – клиент-клиент. Она рекомендуется для применения при соединении не более 2 пользователей, однако, в документации по DirectX максимальное количество пользователей определяется в диапазоне от 20 до 30. При создании такого соединения каждый из клиентов должен послать соответствующие сообщения другим клиентам сети, причем каждому лично. Именно эта особенность и сказывается на скорости передачи данных, делая ее сравнительно небольшой.
Как вы уже, наверно, заметили из сказанного выше, для организации соединения архитектуры peer to peer необходимо создать хост. В данном случае этот хост будет отвечать только за добавление новых игроков в уже созданную сессию, то есть хост будет передавать новому клиенту адреса уже подсоединенных к сессии компьютеров, а тем, в свою очередь, адрес нового клиента, создавая тем самым связь между всеми участниками сессии.
| СОВЕТ Можно также построить приложение в соответствии с требованиями архитектуры «клиент – сервер» (Client/Server). При этом в сети должен быть компьютер (сервер), к которому будут подключаться клиенты. В отличие от хоста в архитектуре peer to peer, сервер будет выполнять вычисления для всех клиентов и вся информация, передающаяся из клиентского приложения и в клиентское приложение, будет проходить через сервер и обрабатываться им. Главным требованием при использовании такой архитектуры является наличие достаточно мощного для решения этой задачи компьютера, исполняющего задачи сервера. |
Как и в других компонентах DirectX, программирование DirectPlay основывано на использовании COM интерфейсов. Этот подход является очень удобным, так как все действия, производимые в процессе выполнения программы, осуществляются через указатели на определенные интерфейсы. Так, например, для каждой архитектуры, будь то клиент – клиент или клиент – сервер, существует главный интерфейс, через указатель на который и осуществляется работа приложения. Необходимо также отметить и вспомогательные интерфейсы, играющие немаловажную роль.
Главным интерфейсом для приложений, использующих архитектуру клиент – клиент является IDirectPlay8Peer. Естественно, для того чтобы им воспользоваться, необходимо использовать заголовочный файл dplay8.h. (Заметьте, что имя интерфейсов DirectX начинается на «I»). Включим в код программы определение указателя на этот интерфейс:
// Главный интерфейс Peer to Peer(при создании делается равным NULL)
IDirectPlay8Peer* lpPeer = NULL;
|
Именно через функции этого интерфейса и будут происходить основные действия. Но для начала разберем функцию, создающую сам COM объект:
CoCreateInstance (CLSID_DirectPlay8Peer, // Идентификатор класса объекта NULL, // должен быть установлен в NULL CLSCTX_INPROC_SERVER, // Указывает на то, что объект// реализован как DLL. IID_IDirectPlay8Peer, // Идентификатор интерфейса (LPVOID*) &lpPeer); // Ранее объявленный указатель на// интерфейс |
После создания объект необходимо инициализировать, причем функция инициализации должна быть использована первой из всех функций интерфейса
lpPeer ->Initialize(PVOID // Информация о пользователе(обычно делается равной NULL)const pvUserContext, // Имя Функции, получающей сообщения от других клиентовconst PFNDPNMESSAGEHANDLER pfn, // Спецификация соединения (обычно делается равной NULL)const DWORD dwFlags); |
Особенно здесь нужно обратить внимание на второй параметр, так как именно эта функция будет отвечать за обработку всех сообщений после успешного соединения. Прототип этой функции следует объявить в самом начале кода, а сама она должна выглядеть примерно так (имя функции можно выбирать произвольно, главное чтобы тип и число аргументов совпадали бы с описанными ниже):
HRESULT WINAPI DirectPlayMessageHandler(PVOID pvUserContext,
DWORD dwMessageId, PVOID pMsgBuffer)
{
return S_OK;
}
|
Тело функции обычно представляет собой большой switch(), который обрабатывает поступающие в функцию сообщения, идентификатор которых равен dwMessageId. Функция обрабатывает данные, на которые указывает параметр pMsgBuffer.
Теперь необходимо получить список провайдеров и устройств, через которое можно будет создать подключение. Ее можно решить, используя в программе следующую функцию:
lpPeer ->EnumServiceProviders( const GUID *const pguidServiceProvider, const GUID *const pguidApplication, const DPN_SERVICE_PROVIDER_INFO *const pSPInfoBuffer, DWORD *const pcbEnumData, DWORD *const pcReturned, const DWORD dwFlags); |
Первый аргумент является указателем на GUID провайдеров, информацию о которых мы хотим получить.
Ниже приведена таблица, в которой рассмотрены провайдеры и соответствующие им идентификаторы:
| Название провайдера | Идентификатор |
|---|---|
| Протокол TCP/IP | CLSID_DP8SP_TCPIP |
| Протокол IPX | CLSID_DP8SP_IPX |
| Модем | CLSID_DP8SP_MODEM |
| Соединение через Serial кабель | CLSID_DP8SP_SERIAL |
| BlueToth (Новинка) | CLSID_DP8SP_BLUETOOTH |
| Симулятор сети TCP/IP | CLSID_NETWORKSIMULATOR_DP8SP_TCPIP |
Второй аргумент является указателем на GUID приложения. Если этот параметр указан, то функция будет перечислять только тех провайдеров, которые могут устанавливать соединения с данным приложением. Если же этот аргумент равен NULL, то функция перечислит всех зарегистрированных провайдеров. Третьим параметром является указатель на структуру типа DPN_SERVICE_PROVIDER_INFO. Об этой структуре мы будем говорить ниже, а пока замечу, что после выполнения функции этот указатель будет указывать на первую из массива структур, а в каждую из этих структур будет записана информация, идентифицирующая одного из провайдеров.
Четвёртым параметром является указатель на буфер, в который будет записана вся требуемая информация о каждом из провайдеров. При этом в первом двойном слове этого буфера при вызове функции должен быть записан размер этого буфера. В том случае, если размер буфера слишком мал, функция возвращает значение DPNERR_BUFFERTOOSMALL и в первое двойное слово буфера записывает размер, необходимый для размещения всей информации.
Следующий, пятый параметр указывает на двойное слово, в которое должно быть записано число провайдеров, информацию о которых мы хотим получить. И, наконец, в качестве последнего аргумента может быть указан NULL или флаг DPNENUMSERVICEPROVIDERS_ALL. В последнем случае перечисляться будут в том числе и провайдеры, которые в момент вызова функции недоступны, и устройства для взаимодействия с которыми в системе не установлены. В большинстве случаев этот флаг не используется.
К моменту вызова функции требуемый размер буфера и число провайдеров могут быть неизвестны. Поэтому обычно функцию перечисления провайденров используют следующим образом. При первом вызове функции указывают в первом двойном слове буфера записывают нуль, то есть указывают что размер буфера равен нулю. При этом функция возвращает DPNENUMSERVICEPROVIDERS_ALL и в первое двойное слово буфера записывает необходимый размер буфера. После этого выделяется буфер необходимого размера, и функция вызывается второй раз. После второго вызова функции мы получаем всю необходимую для работы информацию. В MSDN можно найти пример подобного использования функции. Я привожу этот пример ниже:
DPN_SERVICE_PROVIDER_INFO* pdnSPInfo = NULL; DPN_SERVICE_PROVIDER_INFO* pdnSPInfoEnum = NULL; DWORD dwItems = 0; DWORD dwSize = 0; // Determine the required buffer size hr = g_pDP->EnumServiceProviders(NULL, NULL, NULL, &dwSize, &dwItems, 0); pdnSPInfo = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[dwSize]; //Fill the buffer with service provider information hr = g_pDP->EnumServiceProviders(NULL, NULL, pdnSPInfo, &dwSize, &dwItems, 0) // Print the provider descriptions pdnSPInfoEnum = pdnSPInfo; for(DWORD i = 0; i < dwItems; i++) { printf("Found Service Provider: %S\n", pdnSPInfoEnum->pwszName); pdnSPInfoEnum++; } |
Тип DPN_SERVICE_PROVIDER_INFO описан следующим образом:
typedef
struct _DPN_SERVICE_PROVIDER_INFO {
DWORD dwFlags; // Флаг использования симулятора сети
GUID guid; // Идентификатор провайдера
WCHAR *pwszName; // Имя провайдера
PVOID pvReserved; // Зарезервирован – должен быть NULL
DWORD dwReserved; // Зарезервирован – должен быть NULL
} DPN_SERVICE_PROVIDER_INFO, *PDPN_SERVICE_PROVIDER_INFO;
|
Таким образом, эти данные позволяют однозначно идентифицировать всех провайдеров.
Разобранная нами функция носит универсальный характер, то есть с помощью ее можно выполнять несколько полезных действий, а не только получать список провайдеров. Используя ее по-разному, можно получить список устройств, проверить в рабочем ли состоянии находится провайдер. Подробнее об этом можно узнать из подраздела «Практика».
Итак, появляется вопрос, как создать хост или подключиться к выбранному хосту, используя то или иное устройство. Для этих целей используются два метода, вызываемые через указатель на интерфейс IDirectPlay8Peer. Первый из них, создающий хост, описан следующим образом:
// Создание хоста
HRESULT Host(
// Специальная структура, описывающая приложениеconst DPN_APPLICATION_DESC *const pdnAppDesc,
// Указатель на интерфейс (Адрес устройства)
IDirectPlay8Address **const prgpDeviceInfo,
// Указывает на количество объектов в rgpDeviceInfoconst DWORD cDeviceInfo,
// Зарезервировано - NULLconst DPN_SECURITY_DESC *const pdpSecurity,
// Зарезервировано - NULLconst DPN_SECURITY_CREDENTIALS *const pdpCredentials,
// Необязательный параметр – увеличивается при присоединение клиента
VOID *const pvPlayerContext,
// Флагconst DWORD dwFlags);
|
Рассмотрим параметры по порядку. Первым аргументом является специальная структура DPN_APPLICATION_DESC, многочисленные поля которой характеризуют создаваемую сессию:
typedef
struct _DPN_APPLICATION_DESC {
DWORD dwSize;
DWORD dwFlags;
GUID guidInstance;
GUID guidApplication;
DWORD dwMaxPlayers;
DWORD dwCurrentPlayers;
WCHAR *pwszSessionName;
WCHAR *pwszPassword;
PVOID pvReservedData;
DWORD dwReservedDataSize;
PVOID pvApplicationReservedData;
DWORD dwApplicationReservedDataSize;
} DPN_APPLICATION_DESC, *PDPN_APPLICATION_DESC;
|
Сразу необходимо отметить, что большинству полей этой структуры редко присваивают значения, отличные от принятых по умолчанию. Итак, первое поле должно содержать размер структуры в байтах, вычисляемый функцией sizeof(). Второе поле указывает, какой именно тип сессии будет создан, чаще всего используются следующие значения:
Третье поле структуры должно содержать GUID, который генерирует DirectPlay при запуске сессии. Однако для метода Host это поле значения не имеет, так как этот метод игнорирует данное поле.
В четвертый параметр нужно передать GUID приложения, который необходимо индивидуально сгенерировать для приложения.
Пятое поле, dwMaxPlayers, содержит максимальное число клиентов.
Следующее за ним поле dwCurrentPlayers содержит текущее количество пользователей в сессии, это поле не нужно заполнять самостоятельно.
Седьмое поле, pwszSessionName, должно содержать имя сессии, например, полученное от пользователя.
Наконец, в восьмое поле pwszPassword передается пароль для доступа к сессии. Нужно отметить, что пароль используется в том случае, если выставлен флаг DPNSESSION_REQUIREPASSWORD.
Остальные же четыре поля этой структуры зарезервированы и не используются.
Со вторым параметром дело обстоит куда сложнее. Как видно, здесь требуется указатель на массив вспомогательных объектов, реализующих интерфейс IDirectPlay8Address. Применительно к методу Host этот массив должен содержать адреса устройств, которые будут использоваться для хостинга приложений. DirectPlay предоставляет стандартную реализацию вспомогательного объекта, реализующего IDirectPlay8Address. Его CLSID – CLSID_DirectPlay8Address.
Третий параметр функции Host() – это двойное слово, в которое необходимо передать количество объектов в массиве cDeviceInfo, то есть количество устройств.
Четвертый и пятый параметр – это указатели на структуры DPN_SECURITY_DESC и DPN_SECURITY_CREDENTIALS, которые зарезервированы для дальнейшего использования.
Шестой параметр функции – флаг. Единственное значение, которое он может принимать – DPNHOST_OKTOQUERYFORADDRESSING. Оно означает, что если пользователь ввел недостаточно информации для создания хоста, появится диалоговое окно, позволяющее завершить настройку.
О вспомогательном интерфейсе IDirectPlay8Address необходимо рассказать подробнее. Перед началом работы с объектом, реализующим этот интерфейс, нужно указать, для какого провайдера будет создан адрес устройствa. Сделать это можно с помощью метода SetSP. При этом провайдер задается с помощью GUID-а.
HRESULT SetSP( // Возможные варианты GUID’ов приведены в таблице 1const GUID *const pguidSP); |
Идентификатор устройства задается методом SetDevice.
HRESULT SetDevice( // Идентификатор устройства, полученный фун-ей EnumServiceProvidersconst GUID *const pguidDevice); |
Стоит разобрать еще один метод рассматриваемого интерфейса, AddComponent. Он требуется для создания адреса хоста. Таким образом, интерфейс IDirectPlay8Address предназначен не только для того, чтобы сообщать адрес устройства, но и для того, чтобы создать адрес хоста, к которому будет происходить подключение. Указание адреса хоста понадобится в функции Connect, но об этом чуть позже. Итак, AddComponent описан таким образом:
// Объявление указателя на интерфейс IDirectPlay8Address – lpHostAdsress
// ...
// Создание COM объекта, вызов требуемых функций
// ...
HRESULT AddComponent(
// назначаем тип – будь то порт, тел. номер...const WCHAR * const pwszName,
// буфер, откуда будет взята информацияconstvoid * const lpvData,
// размер буфераconst DWORD dwDataSize,
// тип буфераconst DWORD dwDataType);
|
В качестве первого параметра должен быть указан тип информации, передаваемой функции. Если, например, пользователь выбрал соединение через модем и хочет соединиться с уже созданным хостом, то, естественно, он должен каким-то образом указать не только тип устанавливаемого соединения, но и телефон, по которому модем должен позвонить на компьютер, выполняющий обязанности хоста. В этом случае нужно в первом аргументе указать DPNA_KEY_PHONENUMBER. Это значение означает, что функции будет передан номер телефона. Так, при соединении по протоколу TCP/IP, нужно передать IP-адрес. Сам же телефон или IP-адрес должен находиться в буфере, на который указывает второй параметр, lpvData. Третий параметр должен содержать размер буфера в байтах. Последний параметр должен содержать тип буфера, при этом чаще всего используются буферы строкового (DPNA_DATATYPE_STRING) и числового (DPNA_DATATYPE_DWORD) типа.
Теперь пришло время ознакомиться с функцией Connect интерфейса IDirectPlay8Peer. Интуитивно понятно, что она предназначена для установления соединения с хостом. Приведем ее (довольно длинное) описание:
HRESULT Connect( // Структура, описывающая сетевое приложение (см. выше)const DPN_APPLICATION_DESC *const pdnAppDesc, // Адрес хоста IDirectPlay8Address *const pHostAddr, // Адрес устройства IDirectPlay8Address *const pDeviceInfo, // Зарезервировано - NULLconst DPN_SECURITY_DESC *const pdnSecurity, // Зарезервировано - NULLconst DPN_SECURITY_CREDENTIALS *const pdnCredentials, // Информация о соединяющемся пользователе (Опционально - NULL)constvoid *const pvUserConnectData, // Размер pvUserConnectDataconst DWORD dwUserConnectDataSize, // Опционально - NULLvoid *const pvPlayerContext, // Опционально - NULLvoid *const pvAsyncContext, // Хендл DPNHANDLE *const phAsyncHandle, // Флагconst DWORD dwFlags ); |
Три первых и последний аргумент совпадают с первыми тремя аргументами метода Host , остальные в большинстве случаев устанавливаются в NULL. Следует обратить внимание на флаг.
Последний из рассматриваемых в этом разделе статьи методов, SetPeerInfo, совершает важное действие, связанное с тем, чтобы передать информацию о пользователе в приложение и соответствующим образом обработать ее:
HRESULT SetPeerInfo( // Структура, характеризующая пользователяconst DPN_PLAYER_INFO * const pdpnPlayerInfo, // Ставится чаще всего в NULL PVOID const pvAsyncContext, // Хендл DPNHANDLE *const phAsyncHandle, // Флагconst DWORD dwFlags ); |
Здесь для начала нужно разобрать первый аргумент, представляющий собой указатель на структуру DPN_PLAYER_INFO, содержащую информацию о пользователе. Она содержит описанные ниже поля:
typedef
struct _DPN_PLAYER_INFO {
// Размер структуры
DWORD dwSize;
// Флаг
DWORD dwInfoFlags;
// Имя пользователя
PWSTR pwszName;
// Информация, описывающая пользователя(буфер)
PVOID pvData;
// Размер буфера
DWORD dwDataSize;
// Дополнительная информация
DWORD dwPlayerFlags;
} DPN_PLAYER_INFO, *PDPN_PLAYER_INFO;
|
В большинстве случаев используются поля dwSize – размер структуры, dwInfoFlags и pwszName – поле, которому следует присвоить имя пользователя. Причем dwInfoFlags может равняться DPNINFO_NAME, тогда информация об имени пользователя будет взята из pwszName, или DPNINFO_DATA, тогда будет использоваться буфер pvData, размер которого нужно записать в поле dwDataSize. Также следует уделить особое внимание флагу dwPlayerFlags, который принимает одно из следующих значений: DPNPLAYER_HOST – указывает что структура содержит информацию о хосте, и DPNPLAYER_LOCAL – структура содержит информацию об обычном пользователе.
Второй параметр метода SetPeerInfo() используется при асинхронном обмене информацией о пользователях. Этот параметр связан с сообщением DPN_MSGID_ASYNC_OP_COMPLETE, о котором пойдет речь во второй части статьи. При создании сессии этому параметру присваивается значение NULL.
Насчет третьего параметра нужно отметить, что это своеобразный указатель на асинхронное действие. В нашем случае при создании сессии в этот параметр передается NULL.
Ну и наконец, четвертый параметр, dwFlags, представляет собой флаг, способный принимать единственное значение DPNSETPEERINFOSET_SYNC, означающее синхронное действие.
| ПРЕДУПРЕЖДЕНИЕ Для обработки ошибок, а также в отладочных целях удобно использовать макрос DXTRACE_ERR_MSGBOX(<Текст сообщения>,<Код ошибки>), который выводит диалоговое окно с информацией об ошибке. Этот макрос объявлен в файле dxerr9.h. |
В этом разделе будет по шагам рассмотрен процесс написания программы с использованием функций, описанных выше. Всю программу описывать нет смысла, поэтому будут приведены только ее основные функции. Итак, начнем:
Рисунок 1.
// Идентификатор главного окна
HWND hDirPlay;
// Основной интерфейс DirectPlay
IDirectPlay8Peer * g_pDP = NULL;
// Проверка, будет ли создан сервер
BOOL bHost = TRUE;
// Имя игрока
TCHAR chPlayerName[MAX_PATH];
// Имя сессии
TCHAR chSessionName[MAX_PATH];
// Идентификатор выбранного провайдера
GUID * guidServiceProviderSelect;
// Идентификатор выбранного устройства
GUID * guidSPDevice;
// GUID приложения – сгенерируйте с помощью // инструмента Create GUID в Visual Studiostaticconst GUID ApplicationGuid = ...;
|
HRESULT InitDirectPlay(HWND hDirPlay)
{
// g_pDP – указатель на интерфейс IDirectPlay8Peer.
HRESULT hr = S_OK;
CoInitialize(NULL);
//Создание основного объекта DirectPlay
hr = CoCreateInstance(
CLSID_DirectPlay8Peer,
NULL,
CLSCTX_INPROC_SERVER,
IID_IDirectPlay8Peer,
(LPVOID*)&g_pDP);
if(FAILED(hr))
{
DXTRACE_ERR_MSGBOX(_T("Ошибка при создании объекта"),hr);
return hr;
}
//Инициализация DirectPlay
hr = g_pDP->Initialize(NULL, DirectPlayMessageHandler, 0);
if(FAILED(hr))
{
DXTRACE_ERR_MSGBOX(_T("Ошибка при инициализации"),hr);
return hr;
}
return S_OK;
}
|
При завершении приложения необходимо вызвать CoUninitialize() для прекращения работы с COM.
struct ArrayHelper
{
ArrayHelper(BYTE * array){ m_array = array; }
ArrayHelper(BYTE * array)
{
if(m_array)
delete[] m_array;
}
BYTE * m_array;
};
// Передаем в функцию только идентификатор окна
HRESULT FindListProvider(HWND hDirPlay)
{
// bHost и guidSPDevice должны быть определены до вызова функции// g_pDP – указатель на интерфейс IDirectPlay8Peer.
HRESULT hr = S_OK;
// Переменная цикла
DWORD = 0;
// Основное хранилище данных о провайдерах
DPN_SERVICE_PROVIDER_INFO * pdnSPInfo = NULL;
// Резервное хранилище данных о провайдерах
DPN_SERVICE_PROVIDER_INFO * pdnSPInfoEnum = NULL;
// Количество провайдеров
DWORD dwItems = 0;
// Размер буфера в байтах
DWORD dwSize = 0;
// Промежуточная строчка (иия провайдера для вывода)
TCHAR strBuf[MAX_PATH]= {0};
// Положение в списке провайдеровint nIndex = 0;
// Находим число провайдеров и размер буфера
hr = g_pDP->EnumServiceProviders(NULL, NULL, NULL,
&dwSize, &dwItems, NULL);
// Обрабатываем, если произошла ошибкаif( FAILED(hr) && hr != DPNERR_BUFFERTOOSMALL)
{
DXTRACE_ERR_MSGBOX(_T("Error FindListProvider"),hr);
return hr;
}
// Создаем массив полученного размера для хранения данных о провайдерах
pdnSPInfo = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[dwSize];
// Контроль памяти, занятой под массив
ArrayHelper SPInfoHelper(pdnSPInfo);
// Записываем в созданный массив данные всех провайдеров
hr = g_pDP->EnumServiceProviders(NULL, NULL, pdnSPInfo,
&dwSize, &dwItems, NULL);
if(FAILED(hr))
{
// Обрабатываем возможные ошибки
DXTRACE_ERR_MSGBOX(_T("Error FindListProvider"),hr);
return hr;
}
// Цикл, выводящий найденнных провайдеров на экран// Копируем в резервную структуру
pdnSPInfoEnum = pdnSPInfo;
// Создаем циклfor(DWORD i = 0; i < dwItems; i++)
{
// Конвертируем в strBuf имя провайдера
DXUtil_ConvertWideStringToGenericCch(strBuf,
pdnSPInfoEnum->pwszName, MAX_PATH );
// Добавляем в список IDC_LISTSP
nIndex = (int)SendDlgItemMessage( hDirPlay, IDC_LISTSP, LB_ADDSTRING,
0, (LPARAM) strBuf );
// Записываем GUID в список с названиями
GUID * pGuid = new GUID;
if(NULL == pGuid)
{
DXTRACE_ERR_MSGBOX(_T("Не хватает памяти"), E_OUTOFMEMORY);
return E_OUTOFMEMORY;
}
*pGuid = pdnSPInfoEnum->guid;
// Добавляем в IDC_LISTSP
SendDlgItemMessage(hDirPlay, IDC_LISTSP, LB_SETITEMDATA,
nIndex, (LPARAM)&Guid);
//Переходим на следующего найденного провайдера
pdnSPInfoEnum++;
}
// Если провайдеров не найдено...if(0 == dwItems)
DXTRACE_ERR_MSGBOX(_T("Провайдеры не найдены"),0);
return S_OK;
}
|
HRESULT EnumAdapterSP(HWND hDlg, const GUID* pGuidSP) { // bHost и guidSPDevice должны быть определены до вызова функции// g_pDP – указатель на интерфейс IDirectPlay8Peer. HRESULT hr = S_OK; // Информация о провайдере DPN_SERVICE_PROVIDER_INFO * pdnSPInfo = NULL; DWORD dwItems = 0; // Количество провайдеров DWORD dwSize = 0; // Размер для создания массиваint nIndex; // Положение в списке//Строковый буфер для хранения имени устройства TCHAR strName[MAX_PATH]; //Обнуляем содержимое элемента SendDlgItemMessage(hDlg, IDC_COMBODEVICE, CB_RESETCONTENT, 0, 0); // Получить размер для массива от провайдера// Заметьте, первый аргумент заполнен(!!!) hr = g_pDP->EnumServiceProviders(pGuidSP, NULL, NULL, &dwSize, &dwItems, 0); // Обрабатываем ошибкиif(FAILED(hr) && hr != DPNERR_BUFFERTOOSMALL) { DXTRACE_ERR_MSGBOX(_T("Failed Enumerating Service Providers"),hr); return hr; } // Создаем массив полученного размера для хранения данных об устройствах pdnSPInfo = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[dwSize]; // Контроль памяти, занятой под массив ArrayHelper SPInfoHelper(pdnSPInfo); // Заполняем массив hr = g_pDP->EnumServiceProviders(pGuidSP, NULL, pdnSPInfo, &dwSize, &dwItems, 0); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Failed Enumerating Service Providers"),hr); return hr; } // Резервные данные DPN_SERVICE_PROVIDER_INFO* pdnSPInfoEnum; pdnSPInfoEnum = pdnSPInfo; for (DWORD i = 0; i < dwItems; i++ ) // Пускаем цикл { // Конвертируем в strName имя устройства DXUtil_ConvertWideStringToGenericCch(strName, pdnSPInfoEnum->pwszName, MAX_PATH ); // Добавляем в список имя устройства nIndex = (int)SendDlgItemMessage(hDlg, IDC_COMBODEVICE, CB_ADDSTRING, 0, (LPARAM) strName ); // Добавляем в список идентификатор устройства GUID * pGuid = new GUID; if(NULL == pGuid) { DXTRACE_ERR_MSGBOX(_T("Не хватает памяти"), E_OUTOFMEMORY); return E_OUTOFMEMORY; } *pGuid = pdnSPInfoEnum->guid; SendDlgItemMessage(hDlg, IDC_COMBODEVICE, CB_SETITEMDATA, nIndex, (LPARAM)&Guid ); // Переходим к следующему элементу массива pdnSPInfoEnum++; } // Устанавливаем положение в списке (в нулевую позицию) SendDlgItemMessage(hDlg, IDC_COMBODEVICE,CB_SETCURSEL, 0, 0); return hr; } |
HRESULT SwitchCreateHost(HWND hDlg, GUID* pGuidSP)
{
// bHost и guidSPDevice должны быть определены до вызова функции// g_pDP – указатель на интерфейс IDirectPlay8Peer.if(pGuidSP == NULL)
return E_POINTER;
bHost = IsDlgButtonChecked(hDlg, IDC_CHECKHOST);
// IDC_COMBODEVICE – элемент, содержащий список устройств// IDC_CHECKHOST – элемент управления CheckButton// IDC_PHONENAMEHOST –обычное текстовое поле, используется // для ввода телефона, если выбран провайдер MODEM, или имени хоста,// если выбран TCP/IP// IDC_LINE1 – метка, поясняющая IDC_PHONENAMEHOST// IDC_PORT – текстовое поле для ввода порта, в случае выбора TCP/IP// IDC_LINE2 – метка, поясняющая IDC_PORTif(*pGuidSP == CLSID_DP8SP_TCPIP)
{
EnableWindow(GetDlgItem(hDlg, IDC_COMBODEVICE), TRUE);
EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHECKHOST), TRUE);
SetWindowText(hDlg, _T("TCP/IP - Вы выбрали"));
SetDlgItemText(hDlg, IDC_LINE1, _T("Имя хоста:"));
EnableWindow(GetDlgItem(hDlg, IDC_LINE1), !bHost);
EnableWindow(GetDlgItem(hDlg, IDC_PHONENAMEHOST), !bHost);
EnableWindow(GetDlgItem(hDlg, IDC_LINE2), TRUE);
SetDlgItemText(hDlg,IDC_LINE2, _T("Порт:"));
EnableWindow(GetDlgItem(hDlg, IDC_PORT), TRUE);
}
elseif(*pGuidSP == CLSID_DP8SP_IPX)
{
MessageBox( hDlg,
_T("Выбраный вами протокол (IPX) НЕ ПОДДЕРЖИВАЕТСЯ"),
_T("IPX"), MB_OK | MB_ICONQUESTION);
}
elseif(*pGuidSP == CLSID_DP8SP_SERIAL)
{
SetWindowText(hDlg, _T("SERIAL - Вы выбрали"));
EnableWindow(GetDlgItem(hDlg,IDC_COMBODEVICE),TRUE);
EnableWindow(GetDlgItem(hDlg,IDOK),TRUE);
EnableWindow(GetDlgItem(hDlg,IDC_CHECKHOST),TRUE);
SetDlgItemText(hDlg,IDC_LINE1,NULL);
EnableWindow(GetDlgItem(hDlg,IDC_LINE1),FALSE);
EnableWindow(GetDlgItem(hDlg,IDC_PHONENAMEHOST),FALSE);
EnableWindow(GetDlgItem(hDlg,IDC_LINE2),FALSE);
SetDlgItemText(hDlg,IDC_LINE2,NULL);
EnableWindow(GetDlgItem(hDlg,IDC_PORT),FALSE);
}
elseif(*pGuidSP == CLSID_DP8SP_MODEM)
{
SetWindowText(hDlg, _T("MODEM - Вы выбрали"));
EnableWindow(GetDlgItem(hDlg, IDC_COMBODEVICE), TRUE);
EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_CHECKHOST), TRUE);
SetDlgItemText(hDlg,IDC_LINE1, _T("Телефон:"));
EnableWindow(GetDlgItem(hDlg, IDC_LINE1), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_PHONENAMEHOST), TRUE);
EnableWindow(GetDlgItem(hDlg, IDC_LINE2), FALSE);
SetDlgItemText(hDlg, IDC_LINE2, NULL);
EnableWindow(GetDlgItem(hDlg, IDC_PORT), FALSE);
}
else
{
MessageBox( hDlg, _T("Выбранный Вами пункт не поддерживается"),
_T("Произошла ошибка"), MB_OK | MB_ICONQUESTION);
SetWindowText(hDlg, _T("Произошла ошибка"));
SetDlgItemText(hDlg, IDC_LINE1, NULL);
EnableWindow(GetDlgItem(hDlg, IDC_LINE1), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_PHONENAMEHOST), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_LINE2), FALSE);
SetDlgItemText(hDlg, IDC_LINE2, NULL);
EnableWindow(GetDlgItem(hDlg, IDC_PORT), FALSE);
SendDlgItemMessage(hDlg, IDC_COMBODEVICE, CB_RESETCONTENT, 0, 0);
EnableWindow(GetDlgItem(hDlg, IDC_COMBODEVICE), FALSE);
EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
EnableWindow(GetDlgItem(hDlg, IDC_CHECKHOST), FALSE);
}
return S_OK;
}
|
HRESULT JoinHostGame(HWND hDlg, GUID * pGuidSP, GUID* guidSPDevice, bool bHost) { // g_pDP – указатель на интерфейс IDirectPlay8Peer. HRESULT hr = S_OK; CComPtr<IDirectPlay8Address> spDeviceAddress; // Адрес устройства CComPtr<IDirectPlay8Address> spHostAddress; // Адрес хоста// Начало инициализации адреса устройства hr = CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address, (void**)&spDeviceAddress); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при инициализации устройства"),hr); return hr; } // Назначение идентификатора провайдера устройству hr = spDeviceAddress->SetSP(pGuidSP); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении провайдера устройству"),hr); return hr; } // Задаем тип устройства hr = spDeviceAddress->SetDevice(guidSPDevice); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении устройства"),hr); return hr; } // Если пользователь хочет подключиться к уже готовому хосту, начинаем// инициализацию подключения к удаленному хосту.if(!bHost) { // Создание адреса хоста hr = CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address, (void**)&spHostAddress); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при инициализации хоста"),hr); return hr; } // Задаем идентификатор провайдера hr = spHostAddress->SetSP(pGuidSP); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении адреса хоста"),hr); return hr; } } // ------------------------------------------------------------------------// Если соединение производится по TCP/IP...if(*pGuidSP == CLSID_DP8SP_TCPIP) { TCHAR strHostname[MAX_PATH]; TCHAR strPort[MAX_PATH]; GetDlgItemText(hDlg, IDC_PHONENAMEHOST, strHostname, MAX_PATH); GetDlgItemText(hDlg, IDC_PORT, strPort, MAX_PATH); // Если пользователь хочет создать новый хост.if(bHost) { if(_tcslen(strPort) > 0) { // Добавляем порт к spDeviceAddress DWORD dwPort = _ttoi(strPort); hr = spDeviceAddress->AddComponent(DPNA_KEY_PORT, &dwPort, sizeof(dwPort), DPNA_DATATYPE_DWORD); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Извините, ошибка при назначении порта"),hr); return hr; } } } else { // Если пользователь хочет подключиться к уже готовому хосту, начинаем// инициализацию подключения к удаленному хосту.// Добавляем имя хоста (то есть его IP-адрес) к spHostAddressif(_tcslen(strHostname) > 0) { WCHAR wstrHostname[MAX_PATH]; DXUtil_ConvertGenericStringToWideCch(wstrHostname, strHostname, MAX_PATH); hr = spHostAddress->AddComponent(DPNA_KEY_HOSTNAME, wstrHostname, (DWORD) (wcslen(wstrHostname) + 1) * sizeof(WCHAR), DPNA_DATATYPE_STRING); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении IP-адреса хоста"),hr); return hr; } } if(_tcslen(strPort) > 0) { // Добавляем порт к spDeviceAddress DWORD dwPort = _ttoi(strPort); hr = spHostAddress->AddComponent(DPNA_KEY_PORT, &dwPort, sizeof(dwPort), DPNA_DATATYPE_DWORD); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Извините, ошибка при назначении порта"),hr); return hr; } } } } // ------------------------------------------------------------------------// Если соединение производится через модем...if(*pGuidSP == CLSID_DP8SP_MODEM) { // Получаем значение телефонного номера TCHAR tchPhoneNumber[MAX_PATH]; GetDlgItemText(hDlg, IDC_PHONENAMEHOST, tchPhoneNumber, MAX_PATH); // Если пользователь хочет подключиться к имеющемуся хостуif(!bHost) { // Если телефонный номер задан...if(_tcslen(tchPhoneNumber) > 0) { WCHAR wchPhoneNumber[MAX_PATH]; // Конвертация из TCHAR в WCHAR DXUtil_ConvertGenericStringToWideCch(wchPhoneNumber, tchPhoneNumber, MAX_PATH); // Добавляем номер телефона к spDeviceAddress hr = spHostAddress->AddComponent(DPNA_KEY_PHONENUMBER, wchPhoneNumber, (DWORD)(wcslen(wchPhoneNumber) + 1) * sizeof(WCHAR), DPNA_DATATYPE_STRING); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при инициализации телефонного номера"),hr); return hr; } } } } // ------------------------------------------------------------------------// Если соединение производится по протоколу SERIAL...if(*pGuidSP == CLSID_DP8SP_SERIAL) { MessageBox(hDlg, _T("Извините, данный протокол не обрабатывается"), _T("DirectSendPlay"), MB_OK | MB_ICONQUESTION); } // ------------------------------------------------------------------------// Если соединение производится по протоколу IPX...if(*pGuidSP == CLSID_DP8SP_IPX) { MessageBox(hDlg, _T("Извините, данный протокол не обрабатывается"), _T("DirectSendPlay"), MB_OK | MB_ICONQUESTION); } // ------------------------------------------------------------------------// Заполнение стуктур DPN_PLAYER_INFO, DPN_APPLICATION_DESC GetDlgItemText(hDlg, IDC_EDIT1, chPlayerName, MAX_PATH); WCHAR wchNamePlayer[MAX_PATH]; // Преобразование из TCHAR в WCHAR DXUtil_ConvertGenericStringToWideCch(wchNamePlayer, chPlayerName, MAX_PATH); // Структура, описывающая игрока DPN_PLAYER_INFO dpPlayerInfo; // Обнуляем содержимое структуры ZeroMemory(&dpPlayerInfo, sizeof(DPN_PLAYER_INFO)); // Задаем размер структуры dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO); // Задаем дополнительные флаги dpPlayerInfo.dwInfoFlags = DPNINFO_NAME; // Задаем имя игрока dpPlayerInfo.pwszName = wchNamePlayer; // Задаем информацию об игроке hr = g_pDP->SetPeerInfo(&dpPlayerInfo, NULL, NULL, DPNOP_SYNC); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при передаче информации о игроке"),hr); return hr; } // Структура, описывающая сетевое приложение DPN_APPLICATION_DESC dpnAppDesc; ZeroMemory(&dpnAppDesc, sizeof(DPN_APPLICATION_DESC)); dpnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC); dpnAppDesc.dwFlags = DPNSESSION_NODPNSVR; // Задаем GUID приложения dpnAppDesc.guidApplication = ApplicationGuid; dpnAppDesc.guidInstance = GUID_NULL; // Имя сессии пока инициализируется значением NULL dpnAppDesc.pwszSessionName = NULL; // -------------------------------------------------------------------------// Создание хостаif(bHost) { DWORD dwHostFlags = 0; // Считываем имя сессии GetDlgItemText(hDlg, IDC_NAMESESSION, chSessionName, MAX_PATH); // Если строка не пустаif(_tcslen(chSessionName) > 0) { WCHAR wchSessionName[MAX_PATH]; DXUtil_ConvertGenericStringToWideCch(wchSessionName, chSessionName, MAX_PATH); // Задаем имя сессии dpnAppDesc.pwszSessionName = wchSessionName; } // Назначаем хост сессии hr = g_pDP->Host( // Задаем описание приложения &dpnAppDesc, // Задаем адрес устройства &spDeviceAddress, // Указываем, что устройство одно 1, NULL, NULL, NULL, // Указываем, что не нужно выводить стандартное окно.// Если нужно, чтобы это окно показывалось, // нужно указать флаг DPNHOST_OKTOQUERYFORADDRESSING. 0); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении хоста"),hr); return hr; } } // ------------------------------------------------------------------------// Соединение с уже созданным хостомelse { DWORD dwConnectFlags = 0; // Установка соединения hr = g_pDP->Connect(&dpnAppDesc, // Адрес хоста spHostAddress, // Адрес устройства, через которое производится соединение spDeviceAddress, NULL, NULL, NULL, // указываем, что в параметре pvUserConnectData данные отсутствуют 0, NULL, NULL, NULL, dwConnectFlags); if(FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("Ошибка при назначении соединения"),hr); return hr; } } return(hr); } |
HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext,
DWORD dwMessageId,
PVOID pMsgBuffer )
{
switch(dwMessageId)
{
case DPN_MSGID_CONNECT_COMPLETE:
{
PDPNMSG_CONNECT_COMPLETE pConnectCompleteMsg;
pConnectCompleteMsg=(PDPNMSG_CONNECT_COMPLETE)pMsgBuffer;
if(FAILED(pConnectCompleteMsg->hResultCode))
{
DXTRACE_ERR_MSGBOX(_T("DPN_MSGID_CONNECT_COMPLETE"),
pConnectCompleteMsg->hResultCode );
break;
}
else
MessageBox(hDirPlay,_T("Подключение прошло
удачно"),_T("Проверка"),MB_OK);
break;
}
}
return S_OK;
}
|
Произведя все описанные выше действия, вы должны получить сетевое приложение, работающее с провайдерами TCP/IP и MODEM. Думаю, что написать функции обработки и создания соединения для других провайдеров не составит труда, если приложить немного усилий и прочитать DirectX Help for C++.
Вот и все, на этом первая часть статьи закончена. В заключение могу лишь отметить, что разработчики DirectPlay ставили перед собой задачу создать методы, позволяющие унифицировано работать с различными провайдерами, то есть создавать соединения с помощью различных средств по единому алгоритму. А теперь замечу, что мы рассмотрели только одну часть проблемы. Мы написали только ту часть программы, которая отвечает за инициализацию и установку соединения. Ту часть программы, которая будет отвечать за передачу данных, мы рассмотрим в следующей части статьи.
Оценка 215
[+1/-1]
Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|