Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
412
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

211

}

}

return hr;

}

CClientApartment::CreateComponentOnThread запрашивает у созданного ею компонента интерфейс IX, который используется затем функцией WorkerFunction:

void CClientApartment::WorkerFunction()

{

if (m_pIX)

{

m_pIX->Tick();

}

}

CLIENT.CPP

Теперь созданы и поток и компонент. Всякий раз, когда истекает интервал времени

CSimpleApartment::m_WaitTime, CSimpleApartment::ClassThreadProc вызывает CClientApartment::WorkerFunction.

Таким образом, наш компонент обновляется каждые несколько миллисекунд. Для отображения этих изменений в своем окне клиент создает таймер. Получив сообщение WM_TIMER, клиент вызывает OnTick, которая обращается к IX::GetCurrentCount и затем отображает значение счетчика в окне. Когда клиент вызывает IX::GetCurrentCount, происходит маршалинг вызова через границу подразделения. Когда же WorkerFunction вызывает IX::Tick, имеет место вызов из того же самого подразделения, и маршалинг не производится.

Разделенные потоки могут создаваться не только клиентами. Можно разработать компоненты для создания разделенных потоков. Фактически можно создать фабрику класса, которая создает компоненты в разных разделенных потоках.

Вот и все. Как видите, самая сложная часть реализации разделенного потока — это создание и управление потоками.

Теперь, когда мы стали экспертами по разделенным потокам, давайте рассмотрим модель свободных потоков.

Реализация модели свободных потоков

Если Вам приходилось писать многопоточные программы, то свободные потоки вряд ли составят для Вас понастоящему новые проблемы. Свободные потоки создаются и управляются обычными функциями Win32 для работы с потоками, такими как CreateThread, ResumeThread, WaitForMultipleObjects, WaitForSingleObject,

CreateMutex и CreateEvent. Используя стандартные синхронизационные объекты — мьютексы, критические секции и семафоры, — Вы можете управлять доступом к внутренним данным своего компонента, сделав его «потокобезопасным». Хотя обеспечить настоящую потокобезопасность компонента — всегда непростая задача, хорошо разработанный интерфейс СОМ совершенно ясно покажет Вам, когда происходит доступ к компоненту.

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

Помимо «потокобезопасности» компонентов, для использования свободных потоков надо выполнить, по существу, только три требования. Первое состоит в том, что Ваша операционная система должна поддерживать модель свободных потоков СОМ. Ее поддерживает Windows NT 4.0 и Windows 95 тоже, если Вы установили расширения DCOM. В гл. 10 мы рассматривали, как программным путем определить, что операционная система поддерживает свободные потоки. (В основном для этого нужно установить наличие в OLE32.DLL функции

CoInitializeEx.)

Говоря о CoInitializeEx: поток должен вызвать эту функцию с параметром COINIT_MULTITHREADED, чтобы обозначить себя как свободный. Что значит объявить поток свободным? Поток, создающий компонент, определяет, как компонент обрабатывает вызовы из других потоков. Если компонент создается свободным потоком, то он может быть вызван любым другим свободным потоком в любой момент.

После того, как поток вызывал CoInitializeEx с параметром COINIT_MULTITHREADED, он не может вызвать ее с другим параметром. Поскольку OleInitialize вызывает CoInitializeEx с параметром COINIT_APARTMENTTHREADED, постольку Вы не можете использовать библиотеку OLE из свободного потока.

212

Третье требование является фактически требованием не свободных, а разделенных потоков. Необходимо выполнять маршалинг указателей на интерфейсы при передаче их разделенным потокам. Кстати, это имеет значение только в том случае, если указатель передается не посредством интерфейса COM. Если указатель передается через интерфейс СОМ, то СОМ выполняет маршалинг автоматически. Если клиент находится в другом процессе, то и здесь СОМ выполняет маршалинг автоматически. Конечно, для автоматического маршалинга Вы должны предоставить СОМ DLL заместителя/заглушки. Маршалинг между разделенными потоками мы обсуждали в предыдущем разделе. Свободные потоки используют для маршалинга интерфейсов вручную те же самые функции CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream. Как мы увидим далее, СОМ может незаметно для нас оптимизировать маршалинг.

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

