netlib.narod.ru< Назад | Оглавление | Далее >

Программирование для Windows

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

Прежде всего, есть несколько связанных с Windows тем, которые я хочу обсудить. К этим темам относится изучение потоков и многопоточности, критические секции и COM. Я полагаю, что эта информация абсолютно необходима для чтения остальной части этой книги (намек, намек).

Потоки и многопоточность

Windows 95 познакомила программистов с идеей использования многозадачных систем (даже несмотря на то, что Windows не является истинно многозадачной системой, поскольку использует вытесняющую многозадачность, в которой небольшие фрагменты множества программ выполняются по одному). Идея заключается в том, что у вас есть несколько процессов (приложений) работающих одновременно, каждому из которых выделяется часть времени процессора (называемая квантом времени — time slice).

Многозадачность позволяет также каждый процесс разделить на несколько отдельных подпроцессов, называемых потоками (threads). У каждого потока есть своя задача, например, сканирование сетевых данных, обработка пользовательского ввода или воспроизведение звука. Использование нескольких потоков в одном приложении называется многопоточностью (multithreading).

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

DWORD WINAPI ThreadProc(LPVOID lpParameter);

Параметр lpParameter — это определенный пользователем указатель, который вы предоставляете, когда создаете поток с помощью вызова функции CreateThread:

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES  lpThreadAbilities, // NULL
    DWORD                  dwStackSize,       // 0
    LPTHREAD_START_ROUTINE lpStartAddress,    // функция потока
    LPVOID                 lpParameter,       // предоставляемый пользователем указатель
                                              // (может быть NULL)
    DWORD                  dwCreationFlags,   // 0
    LPDWORD                lpThreadId);       // возвращает идентификатор потока

 

ВНИМАНИЕ!
Возвращаемое функцией CreateThread значение является дескриптором, который при завершении работы должен быть закрыт, или системные ресурсы не будут освобождены. Освобождение используемых потоком ресурсов выполняется путем вызова функции CloseHandle:
CloseHandle(hThread); // используйте дескриптор, возвращенный CreateThread

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

// Функция пользовательского потока
DWORD WINAPI MyThread(LPVOID lpParameter)
{
    BOOL *Active;
    Active = (BOOL*)lpParameter;
    *Active = TRUE;    // флаг активности потока

    // Поместите сюда свой код

    // Завершение потока
    *Active = FALSE;  // Сбрасываем флаг активности
    ExitThread(0);    // Специальный вызов для закрытия потока
}

void InitThread()
{
    HANDLE hThread;
    DWORD  ThreadId;
    BOOL   Active;

    // Создаем поток, передавая определенную пользователем
    // переменную, которая используется для хранения
    // состояния потока
    hThread = CreateThread(NULL, 0,
                           (LPTHREAD_START_ROUTINE)MyThread,
                           (void*)&Active, 0, &ThreadId);

    // Ждем завершения выполнения потока,
    // проверяя состояние флага
    while(Active == TRUE);

    // Закрываем дескриптор потока
    CloseHandle(hThread);
}

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

Когда выполнение потока завершено, вы сигнализируете о том, что поток больше не является активным (записав значение FALSE в упомянутую ранее логическую переменную) и завершаете работу потока вызовом функции ExitThread, которой передается единственный параметр — код завершения потока, или, другими словами, число, сообщающее причину по которой выполнение было завершено. В большинстве случаев можно спокойно использовать в вызове ExitThread значение 0.

Фактически, поток — это просто функция, выполняемая одновременно с вашим приложением. Более подробно об использовании потоков мы поговорим в главе 4.

Критические секции

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

Есть способ гарантировать, что когда необходимо, только одному потоку будет предоставлен полный контроль, и этот способ — использование критических секций (critical sections). При активации критическая секция блокирует все другие потоки, пытающиеся получить доступ к разделяемой памяти (памяти приложения, которую используют все потоки), тем самым позволяя потоку в одиночку менять данные приложения, не беспокоясь о возможности вмешательства других потоков. Чтобы использовать критическую секцию вы должны сперва объявить и инициализировать ее:

