Re[12]: Вызов событий COM из разных потоков
От: silart  
Дата: 25.11.09 02:20
Оценка: 7 (1)
Здравствуйте, Аркадий, Вы писали:

А>Сделал — то же самое Причем попробовал сохранять в GIT и извлекать, используя напрямую, как IDispatch, так и сам IUpTaskEvents.

А>В случае, если FireEvent вызывается в основном потоке — событие исполняется, всё как и должно быть. В случае, если в слушающем потоке — не работает.

Аркадий, а каким образом вы используете GIT?
У меня с этим были тоже проблемы.
Нужно делать так:
1. Тот поток, который вызывает Advise() должен создать GIT, поместить в него указатель, освободить указатель на GIT.
2. Поток, который генерирует события, тоже должен создать GIT, получить интерфейс по куки, сохранить полученный интерфейс, освободить указатель на GIT.

Если ты в каком-то потоке создашь GIT, сохранишь указатель на него и будешь его использовать из другого потока, то работать не будет. По крайней мере у меня так не работало. GIT нужно создавать в тот моент когда он становится нужен.
Вызов событий COM из разных потоков
От: silart  
Дата: 05.11.09 03:02
Оценка:
Добрый день!

Столкнулся со следующей проблемой. Имеется inproc ActiveX компонент. Он генерирует события в отдельном потоке, причем с довольно большой частотой.
Событийный поток занимается обслуживанием очереди событий. Я привожу код полностью процедуры потока.

void EventManager::Routine()
{
HRESULT hr = E_FAIL;
tstring name;
vector<ml::com::Variant> args;
com_ptr<IDispatch, IID_IDispatch> pIEvent;
com_ptr<IEnumConnections, IID_IEnumConnections> pEnumConnections;

    // Блокировка потока, если список стал пустым
    if ( m_EventList.empty() )
    {
        mutex::scoped_lock lock(monitor);
        m_NewEvent.wait(lock);
    }

    // Предотвращаем одновременную генерацию событий из разных потоков.
    // (При использовании нескольких экземпляров компоненты в одном процессе)
    ExternalLock ext_lock;

mutex::scoped_lock cs_InvokeEvent(m_InvokeEventMutex);

    if ( !ExtractEvent(name, args) )
        return;

    DispParams dp(args);

cs_InvokeEvent.unlock();
    hr = pIConnectionPoint->EnumConnections( &pEnumConnections );
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED)
        return;
    ML_ASSERT( SUCCEEDED(hr) );

    CONNECTDATA cd;

cs_InvokeEvent.unlock();
    hr = pEnumConnections->Next(1, &cd, NULL);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED)
        return;
    ML_ASSERT( SUCCEEDED(hr) );

cs_InvokeEvent.unlock();
    hr = cd.pUnk->QueryInterface(IID_IDispatch, (void**)&pIEvent);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );

    BStr memberName( TtoW::from(name).c_str() );
    DISPID dispid;

cs_InvokeEvent.unlock();
    hr = pITypeInfo->GetIDsOfNames( memberName, 1, &dispid);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );

cs_InvokeEvent.unlock();

    hr = pIEvent->Invoke(dispid, IID_NULL, 
        GetUserDefaultLCID(), DISPATCH_METHOD, 
        dp.get(), NULL, NULL, NULL);

cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );
}


При старте событийный поток вызывает CoInitialize(NULL).

bool EventManager::InitDone(bool fInitDone)
{
    if (fInitDone)
    {
        CoInitialize(NULL);
        CoEnableCallCancellation(NULL);
        m_ThreadID = GetCurrentThreadId();
    }
    else
    {
        CoUninitialize();
    }

return true;
}


Если клиент, работает только с одним экземпляром COM объекта, то все отлично.
Как только клиент использует несколько экземпляров, все падает в тот момент, когда COM объекты начинают генерирование событий. Причем такой эффект не наблюдался на 2-х компьютерах, на которых велась разработка, он проявился на компьютере заказчика уже на объекте, где не представляется возможным подключиться отладчиком.
Долгие размышления привели меня к мысли использовать синглетный мьютекс в процедуре потока:

    // Предотвращаем одновременную генерацию событий из разных потоков.
    // (При использовании нескольких экземпляров компоненты в одном процессе)
    ExternalLock ext_lock;


т. е. мьютекс запрещает одновременное обращение к интерфейсу IDispatch из разных потоков.
Таким образом все работает, но мне непонятно почему проблема не проявилась сразу на компах разработчиков.
И вообще, правильно ли такое решение? Может в COM имеется более изящный подход к подобным проблемам?
Re: Вызов событий COM из разных потоков
От: silart  
Дата: 07.11.09 12:41
Оценка:
Товарищи, на вызовы

cs_InvokeEvent.unlock();
    ...
cs_InvokeEvent.lock();


не обращайте внимания. Они нужны для отмены события при выгрузки компоненты.
Т. е. они нужны для того, чтобы завершающий поток мог отменить вызов лишь в тот момент, когда событийный поток совершает вызов COM-метод. эти вызовы не имеют прямого отношения к этой теме.

Может эта проблема связана с апартментами?
Re[2]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 07.11.09 14:50
Оценка:
Здравствуйте, silart, Вы писали:

S>Может эта проблема связана с апартментами?


Может быть, если Вы передаёте в свои потоки событийные интерфейсы без маршалинга. Но по идее оно должно проявляться на всех компьютерах. Правда тут есть некоторые нюансы, связанные с тем, работаете ли Вы с объектом напрямую или через прокси. В общем, в COM есть правило — между апартаментами интерфейсные ссылки должы передаваться только через маршалинг, и это требование необходимо неукоснительно соблюдать. То есть в некоторых случаях это можно обойти, но это чистой воды хак. Я могу рекомендовать воспользоваться Global Interface table (IGlobalInterfaceTable), очень удобно для внутрипроцессного маршалинга.
"Нормальные герои всегда идут в обход!"
Re[3]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 07.11.09 14:53
Оценка:
Здравствуйте, Мизантроп, Вы писали:

Здравствуйте, silart,

Хотя если Вам важна производительность, то стоит рассмотреть использование многопоточных апартаментов (MTA)
"Нормальные герои всегда идут в обход!"
Re[3]: Вызов событий COM из разных потоков
От: silart  
Дата: 08.11.09 03:35
Оценка:
Здравствуйте, Мизантроп, Вы писали:

М>Может быть, если Вы передаёте в свои потоки событийные интерфейсы без маршалинга. Но по идее оно должно проявляться на всех компьютерах. Правда тут есть некоторые нюансы, связанные с тем, работаете ли Вы с объектом напрямую или через прокси. В общем, в COM есть правило — между апартаментами интерфейсные ссылки должы передаваться только через маршалинг, и это требование необходимо неукоснительно соблюдать. То есть в некоторых случаях это можно обойти, но это чистой воды хак. Я могу рекомендовать воспользоваться Global Interface table (IGlobalInterfaceTable), очень удобно для внутрипроцессного маршалинга.


