Добавить в цитаты Настройки чтения

Страница 187 из 372

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

HRESULT GetLongsAlloc([out] ULONG* pNum, [out, size_is(, *pNum)] LONG** ppArr);

Здесь запятая в size is () говорит о том, что *pNum есть размер массива, УКАЗАТЕЛЬ на который есть ppArr.

В данном случае маршалер не может сам выделить память под массив на стороне объекта и, следовательно, это должен делать объект. Но освобождать выделенную память после передачи данных должен уже маршалер, т. к. объект к этому моменту уже завершит работу. Для выделения и освобождения памяти в данном случае следует использовать функции CoTaskMemAlloc () и CoTaskMemFree (), которые может вызывать и маршалер.

Код компонента

STDMETHODIMP CArrays::GetLongsAlloc(ULONG* pNum, LONG** ppArr);

{

        *pNum = 10;

        *ppArr = reinterpret_cast<LONG*>(CoTaskMemAlloc(*pNum * sizeof(LONG)));

        for (ULONG x = 0; x < *pNum; x++) (*ppArr)[x] = x;

        return S_OK;

}

Здесь reinterpret_cast<LONG*> используется для приведения типа указателя, возвращаемого при выделении памяти, к указателю на long. Освобождает выделенную память сам маршалер, вызывая CoTaskMemFree.

На стороне клиента память выделяет маршалер, получив информацию о размере массива — *pNum. Освобождает же эту память клиент

ULONG ulNum;

LONG* pi;

hr = pArrObj —> GetLongsAlloc(SulNum, &pl);

for (ULONG ul = 0; ul < ulNum; ul++)

      printf("%ldn", pi[ul]);

CoTaskMemFree(pi);

Потоковая модель и апартаменты

Процесс и поток

Прежде всего вспомним некоторые понятия, связанные с процессами и потоками.

Приложение — это один или несколько процессов.

Каждый процесс имеет ряд ресурсов, среди которых виртуальное адресное пространство процесса, исполняемый код, данные, дескрипторы объектов, переменные среды, базовый приоритет. Таким образом, процесс это скорее среда, в которой выполняется один или несколько потоков.

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

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

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

• закончился квант времени, выделенный потоку;

• готов к выполнению поток с более высоким приоритетом;

• исполняемый поток перешел в состояние ожидания.

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

Например, если поток выполняет операции ввода/вывода, то система может приписать ему повышенный динамический приоритет. Однако, этот приоритет уменьшается на 1 после получения данным потоком очередного кванта процессорного времени и может достигнуть базового значения. Ниже базового значения приоритет не уменьшается.





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

Как порождаются потоки? Первый поток образуется при выполнении функции main. Новый поток можно запустить функцией CreateThread. В качестве одного из параметров этой функции передается адрес функции, которая будет выполняться в потоке.

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

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

Следующий стандартный псевдокод иллюстрирует использование критических секций

class CoClass { public:

           CoClass::CoClass ()

           {

                  InitializeCriticalSection(&m_cs);

           }

           CoClass::~CoClass()

            {

                  DeleteCriticalSection(&m_cs);

             }

            HRESULT __ stdcall CoClass::Method()

            {

                  EnterCriticalSection(&m cs);

                  ……

                  LeaveCriticalSection(&m_cs);

                  return S_OK;

              }

private:

              CRITICAL_Section m_cs;

};

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

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

InterLockedIncrement(&g_objCount)

и

InterLockedDecrement (&g_objcount),

обеспечивающие безопасное увеличение и уменьшение на единицу значения параметра функции.

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