CRITICAL_SECTION CriticalSection;

InitializeCriticalSection(&CriticalSection);

После этого вы можете войти в критическую секцию, обработать данные и покинуть критическую секцию, как показано в приведенномниже примере:

EnterCriticalSection(&CriticalSection);

// Здесь выполняем обработку данных

LeaveCriticalSection(&CriticalSection);

Если критическая секция вам больше не нужна (например, когда приложение завершает работу), вы должны освободить ее с помощью вызова:

DeleteCriticalSection(&CriticalSection);

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

Использование COM

COM или модель компонентных объектов (Component Object Model) — это применяемая Microsoft техника программирования. Компоненты в парадигме COM — это двоичные объекты, которые могут взаимодействовать между собой используя различные интерфейсы, предоставляемые каждым объектом. Объекты могут заменяться на новые или улучшенные версии не вызывая сбоев в разработанных ранее приложениях. Рассмотрим, к примеру, DirectX — модуль графических объектов DirectX 8 может быть заменен на модуль графических объектов DirectX 9, и вам не надо беспокоиться о том, что ваша, разработанная для DirectX 8 игра больше не будет работать. Чтобы получить больше информации о возможностях COM, посетите посвященный COM сайт Microsoft http://www.microsoft.com/COM/.

Используя COM, вы создаете программные компоненты таким образом, чтобы их функциональность была совместима со всеми программами. Возьмем, например, Internet Explorer. Готов поспорить, вы не знаете, что панель инструментов и окно браузера являются COM-объектами. Более того, вы можете использовать эти объекты в своих приложениях!

Хотя это и весомая причина для того, чтобы начать использовать COM, более веской причиной является DirectX; DirectX составлен исключительно из COM-объектов.

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

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

// Для однопоточных приложений
HRESULT CoInitialize(
     LPVOID pvReserved); // NULL

// Для многопоточных приложений
HRESULT CoInitializeEx(
     void *pvReserved,  // NULL
     DWORD dwCoInit);   // модель распараллеливания

Обе эти функции выполняют поставленную задачу, но если ваше приложение многопоточное, то вы должны использовать вторую функцию, CoInitializeEx, поскольку необходимо указать флаг COINIT_MULTITHREADED в параметре dwCoInit, для того чтобы система COM функционировала правильно.

Когда вы завершаете использование системы COM, необходимо выключить ее вызовом функции CoUninitialize, не имеющей параметров:

void CoUninitialize();

Каждому вызову CoInitialize или CoInitializeEx должен соответствовать отдельный вызов CoUninitialize. Если вы вызываете CoInitialize дважды (это допускается), вам необходимы два вызова CoUnitialize. Это иллюстрирует следующий фрагмент кода:

// Инициализация системы COM
CoInitialize(NULL);

// Инициализация COM с поддержкой многопоточности
CoInitializeEx(NULL, COINIT_MULTITHREADED);

// Освобождение COM (дважды)
CoUninitialize();
CoUninitialize();

IUnknown

IUnknown — это базовый класс для всех COM-интерфейсов. Он содержит только три функции: AddRef, Release и QueryInterface. AddRef выполняет, если необходимо, инициализацию, и увеличивает счетчик, показывающий сколько раз был создан экземпляр класса. Вы должны сделать так, чтобы значение счетчика ссылок соответствовало количеству вызовов функции Release, освобождающей ресурсы, используемые экземпляром объекта.

ПРИМЕЧАНИЕ
Не путайте классы, интерфейсы и объекты! Объект — это экземпляр класса. Интерфейс — это набор функций, предоставляемых объектом (другими словами, интерфейсы позволяют взаимодействовать с объектом).

Третью функцию, QueryInterface, вы используете чтобы получить доступ к предоставляемым объектом интерфейсам, включая их новые версии. Такая ситуация может иметь место, когда было выпущено несколько версий объектов, как в случае DirectX. Вы можете по прежнему использовать старые интерфейсы, а чтобы получить доступ к новым, необходимо запросить их. Если новый интерфейс существует, объект возвратит указатель; в ином случае QueryInterface возвращает NULL, что свидетельствует об отсутствии интерфейса или об ошибке.