Про IGlobalInterfaceTable я в курсе. Я его использую. У меня генерируются события из внутреннего потока внутри компоненты. Это все работает если клиент загружает один экземпляр компонента. Все начинает падать, когда клиент пытается загрузить несколько экземпляров компонента. Так как у нас inproc-сервер, компонент — это dll и все находится в одном адресном пространстве клиента. Как только несколько экземпляров компонента начинают генерировать события одновременно, все падает. Я поставил синглетный мьютекс как раз в то место, чтобы предотвратить генерацию событий разными потоками одновременно. Сейчас разные потоки генерируют события поочередно.

Хотелось бы выяснить в чем проблема и решается ли она средствами COM.
Re[4]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 08.11.09 06:28
Оценка:
Здравствуйте, silart, Вы писали:

S>Хотелось бы выяснить в чем проблема и решается ли она средствами COM.


Ну если у Вас нигде нет прямых передач интерфейсов без маршалинга и используется STA, то боюсь, что проблема не в СOM, а просто имеют место ошибки в Вашем коде. Дело в том, что вызовы в ST апартамент и так сериализованы, то есть поступают последовательно, и обработка нового вызова в данном апартаменте не начнётся, пока не завершится предыдущий. А потоки, из которых эти вызовы пришли (если они тоже STA), будут крутить свой внутренний Message loop для обеспечения реентерабельности, либо, в случае MTA, будут просто заблокированы, если правильно помню. Судя по тому, что добавление Вами явной синхронизации проблему снимает, то скорее всего имеет место всё-таки какая-то путаница с потоковыми моделями, а из Вашего изложения реализованная Вами схема выглядит весьма туманно, извините.

Разное же проявление на разных машинах может быть связано с разным количеством процессоров или ядер на этих машинах.
"Нормальные герои всегда идут в обход!"
Re[5]: Вызов событий COM из разных потоков
От: silart  
Дата: 09.11.09 09:19
Оценка:
Здравствуйте, Мизантроп, Вы писали:

реализованная Вами схема выглядит весьма туманно, извините.

Внесем ясность.

Вот потоковая функция, которая посылает события (Теперь я из нее убрал все лишнее).


void EventManager::Routine()
{
HRESULT hr = E_FAIL;
tstring name;
vector<ml::com::Variant> args;
com_ptr<IDispatch, IID_IDispatch> pIEvent;
com_ptr<IEnumConnections, IID_IEnumConnections> pEnumConnections;

    // Блокировка потока, если список стал пустым
    if ( m_EventList.empty() )
    {
        mutex::scoped_lock lock(monitor);
        m_NewEvent.wait(lock);
    }

    // Предотвращаем одновременную генерацию событий из разных потоков.
    // (При использовании нескольких экземпляров компоненты в одном процессе)
    ExternalLock ext_lock;

    if ( !ExtractEvent(name, args) )
        return;

    DispParams dp(args);

    hr = pIConnectionPoint->EnumConnections( &pEnumConnections );

    ML_ASSERT( SUCCEEDED(hr) );

    CONNECTDATA cd;

    hr = pEnumConnections->Next(1, &cd, NULL);

    ML_ASSERT( SUCCEEDED(hr) );

    hr = cd.pUnk->QueryInterface(IID_IDispatch, (void**)&pIEvent);

    ML_ASSERT( SUCCEEDED(hr) );

    BStr memberName( TtoW::from(name).c_str() );
    DISPID dispid;

    hr = pITypeInfo->GetIDsOfNames( memberName, 1, &dispid);    // &lpolestrName

    ML_ASSERT( SUCCEEDED(hr) );

    hr = pIEvent->Invoke(dispid, IID_NULL, 
        GetUserDefaultLCID(), DISPATCH_METHOD, 
        dp.get(), NULL, NULL, NULL);

    ML_ASSERT( SUCCEEDED(hr) );

    cd.pUnk->Release();
}


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

EventManager::EventManager(IID lib_id, IID event_id)
: m_EventId(event_id)
{
    HRESULT hr;
    com_ptr<ITypeLib, IID_ITypeLib> pITypeLib;

    hr = ::LoadRegTypeLib(lib_id, 1, 0, 0x00, &pITypeLib);
    ML_ASSERT( SUCCEEDED(hr) );

    // Get type information for the interface of the object.
    hr = pITypeLib->GetTypeInfoOfGuid(event_id, &pITypeInfo);
    ML_ASSERT( SUCCEEDED(hr) );

    pIConnectionPoint.reset( new ConnectionPoint(this, m_EventId) );
}


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

Событийный поток при старте вызывает CoInitialize()

bool EventManager::InitDone(bool fInitDone)
{
    if (fInitDone)
    {
        CoInitialize(NULL);
        CoEnableCallCancellation(NULL);
        m_ThreadID = GetCurrentThreadId();
    }
    else
    {
        CoUninitialize();
    }

return true;
}


Мизантроп, если речь идет о компоненте без событий, то с STA все понятно. А как задать STA, если речь идет о генерации событий? Как задать STA именно для событийного потока явно?
А могут быть проблемы от "кривости" клиента? Клиент написан на Borland C++ Builder.
Re[6]: Вызов событий COM из разных потоков
От: Vi2 Удмуртия http://www.adem.ru
Дата: 09.11.09 10:30
Оценка:
Здравствуйте, silart, Вы писали:

S>Событийный поток при старте вызывает CoInitialize()


А какая у тебя уверенность, что все методы объекта EventManager вызываются в том же потоке, что и конструктор?
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re[6]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 09.11.09 12:34
Оценка:
Здравствуйте, silart, Вы писали:

S>Внесем ясность.


Удачная мысль

S>Понятно, что конструктор вызывается другим (по отношению к отправляющему события) потоком.

S>Таким образом, pITypeInfo получается в конструкторе одним потоком, а используется в потоковой функции другим потоком. Нужно ли здесь использовать маршалинг? Ведь по идее должно было все обрушиться и при одном компоненте, а все работает.

Ну а с чего бы ему обрушаться Когда Вы просто передаёте куда-то интерфейс, без маршалинга, Вы фактически передаёте адрес таблицы в памяти, содержащей адреса методов, реаслизующих этот интерфейс. При этом вызов метода интерфейса — это просто самый обыкновенный вызов функции по адресу. Вы же не пишите в реализации каждого метода проверку валидности текущего апартамента и не генерите по этому поводу исключений, откуда же возьмётся обрушение? И никто этого не делает, потому что это не задача объекта. Это задача для прокси, и у него есть для этого необходимые средства. Когда Вы попытаетесь вызвать метод прокси из чужого для него апартамента, тот обратится за RPC буфером к менеджеру канала, а тот в ответ вернёт WRONG_THREAD. В случае же прямого вызова такой проверки сделать просто некому.

S>Мизантроп, если речь идет о компоненте без событий, то с STA все понятно. А как задать STA, если речь идет о генерации событий? Как задать STA именно для событийного потока явно?


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

От наличия событий тут тоже мало что зависит, только что общая логика усложняется. И любой событийный интерфейс, и любой "инфраструктурный" интерфейс поддержки событий — это самые обыкновенные интерфейсы, и к ним применимы все те-же правила, что и к любым другим. А правило простое — любой интерфейс должен пердаваться в другой апартамент только через маршалинг.
Например, Вы похоже pIConnectionPoint передаёте без маршалинга, что уже является ошибкой.

Но вообще мне, честно говоря, не очень нравится Ваша схема. Я бы сразу при подписке маршалировал событийный интерфейс в GIT, а в потоке один раз их демаршалировал. Разумеется, это потребует реализации собственного механизма подписки — IConnectionPointContainer для этого не слишком удачный выбор, но оно того стоит, по-моему. Если конечно вообще оправдана генерация событий в отдельном потоке. Может лучше чтобы доп. поток извещал главный о завершении обработки, а тот уже генерировал бы события?

S>А могут быть проблемы от "кривости" клиента? Клиент написан на Borland C++ Builder.


Я билдера не знаю, но сильно сомневаюсь.
"Нормальные герои всегда идут в обход!"
Re[7]: Вызов событий COM из разных потоков
От: silart  
Дата: 10.11.09 02:37
Оценка:
Здравствуйте, Vi2, Вы писали:

Vi2>Здравствуйте, silart, Вы писали:


S>>Событийный поток при старте вызывает CoInitialize()


Vi2>А какая у тебя уверенность, что все методы объекта EventManager вызываются в том же потоке, что и конструктор?


Конструктор вызывается в одном потоке, функция потока отправки событий — в другом.
Re[7]: Вызов событий COM из разных потоков
От: silart  
Дата: 10.11.09 03:30
Оценка:
Здравствуйте, Мизантроп, Вы писали:

S>>Понятно, что конструктор вызывается другим (по отношению к отправляющему события) потоком.

S>>Таким образом, pITypeInfo получается в конструкторе одним потоком, а используется в потоковой функции другим потоком. Нужно ли здесь использовать маршалинг? Ведь по идее должно было все обрушиться и при одном компоненте, а все работает.

М>Ну а с чего бы ему обрушаться Когда Вы просто передаёте куда-то интерфейс, без маршалинга, Вы фактически передаёте адрес таблицы в памяти, содержащей адреса методов, реаслизующих этот интерфейс. При этом вызов метода интерфейса — это просто самый обыкновенный вызов функции по адресу. Вы же не пишите в реализации каждого метода проверку валидности текущего апартамента и не генерите по этому поводу исключений, откуда же возьмётся обрушение?


Мезантроп, ну как же? У нас указатель получается в конструкторе (один поток), а используется в функции потока (другой поток). Вроде же разные апартменты? В этом месте я вас немного не понял. Поясните пожалуйста свою мысль.

М>Апартаментная модель для потока всегда задаётся одинаково — вызовом CoInitialize(Ex).

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

М>От наличия событий тут тоже мало что зависит, только что общая логика усложняется. И любой событийный интерфейс, и любой "инфраструктурный" интерфейс поддержки событий — это самые обыкновенные интерфейсы, и к ним применимы все те-же правила, что и к любым другим. А правило простое — любой интерфейс должен пердаваться в другой апартамент только через маршалинг.

М>Например, Вы похоже pIConnectionPoint передаёте без маршалинга, что уже является ошибкой.

Да, вы правы. Та же история. pIConnectionPoint создается в конструкторе (один поток), а используется в функции потока (другой поток). Опять возникает вопрос, почему все не обрушивется, когда имеется всего один экземпляр компонента? Ведь в этом случае присутствуют оба потока: и поток клиена, который создает компонент (вызов конструктора) и событийный поток, который отправляет события (функция Routine()). Мезантроп, если требуется маршалинг, подскажите как его лучше сделать?

М>Но вообще мне, честно говоря, не очень нравится Ваша схема. Я бы сразу при подписке маршалировал событийный интерфейс в GIT, а в потоке один раз их демаршалировал. Разумеется, это потребует реализации собственного механизма подписки — IConnectionPointContainer для этого не слишком удачный выбор, но оно того стоит, по-моему.


Мезантроп, GIT у мена реализован в ConnectionPoint:

ConnectionPoint::ConnectionPoint(IConnectionPointContainer* pIConnectionPointContainer, IID iid)
:    m_dwNextCookie(0), event_id(iid)
{
    ML_ASSERT(pIConnectionPointContainer != NULL);

    pContainer = pIConnectionPointContainer; // AddRef is not needed.

    HRESULT hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
        NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void **)&pGIT);

    ML_ASSERT( SUCCEEDED(hr) );
}

ConnectionPoint::~ConnectionPoint()
{
    pGIT->Release();
}

STDMETHODIMP ConnectionPoint::GetConnectionInterface(IID* piid)
{
     if (piid == NULL)
     {
         return E_POINTER;
     }

    *piid = event_id;
    return S_OK;
}

STDMETHODIMP ConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer** ppIConnectionPointContainer)
{
    if (ppIConnectionPointContainer == NULL)
    {
        return E_POINTER;
    }

    *ppIConnectionPointContainer = pContainer;
    pContainer->AddRef();
    return S_OK;
}

STDMETHODIMP ConnectionPoint::Advise(IUnknown* pIUnknownSink, DWORD* pdwCookie)
{
com_ptr<IUnknown, IID_IUnknown> pI;
HRESULT hr;

    if (pIUnknownSink == NULL || pdwCookie == NULL)
    {
        *pdwCookie = 0;
        return E_POINTER;
    }

    hr = pIUnknownSink->QueryInterface(event_id, (void**)&pI);

    if (SUCCEEDED(hr))
    {
        hr = pGIT->RegisterInterfaceInGlobal(pI, IID_IDispatch, &m_dwNextCookie);

        ML_ASSERT( SUCCEEDED(hr) );
        return S_OK;
    }
    else
    {
        return CONNECT_E_CANNOTCONNECT;
    }
}

STDMETHODIMP ConnectionPoint::Unadvise(DWORD dwCookie)
{
    pGIT->RevokeInterfaceFromGlobal(m_dwNextCookie);
    return S_OK;
}



М>Если конечно вообще оправдана генерация событий в отдельном потоке. Может лучше чтобы доп. поток извещал главный о завершении обработки, а тот уже генерировал бы события?


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

Может у вас будут каки-нибудь мысли по модернизации моей модели?
Re[8]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 10.11.09 14:34
Оценка:
Здравствуйте, silart, Вы писали:

S>Мезантроп, ну как же? У нас указатель получается в конструкторе (один поток), а используется в функции потока (другой поток). Вроде же разные апартменты? В этом месте я вас немного не понял. Поясните пожалуйста свою мысль.


Честно говоря, я даже не знаю, что ещё сказать Ну а отчего здесь что-то должно обрушиться-то? Попробуйте сформулировать прричину обрушения, как Вы её видите но не в терминах "один поток, другой поток", а более конкретно, типа, вот мы вызвали функцию, произошло то-то, из-за этого такой-то механизм или такой-то код возбудил исключение...или что Вы подразумеваете под обрушением? В интерфейсах никакой магии нет, всё вполне приземлённо, и работает только тот код, который имеется вналичии. Я же уже писал, интерфейс — только лишь адрес таблицы. Или можно сказать так, интерфейс == абстрактный класс. Вот Вы объявили переменную типа такого абстрактного класса, потом записали в неё адрес потомка этого класса, а затем начали из другого потока вызывать методы через эту переменную. Что здесь может "обрушиться" и главное кем?

Откровенно говоря, я сегодня сильно устал, и особо вникать мне не хочется, извините Я у Вас вижу маршалинг в GIT, а демаршалинг где? Иными словами, кто и как реализует IEnumConnections? Маршалировать ведь недостаточно, нужно ещё демаршалировать в том потоке, где интерфейс будет использоваться.
"Нормальные герои всегда идут в обход!"
Re[8]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 11.11.09 02:51
Оценка:
Здравствуйте, silart, Вы писали:.

S>Может у вас будут каки-нибудь мысли по модернизации моей модели?


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

Контейнер событий реализуете в виде отдельного объекта (КС). Сам COM-объект (O) при создании запускает доп. поток и ждёт окончания его инициализации, например с помощью Event'a или семафора. Доп. поток создаёт КС, маршалирует его интерфейс IConnectionPointContainer и извещает О взводом Event'а о завершении своей инициализации. Объект О демаршалирует IConnectionPointContainer и в дальнейшем по запросу клиента возвращает именно его, для этого можно просто переопределить реализацию QueryInterface. Получается, что О делегирует реализацию IConnectionPointContainer объекту КС. Как результат вся работа с событийными интерфейсами будет автоматически перенесена в доп. поток и никаких дополнительных действий по маршалингу — демаршалингу событийных интерфейсов не потребуется.
"Нормальные герои всегда идут в обход!"
Re[9]: Вызов событий COM из разных потоков
От: silart  
Дата: 17.11.09 07:27
Оценка:
Здравствуйте, Мизантроп!

Вы не знаете из-за чего может виснуть вызов Release() ?
Я вызываю Release() на интерфейс, полученный из GIT (это Event Sink) и в некоторых случаях он зависает. Т. е. поток в него входит и не может выйти.

Такая же ситуация с зависанием CoUninitialize().

CoUninitialize() вызывается в процедуре завершения потока, т. е. это последнее что вызывает поток.
Причем в если получение Event Sink из GIT поместить в одно место (процедура инициализации потока) то CoUninitialize() зависает постоянно. Указатель на Event Sink хранится все время, пока живет объект.
А если получение Event Sink из GIT поместить в процедуру потока, зависания не происходит, т. е. указатель на интерфейс Event Sink не будет храниться в переменной, а получаться в момент вызова события.

Я понимаю, это просто слова... Но хотелось бы узнать почему может зависнуть CoUninitialize() и Release(). Интересуют причины этого.
Re[9]: Вызов событий COM из разных потоков
От: silart  
Дата: 17.11.09 09:31
Оценка:
Здравствуйте, Мизантроп!

Проблема с сообщениями пока остась не решенной.

Добиться нужно следующего:

Есть компонент, inproc-сервер. У него есть методы и события. Внутри компонента есть рабочий поток, генерирующий данные. События по идее он и должен генерировать, но так как обработка событий ложится на клиента, а поток критичный по времени, этого допустить нельзя. Иначе клиент будет затормаживать работу компонента. Значит нужно создать другой поток, который будет генерировать события из данных, предоставляемых ему рабочим потоком.

Но событийный поток должен кто-то запускать и останавливать. Для этого есть 2 метода у компонента: Init() и Done(). Метод Init() вызывается в конструкторе окна клиента, Done() соответственно — в деструкторе.
Здесь и кроется проблема.

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

Для того чтобы она не происходила, я завершаю вызов события принудительно:

    HRESULT hr = E_FAIL;
    com_ptr<ICancelMethodCalls, IID_ICancelMethodCalls> pCancel;
    hr = CoGetCancelObject(m_ThreadID, IID_ICancelMethodCalls, (void**)&pCancel);
    if (SUCCEEDED(hr))
    {
        hr = pCancel->Cancel(0);
    }


А в потоковой функции, которая генерирует события, в это время происходит следующее:

    hr = pEventSink->Invoke(dispid, IID_NULL,
        GetUserDefaultLCID(), DISPATCH_METHOD, 
        dp.get(), NULL, NULL, NULL);

    // принудительный выход
    if (hr == RPC_E_CALL_CANCELED) 
    {
        return;
    }


Хотел бы узнать, правильный подход к такой проблеме? Ситуация ведь довольно распространенная. Каким образом принято строить компоненты, которые генерируют события из отдельного потока? Как принято организовывать управление событийным потоком?

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

Может ли быть связанно зависание с тем, что EventSink в клиенте уже уничтожился к тому времени, когда я вызвал Release()? Ведь завершение потока происходит во время закрытия окна клиента. А если не вызывать Release(), тогда виснет Counitialize().

Хотел бы уяснить для себя следующие вопросы:
1. Правильный ли подход к организации асинхронных событий я использую?
2. Из-за чего происходит зависание Counitialize() и Release()?

Заранее благодарен за любую помощь!
Re[8]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 20.11.09 15:46
Оценка:
Здравствуйте, silart, Вы писали:

S>
S>ConnectionPoint::ConnectionPoint(IConnectionPointContainer* pIConnectionPointContainer, IID iid)
S>:    m_dwNextCookie(0), event_id(iid)
S>{
S>    ML_ASSERT(pIConnectionPointContainer != NULL);

S>    pContainer = pIConnectionPointContainer; // AddRef is not needed.

S>    HRESULT hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
S>        NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void **)&pGIT);

S>    ML_ASSERT( SUCCEEDED(hr) );
S>}

S>ConnectionPoint::~ConnectionPoint()
S>{
    pGIT->>Release();
S>}

S>STDMETHODIMP ConnectionPoint::GetConnectionInterface(IID* piid)
S>{
S>     if (piid == NULL)
S>     {
S>         return E_POINTER;
S>     }

S>    *piid = event_id;
S>    return S_OK;
S>}

S>STDMETHODIMP ConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer** ppIConnectionPointContainer)
S>{
S>    if (ppIConnectionPointContainer == NULL)
S>    {
S>        return E_POINTER;
S>    }

S>    *ppIConnectionPointContainer = pContainer;
S>    pContainer->AddRef();
S>    return S_OK;
S>}

S>STDMETHODIMP ConnectionPoint::Advise(IUnknown* pIUnknownSink, DWORD* pdwCookie)
S>{
S>com_ptr<IUnknown, IID_IUnknown> pI;
S>HRESULT hr;

S>    if (pIUnknownSink == NULL || pdwCookie == NULL)
S>    {
S>        *pdwCookie = 0;
S>        return E_POINTER;
S>    }

S>    hr = pIUnknownSink->QueryInterface(event_id, (void**)&pI);

S>    if (SUCCEEDED(hr))
S>    {
S>        hr = pGIT->RegisterInterfaceInGlobal(pI, IID_IDispatch, &m_dwNextCookie);

S>        ML_ASSERT( SUCCEEDED(hr) );
S>        return S_OK;
S>    }
S>    else
S>    {
S>        return CONNECT_E_CANNOTCONNECT;
S>    }
S>}

S>STDMETHODIMP ConnectionPoint::Unadvise(DWORD dwCookie)
S>{
S>    pGIT->RevokeInterfaceFromGlobal(m_dwNextCookie);
S>    return S_OK;
S>}
S>


Прочитав этот Ваш пример, я сделал то же самое. Переопределил методы Advise и Unadvise, добавив в них занесение интерфейса в GIT.
Действительно, интерфейс регистрируется в GIT, hr = S_OK, а потом удачно дерегистрируется.
И интерфейс достается из GIT без проблем. Но при отработке ничего не происходит. Почему это может быть, Вы, скорее всего, уже обошли этот вопрос.
Вот код:

template <class T>
STDMETHODIMP TEvents_UpTask<T>::Advise(IUnknown* pUnkSink,
    DWORD* pdwCookie)
{
     T* pT = static_cast<T*>(this);
    IUnknown* p;
    IID iid;
    GetConnectionInterface(&iid);
    HRESULT hRes = pUnkSink->QueryInterface(iid, (void**)&p);
    if (SUCCEEDED(hRes))
    {
          pT->Lock();
          DWORD myCookie;
          gp_GIT->RegisterInterfaceInGlobal(p, IID_IDispatch, &myCookie);
          //это std::map<DWORD, DWORD> для хранения всех Cookie
          m_GitPointers[*pdwCookie] = myCookie;

          pT->Unlock();
    }
    return hRes;
}

template <class T>
STDMETHODIMP TEvents_UpTask<T>::Unadvise(DWORD dwCookie)
{
...
        //Arkady Add
        gp_GIT->RevokeInterfaceFromGlobal(m_GitPointers[dwCookie]);
...
}

//метод, который должен вызываться при старте потока, генерирующего данные (такого же, как у Вас).
template <class T> HRESULT
TEvents_UpTask<T>::Fire_OnListenStart(void)
{
  DWORD dw;

  T * pT = (T*)this;
  pT->Lock();
  IUnknown ** pp = m_vec.begin();
  while (pp < m_vec.end())
  {
    if (*pp != NULL)
    {
      dw = (DWORD)pp;

      IDispatch* dp;
      HRESULT hr = gp_GIT->GetInterfaceFromGlobal(m_GitPointers[dw], IID_IDispatch, (void**)&dp);
      if (SUCCEEDED(hr))
      {
        IUpTaskEvents* Event;
        hr = dp->QueryInterface(DIID_IUpTaskEvents, (void**)&Event);
        if (SUCCEEDED(hr))
        {
            Event->OnListenStart();
        }
      }
    }
    pp++;
  }
  pT->Unlock();
}


Так вот, сервер попадает в Event->OnListenStart(); и выполняет этот метод, но при этом клиентский обработчик об этом почему-то не знает.
Когда то же самое событие вызывается в основном потоке сервера, клиентская реализация интерфейса отрабатывает. А тут — нет.
Не знаете, почему?
С уважением, Аркадий.
Re[9]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 22.11.09 19:33
Оценка:
Конечно, CoInitialize(NULL, COINIT_MULTITHREADED) и CoUninitialize() вызываются в начале и в конце второго потока.
С уважением, Аркадий.
Re[10]: Вызов событий COM из разных потоков
От: silart  
Дата: 23.11.09 06:22
Оценка:
Здравствуйте, Аркадий!

Вот ваша функция генерации событий:

template <class T> HRESULT
TEvents_UpTask<T>::Fire_OnListenStart(void)
{
  DWORD dw;

  T * pT = (T*)this;
  pT->Lock();
  IUnknown ** pp = m_vec.begin();
  while (pp < m_vec.end())
  {
    if (*pp != NULL)
    {
      dw = (DWORD)pp;

      IDispatch* dp;
      HRESULT hr = gp_GIT->GetInterfaceFromGlobal(m_GitPointers[dw], IID_IDispatch, (void**)&dp);
      if (SUCCEEDED(hr))
      {
        IUpTaskEvents* Event;
        hr = dp->QueryInterface(DIID_IUpTaskEvents, (void**)&Event);
        if (SUCCEEDED(hr))
        {
            Event->OnListenStart();
        }
      }
    }
    pp++;
  }
  pT->Unlock();
}


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

      if (SUCCEEDED(hr))
      {
        IUpTaskEvents* Event;
        hr = dp->QueryInterface(DIID_IUpTaskEvents, (void**)&Event);
        if (SUCCEEDED(hr))
        {
            Event->OnListenStart();
        }
      }

Т. е. я не запрашиваю у интерфейса, полученного из GIT конкретный disp-интерфейс, а использую интерфейс IDispatch напрямую.

Вот моя реализация:

Функция вызывается при старте и остановке потока.
bool EventManager::InitDone(bool fInitDone)
{
HRESULT hr;

    if (fInitDone)
    {
        hr = CoInitialize(NULL);

        // Получаем интерфейс событий
        DWORD cookie = m_connectionPoint->cookie();
        m_eventSink = getEventSink(cookie);

        m_IsStoped = false;
    }
    else
    {
        CoUninitialize();

        m_IsStoped = true;
    }

return true;
}


Функция, получающая Event Sink из GIT.
ml::com::com_ptr<IDispatch, IID_IDispatch> EventManager::getEventSink(DWORD cookie)
{
    HRESULT hr;
    ml::com::com_ptr<IDispatch, IID_IDispatch> eventSink;
    ml::com::com_ptr<IGlobalInterfaceTable, IID_IGlobalInterfaceTable> GIT;

    hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, 
        CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void **)&GIT);

    ML_ASSERT( SUCCEEDED(hr) );

    hr = GIT->GetInterfaceFromGlobal(cookie, IID_IDispatch, 
        reinterpret_cast<void **>(&eventSink));

    ML_ASSERT( SUCCEEDED(hr) );

return eventSink;
}


Функция потока, вызывается периодически потоком, генерирует события.
void EventManager::Routine()
{
HRESULT hr = E_FAIL;
tstring name;
vector<ml::com::Variant> args;
DISPID dispid;

    // Блокировка потока, если список стал пустым
    if ( m_eventList.empty() )
    {
        mutex::scoped_lock lock(monitor);
        m_NewEvent.wait(lock);
    }

    // Берем новое событие из очереди
    if ( !ExtractEvent(name, args) )
        return;

    // Готовим для него все необходимое
    BStr memberName( TtoW::from(name).c_str() );
    DispParams dp(args);

    hr = m_typeInfo->GetIDsOfNames( memberName, 1, &dispid);
    ML_ASSERT( SUCCEEDED(hr) );

    // Вызываем.
    hr = m_eventSink->Invoke(dispid, IID_NULL,
        GetUserDefaultLCID(), DISPATCH_METHOD, 
        dp.get(), NULL, NULL, NULL);
    ML_ASSERT( SUCCEEDED(hr) );
}


Т. е. у меня поток в начальный момент времени при старте получает Event Sink, сохраняет его в переменной внутри класса, при этом полученный интерфейс Event Sink'а используется напрямую.
Re[11]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 23.11.09 14:21
Оценка:
Здравствуйте, silart, Вы писали:

S>Т. е. у меня поток в начальный момент времени при старте получает Event Sink, сохраняет его в переменной внутри класса, при этом полученный интерфейс Event Sink'а используется напрямую.


Сделал — то же самое :( Причем попробовал сохранять в GIT и извлекать, используя напрямую, как IDispatch, так и сам IUpTaskEvents.
В случае, если FireEvent вызывается в основном потоке — событие исполняется, всё как и должно быть. В случае, если в слушающем потоке — не работает.

Может быть дело в том, что клиентом моему серверу выступает VBA приложение? =(
С уважением, Аркадий.
Re[13]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 26.11.09 02:47
Оценка:
Здравствуйте, silart, Вы писали:

S>Если ты в каком-то потоке создашь GIT, сохранишь указатель на него и будешь его использовать из другого потока, то работать не будет. По крайней мере у меня так не работало. GIT нужно создавать в тот моент когда он становится нужен.


Вероятно, Вы что-то не так сделали. Стандартный GIT — объект с нейтральной моделью, то есть получив его интерфейс в одном апартаменте, этот интерфейс затем можно использовать в любом другом апартаменте. Фактически метод RegisterInterfaceInGlobal создаёт стрим на HGlobal, маршалирует в него интерфейс с помощью CoMarshalInterface, и сохраняет HGlobal в своём внутреннем списке. Метод GetInterfaceFromGlobal по куку ищет HGlobal, опять из него создаёт стрим и вызывает CoUnmarshalInterface. Так что никаких препятствий для использования одной ссылки на IGlobalInterfaceTable из всех апартаментов нет.
"Нормальные герои всегда идут в обход!"
Re[14]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 26.11.09 09:37
Оценка:
Здравствуйте, Мизантроп, Вы писали:

М>Здравствуйте, silart, Вы писали:


S>>Если ты в каком-то потоке создашь GIT, сохранишь указатель на него и будешь его использовать из другого потока, то работать не будет. По крайней мере у меня так не работало. GIT нужно создавать в тот моент когда он становится нужен.


М>Вероятно, Вы что-то не так сделали. Стандартный GIT — объект с нейтральной моделью, то есть получив его интерфейс в одном апартаменте, этот интерфейс затем можно использовать в любом другом апартаменте. Фактически метод RegisterInterfaceInGlobal создаёт стрим на HGlobal, маршалирует в него интерфейс с помощью CoMarshalInterface, и сохраняет HGlobal в своём внутреннем списке. Метод GetInterfaceFromGlobal по куку ищет HGlobal, опять из него создаёт стрим и вызывает CoUnmarshalInterface. Так что никаких препятствий для использования одной ссылки на IGlobalInterfaceTable из всех апартаментов нет.


Я пробовал и инициализировать новый указатель на GIT в каждом апартменте, и использовать один, инициализированный в одном — результат одинаков. Что-то не так — это что? Может быть, я перечислю, что есть, а Вы сможете подсказать, где там может быть ошибка?

Есть DLL, внутри которой крутится поток. На вход эта DLL принимает интерфейс IMyEvents, дергая его методы тогда, когда мне нужны события. Реализовано всё руками и всё можно переделать. Интерфейс этот содержит три метода OnStart (перед стартом цикла прослушивания сообщений), OnMessage (если пришло сообщение) и OnFinish (после завершения цикла прослушивания сообщений). Все эти события вызываются в моем доп потоке DLL.

Далее есть .exe КОМ сервер, который цепляет эту ДЛЛ. Он реализует метод OnStart — добавляя туда CoInitializeEx(NULL, MultiThreaded..), реализует метод OnFinish, добавляя туда CoUninitialize(); , и реализует метод OnMessage, добавляя туда обертку Fire_OnMessage уже своего событийного интерфейса (чтобы отрабатывалось событие).

Все методы IMyEvents выполняются в дополнительном потоке... сначала там выполнится CoInitializeEx(...), потом события, достающиеся из GIT (это зашито в Fire_OnMessage) потом CoUninitialize().

Что тут может быть не так? Правильно я понимаю, что клиент на VBA должен цеплять эти события?
С уважением, Аркадий.
Re[15]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 26.11.09 12:23
Оценка:
Здравствуйте, Аркадий, Вы писали:

S>>>Если ты в каком-то потоке создашь GIT, сохранишь указатель на него и будешь его использовать из другого потока, то работать не будет. По крайней мере у меня так не работало. GIT нужно создавать в тот моент когда он становится нужен.


А>Есть DLL, внутри которой крутится поток. На вход эта DLL принимает интерфейс IMyEvents, дергая его методы тогда, когда мне нужны события. Реализовано всё руками и всё можно переделать. Интерфейс этот содержит три метода OnStart (перед стартом цикла прослушивания сообщений), OnMessage (если пришло сообщение) и OnFinish (после завершения цикла прослушивания сообщений). Все эти события вызываются в моем доп потоке DLL.


А>Далее есть .exe КОМ сервер, который цепляет эту ДЛЛ. Он реализует метод OnStart — добавляя туда CoInitializeEx(NULL, MultiThreaded..), реализует метод OnFinish, добавляя туда CoUninitialize(); , и реализует метод OnMessage, добавляя туда обертку Fire_OnMessage уже своего событийного интерфейса (чтобы отрабатывалось событие).


А>Все методы IMyEvents выполняются в дополнительном потоке... сначала там выполнится CoInitializeEx(...), потом события, достающиеся из GIT (это зашито в Fire_OnMessage) потом CoUninitialize().


Ну вроде в описании всё понятно, кроме нескольких вещей. Во-первых, это загадочная фраза

добавляя туда обертку Fire_OnMessage уже своего событийного интерфейса (чтобы отрабатывалось событие).

Что Вы имели в виду?
Далее, какие апартаментные модели поддерживает объект, реализующий IMyEvents? Кто и как создаёт этот объект, в какой апартамент входит поток, в котором создаётся этот объект?
И самое главное, а что не получается-то?

На всякий случай:

добавляя туда CoInitializeEx(NULL, MultiThreaded..)


То есть вызвавший этот метод поток подключается к MTA. MTA всегда один на процесс, все потоки данного процесса, вызвавшие так CoInitializeEx, являются членами одного апартамента. Маршалинга между членами одного апартамента не требуется, и переключение потоков внутри апартамента не производится, методs работают в том потоке, в котором были вызваны.

Ну а лучше всего, вероятно:

Ты не умничай, ты код давай!


(© Неизвестный посетитель форума)
"Нормальные герои всегда идут в обход!"
Re[16]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 26.11.09 13:00
Оценка:
Здравствуйте, Мизантроп, Вы писали:

М>Ну вроде в описании всё понятно, кроме нескольких вещей. Во-первых, это загадочная фраза
М>[q]
М>добавляя туда обертку Fire_OnMessage уже своего событийного интерфейса (чтобы отрабатывалось событие).

М>Что Вы имели в виду?[/q]
Я имел ввиду, что есть интерфейс событий DLL — IMyEvents, а есть интерфейс событий сервера (.exe), который называется IUpEvents. С методом OnMessage.
Этот метод оборачивается вызовом Fire_OnMessage (где интерфейс извлекается из GIT и используется его метод).

Этот вызов Fire_OnMessage я помещаю в метод OnMessage интерфейса IMyEvents. Таким образом добиваясь, чтобы из потока dll, где дергается метод OnMessage интерфейса IMyEvents, через него дергался Fire_OnMessage.

М>Далее, какие апартаментные модели поддерживает объект, реализующий IMyEvents? Кто и как создаёт этот объект, в какой апартамент входит поток, в котором создаётся этот объект?
М>И самое главное, а что не получается-то?

1) объект, реализующий IMyEvents — обычный внутренний объект сервера, который создается .exe сервером на этапе инициализации сервера и передается в DLL. Как можно настроить ему апартментовую модель?
2) Поток, в котором создается этот объект — основной поток сервера, его потоковая модель настраивается наличием такого макроса внутри объекта
DECLARE_THREADING_MODEL(otFree);
и следующей директивы
#define ATL_FREE_THREADED


В опциях проекта (CBuilder6) установлено
Instancing: multiple use
OLE initialization COINIT_XXX_Flag: MULTITHREADED
Threading Model: Free

Это, надо понимать, распространяется не на все объекты сервера...

3) Не получается следующее: Если вызов Fire_OnMessage находится в основном потоке процесса, то они корректно отрабатываются. А помещенный в объект, реализующий IMyEvent, и вызываемый там в другом потоке — не дает никакого эффекта, т.е. полностью дебаггер проходит по телу Fire_OnMessage и не дает никакого результата.

М>На всякий случай:

М>

М>добавляя туда CoInitializeEx(NULL, MultiThreaded..)


М>То есть вызвавший этот метод поток подключается к MTA. MTA всегда один на процесс, все потоки данного процесса, вызвавшие так CoInitializeEx, являются членами одного апартамента. Маршалинга между членами одного апартамента не требуется, и переключение потоков внутри апартамента не производится, методs работают в том потоке, в котором были вызваны.

Т.е. теоретически вообще всё должно отрабатывать просто при простой передаче указателя на IUpEvents? А у меня не работает даже если я его передаю через GIT..

М>Ну а лучше всего, вероятно:

Ты не умничай, ты код давай!


М>(© Неизвестный посетитель форума) :))

=)) Там очень много методов, возможно, влияющих, может быть лучше я вышлю Вам на почту части кода, которые этим занимаются, а после того, как удастся понять, в чем дело — тогда уже сюда ту самую проблемную часть кода можно будет выложить с объяснением "для потомков"?
С уважением, Аркадий.
Re[17]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 26.11.09 14:14
Оценка:
Здравствуйте, Аркадий, Вы писали:

А>1) объект, реализующий IMyEvents — обычный внутренний объект сервера, который создается .exe сервером на этапе инициализации сервера и передается в DLL. Как можно настроить ему апартментовую модель?


Никак, да и не нужно. Модель влияет только при создании объекта через сервис CoCreateInstance(Ex).

А>3) Не получается следующее: Если вызов Fire_OnMessage находится в основном потоке процесса, то они корректно отрабатываются. А помещенный в объект, реализующий IMyEvent, и вызываемый там в другом потоке — не дает никакого эффекта, т.е. полностью дебаггер проходит по телу Fire_OnMessage и не дает никакого результата.


А>Т.е. теоретически вообще всё должно отрабатывать просто при простой передаче указателя на IUpEvents? А у меня не работает даже если я его передаю через GIT..


Остаётся не прояснённым вопрос, откуда берётся внезапно объявившийся IUpEvents Кто его реализует, как Вы его получаете, ну и тд, всё то-же, что уже спрашивалось про IMyEvent. Нужен ли для него маршаллинг, зависит от этого. А так — да, если все объекты созданы в одном апартаменте, и этот апартамент не противоречит их потоковой модели, то всё должно работать напрямую.

Дело в том, что так — "дебаггер проходит по телу Fire_OnMessage и не дает никакого результата" — просто не бывает. Если вызов метода реально присутствует в скомпилённом коде, и выполнение до него доходит, то метод просто физически не может быть не вызван. А вот что происходит внутри этого метода, это уже другой вопрос. Может там что-нибудь типа

if (GetCurrentThreadId() != InitThreadId) return 0;

вот и получится, что при вызове этого метода из "неправильного" потока "ничего не происходит". Ну и определённая разница может быть от того, вызываете ли Вы метод самого объекта, или-же Ваш код имеет дело с прокси.

А>=)) Там очень много методов, возможно, влияющих, может быть лучше я вышлю Вам на почту части кода, которые этим занимаются, а после того, как удастся понять, в чем дело — тогда уже сюда ту самую проблемную часть кода можно будет выложить с объяснением "для потомков"?


Оставим этот вариант на крайний случай
"Нормальные герои всегда идут в обход!"
Re[18]: Вызов событий COM из разных потоков
От: Аркадий Россия  
Дата: 26.11.09 14:55
Оценка:
Здравствуйте, Мизантроп, Вы писали:

М>Остаётся не прояснённым вопрос, откуда берётся внезапно объявившийся IUpEvents :) Кто его реализует, как Вы его получаете, ну и тд, всё то-же, что уже спрашивалось про IMyEvent. Нужен ли для него маршаллинг, зависит от этого. А так — да, если все объекты созданы в одном апартаменте, и этот апартамент не противоречит их потоковой модели, то всё должно работать напрямую.

IUpEvent объявлен уже в ..._TLB.h следующим образом:
interface DECLSPEC_UUID("{3D382A6F-126C-486D-AB6F-B45C09D55667}") IUpTaskEvents;

interface IUpTaskEvents : public TDispWrapper<IDispatch>
{
  HRESULT __fastcall OnMessage(Upiterobj_tlb::IMessage* Message/*[in]*/)
  {
    _TDispID _dispid(/* OnMessage */ DISPID(5));
    TAutoArgs<1> _args;
    _args[1] = (LPDISPATCH)(Upiterobj_tlb::IMessage*)Message /*[VT_USERDEFINED:1]*/;
    return OleFunction(_dispid, _args);
  }
};

Я его получаю при создании объекта (Up) через Wizard Builder-а, указав, что объекту нужны события.
Так выглядит автоматически создаваемая при этом голова объекта Up:
class ATL_NO_VTABLE TUpImpl : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<TUpImpl, &CLSID_Up>,
  public IConnectionPointContainerImpl<TUpImpl>,
 // public TEvents_Up<TUpImpl>,
  public TEventsWrapper<TUpTaskImpl>,
  public IDispatchImpl<IUpTask, &IID_IUpTask, &LIBID_UpiterObj>
{

Замечу при этом, что TEvents_Up — это обертка интерфейса, позволяющая "дергать" методы интерфейса, который реализовал клиент и передал через advise(...). Я её переписал с использованием GIT, назвал TEventsWrapper. Работает так же, только сохраняет подключившиеся интерфейсы в GIT, а не в свой массив интерфейсов, это должно было помочь вызывать событие из другого потока, но не помогло.

М>Дело в том, что так — "дебаггер проходит по телу Fire_OnMessage и не дает никакого результата" — просто не бывает. Если вызов метода реально присутствует в скомпилённом коде, и выполнение до него доходит, то метод просто физически не может быть не вызван. А вот что происходит внутри этого метода, это уже другой вопрос. Может там что-нибудь типа
М>

М>if (GetCurrentThreadId() != InitThreadId) return 0;
М>

М>вот и получится, что при вызове этого метода из "неправильного" потока "ничего не происходит". Ну и определённая разница может быть от того, вызываете ли Вы метод самого объекта, или-же Ваш код имеет дело с прокси.

Да, но доступ до тела Fire_OnMessage у меня есть, и я по нему "хожу" дебаггером и смотрю, что по всем своим вызовам он проходится. Один из вызовов в Fire_OnMessage — это OnMessage() интерфейса IUpEvents, который отрабатывается, не давая никакой реакции. Но доступа до тела IUpEvents у меня нет, т.к. он передавался от клиента, через advise().

При этом если Fire_OnMessage выполняется в основном потоке сервера, то OnMessage интерфейса IUpEvents отрабатывается.
С уважением, Аркадий.
Re[19]: Вызов событий COM из разных потоков
От: Мизантроп  
Дата: 26.11.09 16:40
Оценка:
Здравствуйте, Аркадий, Вы писали:

Вы поймите, код Вашего приложения видите Вы, но его не видят те, кто читает Ваши посты на форуме. Ваше последнее сообщение порождает опять больше вопросов, чем даёт ответов. Например, откуда у Вас взялся файл с секретным именем "..._TLB.h"? Вам его кто-то подарил? Вы импортировали чью-то библиотеку типов? Вы сами создали библиотеку типов?

А>Да, но доступ до тела Fire_OnMessage у меня есть, и я по нему "хожу" дебаггером и смотрю, что по всем своим вызовам он проходится. Один из вызовов в Fire_OnMessage — это OnMessage() интерфейса IUpEvents, который отрабатывается, не давая никакой реакции. Но доступа до тела IUpEvents у меня нет, т.к. он передавался от клиента, через advise().


А>При этом если Fire_OnMessage выполняется в основном потоке сервера, то OnMessage интерфейса IUpEvents отрабатывается.


Если метод вызывается, значит он вызывается. Точка. Всё остальное зависит от реализации этого метода, и если у Вас нет доступа к этой реализации, то поделать Вы ничего не сможете. Остаётся, правда, не ясно — проверяете ли Вы результат вызова функций, в частности функций маршалинга и демаршалинга IUpEvents, и поддерживает ли этот интерфейс маршалинг вообще.

Я, например, понятия не имею, какие обёртки генерит билдер, и что, например, будет делать эта обёртка при отсутствии у неё реальной ссылки на внешний интерфейс.

А>Я его получаю при создании объекта (Up) через Wizard Builder-а, указав, что объекту нужны события.


Кого — его? Через какой визард? Создания объекта с поддержкой событий? Или создания обёртки для чужого событийного интерфейса?

В общем, в таком духе. Старайтесь сами себе задавать подобные вопросы, когда описываете проблему, тем более что спрашивал-то я не это, а кто, где и как создаёт инстанс, реализующий IUpEvents.

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

У Вас есть eхе-приложение. В нём определён некий COM-объект с поддержкой событий через интерфейс то-ли IUpEvents, то-ли IUpTaskEvents. Внешний клиент создаёт экзэмпляр этого объекта и подписывается на его события, передав реализованный этим клиентом интерфейс IUpEvents (или IUpTaskEvents?). В ответ Ваш COM объект загружает DLL, которой передаёт свой интерфейс IMyEvents. DLL создаёт доп. поток и из этого потока вызывает методы интерфейса IMyEvents. Метод IMyEvents.OnMessage Вашего COM-объекта вызывает функцию Fire_OnMessage, которая, в свою очередь, должна вызвать
IUpEvents.OnMessage.

Так? В методе Advise Вы маршалируете пришедший извне IUpEvents с помощью GIT. В Fire_OnMessage Вы его демаршалируете и пытаетесь вызвать его метод. Так? Если Advise вызывается в потоке-члене MTA (а именно так и должно быть судя по Вашим словам), и Fire_OnMessage вызывается членом MTA, то маршалинг между ними не нужен, всё и так обязано работать. Но и помешать он тоже не может, при демаршалинге Вы должны получить тот самый экзэмпляр интерфейсной ссылки, который маршалировали. В этом можно убедиться, просто сравнив эти ссылки друг с другом как целые или указатели.

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

Потому, что чудес не бывает, и "Если метод вызывается, значит он вызывается. Точка".
"Нормальные герои всегда идут в обход!"
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.