Как видите, за исключением маршалинга интерфейсов в другие подразделения, требования модели свободных потоков просты. Самая сложная задача, связанная с этой моделью, состоит в обеспечении «потокобезопасности» компонентов. Однако это не требование СОМ, а стандартная проблема многопоточности.

Пример со свободным потоком

Создание свободного потока не слишком отличается от создания разделенного потока. Каталог \CHAP12\FREE_THD содержит код создания двух свободных потоков, совместно использующих один компонент. Первый свободный поток увеличивает счетчик компонента (как в примере с разделенным потоком). Другой счетчик уменьшает его. Кроме того, теперь мы будем представлять себе счетчик как находящийся то на одной, то на другой «стороне». Первый свободный поток переводит его «налево», а второй — «направо». Главный поток (разделенный) получает после маршалинга копию указателя на интерфейс и использует ее для периодического опроса состояния компонента. Большая часть кода аналогична приведенному ранее для создания разделенного потока. Чтобы не повторяться, я лишь укажу на отличия в этих двух примерах.

Очевидные отличия

Самое очевидное отличие — замена имени CSimpleApartment на CSimpleFree. При создании свободного потока создается не новое подразделение, а лишь поток. Аналогично, CClientApartment теперь называется CClientFree.

Подчеркну, что CSimpleFree — не универсальный подход к созданию и управлению свободными потоками. Сам по себе CSimpleFree не «потокобезопасен». Он предназначен только для создания свободных потоков клиентом, использующим модель разделенных потоков. Недостаток устойчивости CSimpleFree компенсируется простотой.

CSimpleFree::ClassThreadProc

Единственная функция, которой CSimpleFree существенно отличается от CSimpleApartment, — это

ClassThreadProc. Вместо вызова CoInitialize, как это делается в CSimpleApartment, она вызывает CoInitializeEx(0, COINIT_MULTITHREADED). Прежде чем использовать CoInitializeEx, необходимо сделать две вещи. Во-первых, нужно определить _WIN32_WINNT = 0x400 или _WIN32_DCOM. Если этого не сделать, то в OBJBASE.H не будет определения CoInitializeEx. Во-вторых, мы должны во время выполнения программы убедиться, что операционная система поддерживает CoInitializeEx. Все это показано ниже:

BOOL CSimpleFree::ClassThreadProc()

{

BOOL bReturn = FALSE;

// Проверить наличие CoInitializeEx

typedef HRESULT (__stdcall *FPCOMINITIALIZE)(void*, DWORD); FPCOMINITIALIZE pCoInitializeEx =

reinterpret_cast<FPCOMINITIALIZE>( ::GetProcAddress(::GetModuleHandle("ole32"), "CoInitializeEx"));

if (pCoInitializeEx == NULL)

{

trace("Эта программа требует поддержки свободных потоков в DCOM"); SetEvent(m_hComponentReadyEvent);

return FALSE;

}

// Инициализировать библиотеку COM

HRESULT hr = pCoInitializeEx(0, COINIT_MULTITHREADED); if (SUCCEEDED(hr))

{

// Сигнал о начале работы

213

SetEvent(m_hComponentReadyEvent);

// Создать массив событий

HANDLE hEventArray[2] = { m_hCreateComponentEvent, m_hStopThreadEvent };

// Ждать команды на создание компонента

BOOL bContinue = TRUE; while (bContinue)

{

switch(::WaitForMultipleObjects(2,

hEventArray,

FALSE, m_WaitTime))

{

//Создать компонент case WAIT_OBJECT_0:

CreateComponentOnThread();

break;

//Остановить поток

case (WAIT_OBJECT_0 +1): bContinue = FALSE; bReturn = TRUE; break;

// Выполнить фоновую обработку case WAIT_TIMEOUT:

WorkerFunction();

break;

default:

trace("Ошибка при ожидании", GetLastError());

}

}

// Освободить библиотеку COM CoUninitialize();

}

// Сигнализировать, что мы закончили

SetEvent(m_hComponentReadyEvent); return bReturn;

}

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

MsgWaitForMultipleObjects на WaitForMultipleObjects. Для остановки потока вместо WM_QUIT используется m_hStopThreadEvent.

Хотя MsgWaitForMultipleObjects больше не нужна нам в ClassThreadProc, она по-прежнему используется в

CSimpleFree::StartThread и в CSimpleFree::CreateComponent. Эти функции вызываются главным потоком

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

Фактически только в этом и состоят различия между CSimpleFree и CSimpleApartment.

CClientFree

Я хочу продемонстрировать Вам работу двух свободных потоков, которые совместно используют один компонент, без маршалинга их указателей на интерфейсы. Для этого я добавил в CClientFree два метода. CClientFree в этом примере со свободным потоком служит эквивалентом CClientApartment из предыдущего примера. CClientFree наследует CSimpleFree и реализует виртуальные функции CreateComponentOnThread и WorkerFunction. В CClientFree две новые функции — ShareUnmarshaledInterfacePointer и UseUnmarshaledInterfacePointer. (Меня до того воодушевили длинные имена некоторых функций СОМ, что я решил так называть и свои функции.) Первая, ShareUnmarshaledInterfacePointer, возвращает указатель на интерфейс IX, используемый CClientFree в его функции WorkerFunction. Маршалинг этого интерфейса не выполняется, поэтому его можно использовать только в свободном потоке. Вторая функция, UseUnmarshaledInterfacePointer, устанавливает указатель на IX, который объект CClientFree будет использовать в своей функции WorkerFunction. Теперь посмотрим, как эти функции используются в CLIENT.CPP.

214

Функция InitializeThread используется в CLIENT.CPP для создания свободного потока и компонента. Эта функция похожа на вызов InitializeApartment из примера однопоточного подразделения. После вызова InitializeThread клиент вызывает InitializeThread2. Эта функция создает второй поток. Однако вместо создания второго компонента этот поток использует компонент, созданный первым потоком. Код InitializeThread2 показан ниже:

BOOL InitializeThread2()

{

if (g_pThread == NULL)

{

return FALSE;

}

//Создать второй поток

//У этого потока другая WorkerFunction

g_pThread2 = new CClientFree2;

// Запустить поток

if (g_pThread2->StartThread())

{

trace("Второй поток получен успешно");

// Получить тот же указатель, который использует первый поток

IX* pIX = NULL;

pIX = g_pThread->ShareUnmarshaledInterfacePointer(); assert(pIX != NULL);

// Использовать этот указатель во втором потоке g_pThread2->UseUnmarshaledInterfacePointer(pIX); pIX->Release();

return TRUE;

}

else

{

trace("Ошибка при запуске второго потока");

return FALSE;

}

}

InitializeThread2 вместо объекта CClientFree создает объект CClientFree2. CClientFree2 отличается от CClientFree

только реализацией WorkerFunction. Обе реализации приведены ниже:

void CClientFree::WorkerFunction()

{

CSimpleLock Lock(m_hInterfaceMutex);

if (m_pIX)

{

m_pIX->Tick(1); m_pIX->Left();

}

}

void CClientFree2::WorkerFunction()

{

CSimpleLock Lock(m_hInterfaceMutex);

if (m_pIX)

{

m_pIX->Tick(-1); m_pIX->Right();

}

}

CSimpleLock мы скоро обсудим. Я изменил IX::Tick, чтобы она принимала в качестве параметра размер увеличения счетчика. Я также добавил методы Left и Right. Эти функции управляют тем, на какой «стороне» находится счетчик. CClientFree увеличивает счетчик и помещает его «налево». CClientFree2 уменьшает его и помещает «направо». Функция InRightHand возвращает TRUE, если счетчик находится на правой стороне. Таким образом, с ее помощью мы можем определить, какой поток использовал компонент последним.

Соседние файлы в предмете Программирование на C++