Чтобы добавить функцию, в объекте должен быть класс унаследованный от IUnknown, в объявление которого и вставляется код новой функции. Обратите внимание, что согласно установленному Microsoft стандарту COM объекты не могут предоставлять доступ к своим переменным — только к функциям.

Необходимо, чтобы функция возвращала значение типа HRESULT, сообщающее об ошибке или об успешном завершении. Чтобы получить какое-либо значение из COM-объекта вы передаете указатель на переменную (которая должна быть словом или двойным словом — байты и другие типы здесь не поддерживаются) в функцию и использовать этот указатель для возврата значения, находящегося внутри объекта.

В качестве примера создадим простой объект (наследуемый от IUnknown), который получает два числа, складывает их и помещает результат в переменную, указанную в третьем параметре:

class IMyComObject : public IUnknown
{
    public:
        HRESULT Add(long *Num1, long *Num2, long *Result);
};

HRESULT IMyComObject::Add(long *Num1, long *Num2, long *Result)
{
    // Складываем числа и сохраняем результат
    *Result = *Num1 + *Num2;

    // Возвращаем код успеха
    return S_OK;
}

Инициализация и освобождение объектов

Чтобы использовать COM-объект вы должны (помимо написания загружаемой Windows библиотеки) создать его с помощью функции CoCreateInstance:

STDAPI CoCreateInstance(
     REFCLSID  rclsid,       // Идентификатор класса объекта
     LPUNKNOWN pUnkOuter,    // NULL
     DWORD     dwClsContext, // CLSCTX_INPROC
     REFIID    riid,         // ссылка на идентификатор интерфейса
     LPVOID    *ppv);        // Указатель для возврата созданного объекта

Для использования функции CoCreateInstance необходимо знать идентификаторы класса объекта и интерфейса. Идентификатор класса с префиксом CLSID_, определяет класс объекта, который вы создаете, а ссылка с префиксом IID_ — это конкретный интерфейс, который вы ищете. Скажем, у вас есть класс с названием Math и идентификатором класса CLSID_MATH. Класс Math содержит три объекта: IAdd (с идентификатором IID_IAdd), ISubtract (IID_ISubtract) и IAdd2 (IID_IAdd2). Вызов CoCreateInstance для получения ссылки на объект IAdd2 будет выглядеть так:

IAdd2 *pAdd2;

if(FAILED(CoCreateInstance(CLSID_MATH, NULL, CLSCTX_INPROC,
                           IID_IAdd2, (void**)&pAdd2))) {
     // Обнаружена ошибка
}

Все созданные вами COM-объекты должны быть в конечном счете освобождены. Для этих целей предназначена функция IUnknown::Release без параметров:

HRESULT IUnknown::Release();

После того, как вы завершите работу с интерфейсом IAdd2, необходимо освободить его следующим образом:

IAdd2->Release();

Запрос интерфейсов

Одна из лучших особенностей COM — обратная совместимость. Если у вас появится новый COM-объект (содержащий новые интерфейсы), то останется полный доступ через объект к старым интерфейсам. Сохранение старых интерфейсов гарантирует, что код будет правильно работать, даже если конечному пользователю установили новые версии COM-объектов. Это также означает, что старые интерфейсы могут запрашивать новые интерфейсы.

Это делается с помощью метода IUnknown::QueryInterface:

HRESULT IUnknown::QueryInterface(
     REFIID iid,          // Идентификатор нового интерфейса
     void   **ppvObject); // Указатель на новый объект

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

IAdd  *pAdd;
IAdd2 *pAdd2;

// Сперва получаем интерфейс IAdd
if(FAILED(CoCreateInstance(CLSID_MATH, NULL, CLSCTX_INPROC,
                           IID_IAdd, (void**)&pAdd))) {
    // Произошла ошибка
}

// Запрашиваем интерфейс IAdd2
if(SUCCEEDED(pAdd->QueryInterface(IID_IAdd2, (void**)&pAdd2))) {
    // Интерфейс получен, освобождаем первый интерфейс
    IAdd->Release();
}

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


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz