Здравствуйте.
Есть Qt 5.1.1 и Windows XP 32 bit.
Есть некий COM объект, вот часть idl:
COM объект реализует несколько интерфейсов, среди прочих IAgentEvents
[
object,
uuid(0610FFE9-0FAA-46d1-B3E1-A1377D793E10),
oleautomation,
pointer_default(unique)
]
interface IAgentEvents:IUnknown
{
HRESULT InitOnAdvise([in] BSTR status,
[in] IQueueCollection* queues,
[in] IAgentPhoneSession* session);
HRESULT OnPhoneSessionOpened( [in] IAgentPhoneSession* session);
HRESULT OnStatusChanged( [in] BSTR status);
};
И есть объект реализующий этот интерфейс:
[
uuid(F24B5336-8DDA-4f8f-B561-78AE4D335657),
helpstring("Call Center Agent")
]
coclass CallCenterAgent{
[default] interface IAgent;
[source,default] interface IAgentEvents;
};
получаю объект агента следующим образом:
QAxObject* agent = callCenter->querySubObject("Agent()");
объект нормально создается, но к объекту не "цепляются" события:
InitOnAdvise(...);
OnPhoneSessionOpened(...);
OnStatusChanged(...);
посмотрел по исходному коду Qt:
события в QAxObject инициализируются (вызывается QAxBase::connectNotify()), только во время первого QObject::connect к сигналу этого объекта.
и еще в qaxbase.cpp есть код:
TYPEKIND eventKind = eventAttr->typekind;
eventinfo->ReleaseTypeAttr(eventAttr);
if (eventKind != TKIND_DISPATCH) {
eventinfo->Release();
break;
}
как раз игнорируется мой случай
В другом COM объетке связанном с вышеописанным есть такое описание:
[
uuid(84692E4E-5D0D-4f41-B298-B974792FD8A4),
helpstring("Connection Class")
]
coclass CSConnection
{
[default] interface ICSConnection;
[default, source] dispinterface _ICSConnectionEvents;
};
В котором события описаны как:
dispinterface _ICSConnectionEvents
С отловом событий в этом объекте проблем нет.
Как быть? Как поймать события?
Может кто знает куда копнуть для более низкоуровневой реализации отлова событий?
.net цепляется без проблем к этому объекту и его событиям.
Уже думаю писать обертку .net + qt, но тоже пока не знаю с какой стороны подойти.
Решение есть, почти написал, приведу в порядок и опубликую.
Но есть вопрос к знатокам COM (ActiveX).
Можно ли объединить QObject и IUnknown в одном объекте (нужно для задействования сигнального механизма Qt)? И тот и другой классы требуют чтобы были указаны первым при наследовании, особенно это касается IUnknown для правильного формирование vtable. Я прав?
interface IAgentEvent : public IUnknown
{
...
}
class AgentEvent : public QObject, public IAgentEvent
{
Q_OBJECT
...
}
Выкладываю решение:
Класс для подключения к event'ам:
C++ (Qt)
#ifndef EVENTSINK_H
#define EVENTSINK_H
#include <OCIdl.h>
class EventSinkBase
{
public:
EventSinkBase(const IID* iidEvent, IUnknown* that)
: _iidEvent(iidEvent)
, _that(that)
, _cpoint(NULL)
, _cookie(NULL)
{ }
virtual ~EventSinkBase()
{
unadvise();
}
const IID* iidEvent() const { return _iidEvent; }
bool advise(IConnectionPoint* cpoint)
{
_cpoint = cpoint;
_cpoint->AddRef();
_cpoint->Advise(_that, &_cookie);
return _cookie;
}
void unadvise()
{
if (_cpoint) {
_cpoint->Unadvise(_cookie);
_cpoint->Release();
_cpoint = NULL;
_cookie = NULL;
}
}
void addRef() { _that->AddRef(); }
void release() { _that->Release(); }
private:
const IID* _iidEvent;
IUnknown* _that;
IConnectionPoint* _cpoint;
ULONG _cookie;
};
template<class TEvent>
class EventSink : public TEvent, public EventSinkBase
{
public:
EventSink(const IID* iidEvent) : EventSinkBase(iidEvent, this) { }
virtual STDMETHODIMP QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown) {
*ppv = static_cast<IUnknown*>(this);
} else
if (iid == *iidEvent()) {
*ppv = static_cast<TEvent*>(this);
} else {
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
virtual STDMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_ref);
}
virtual STDMETHODIMP_(ULONG) Release()
{
ULONG refCount = InterlockedDecrement(&_ref);
if (!refCount) {
delete this;
}
return refCount;
}
private:
ULONG _ref;
};
#endif // EVENTSINK_H
от него наследуем наш обработчик:
C++ (Qt)
class AgentEvents
: public EventSink<IAgentEvents>
{
public:
AgentEvents();
~AgentEvents();
HRESULT STDMETHODCALLTYPE InitOnAdvise(
BSTR status,
IQueueCollection* queues,
IAgentPhoneSession* session)
{
QString s = QString::fromWCharArray(status, ::SysStringLen(status));
emit notifier()->initOnAdvise(s,
new QAxObject(queues), new QAxObject(session));
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnPhoneSessionOpened(
IAgentPhoneSession* session)
{
emit notifier()->openedPhoneSession(new QAxObject(session));
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnStatusChanged(
BSTR status)
{
QString s = QString::fromWCharArray(status, ::SysStringLen(status));
emit notifier()->changedStatus(s);
return S_OK;
}
inline AgentEventsNotifier* notifier() { return ¬ifier; }
private:
AgentEventsNotifier notifier;
};
Вспомогательный класс который будет испускать сигналы (все в одном реализовать не получилось):
C++ (Qt)
class QAxObject;
class AgentEventsNotifier : public QObject
{
Q_OBJECT
public:
AgentEventsNotifier() : QObject() { }
~AgentEventsNotifier() { }
signals:
void initOnAdvise(const QString& status,
QAxObject* queues, QAxObject* session);
void openedPhoneSession(QAxObject* session);
void changedStatus(const QString& status);
};
ну и класс "подключатель", который связывает сам COM объект и event'ы:
C++ (Qt)
#ifndef AXEVENTCONNECTOR_H
#define AXEVENTCONNECTOR_H
#include <QUuid>
#include <QVector>
#include "eventsink.h"
class QObject;
class QAxObject;
class AxEventConnector
{
public:
AxEventConnector(IUnknown* iface);
AxEventConnector(QAxObject* container);
~AxEventConnector();
bool isValid() const;
bool connectEvent(
EventSinkBase* eventSink, bool deleteEventSinkAtError = true);
private:
IUnknown* _iface;
IConnectionPointContainer* _cpoints;
QVector<EventSinkBase*> _eventSinks;
};
#endif // AXEVENTCONNECTOR_H
его код:
C++ (Qt)
#include "axeventconnector.h"
#include <QAxObject>
#include <QUuid>
AxEventConnector::AxEventConnector(IUnknown* iface)
: _iface(NULL)
, _cpoints(NULL)
{
if (iface) {
iface->QueryInterface(IID_IUnknown, (void**)&_iface);
}
}
AxEventConnector::AxEventConnector(QAxObject* container)
: _iface(NULL)
, _cpoints(NULL)
{
if (container) {
container->queryInterface(IID_IUnknown, (void**)&_iface);
}
}
AxEventConnector::~AxEventConnector()
{
// clear sinks table
QVector<EventSinkBase*>::iterator it = _eventSinks.begin();
while (it != _eventSinks.end()) {
EventSinkBase* eventSink = *it;
eventSink->unadvise();
eventSink->release();
++it;
}
if (_cpoints) {
_cpoints->Release();
}
if (_iface) {
_iface->Release();
}
}
bool AxEventConnector::isValid() const
{
return !_iface;
}
bool AxEventConnector::connectEvent(
EventSinkBase* eventSink, bool deleteEventSinkAtError /* = true*/)
{
bool result = false;
if (!_iface) { return result; }
if (!_cpoints) {
_iface->QueryInterface(IID_IConnectionPointContainer, (void**)&_cpoints);
}
if (_cpoints) {
IConnectionPoint* cpoint = 0;
_cpoints->FindConnectionPoint(*(eventSink->iidEvent()), &cpoint);
if (cpoint) {
if (result = eventSink->advise(cpoint)) {
_eventSinks.append(eventSink);
} else
if (deleteEventSinkAtError){
delete eventSink;
}
cpoint->Release();
}
}
return result;
}
И наконец использование:
C++ (Qt)
QAxObject agent = ...;
AgentEvents* events = new AgentEvents();
AgentEventsNotifier* obj = events->notifier();
connect(obj, SIGNAL(initOnAdvise(QString,QAxObject*,QAxObject*)),
SLOT(agent_agentInited(QString,QAxObject*,QAxObject*)));
connect(obj, SIGNAL(changedStatus(QString)),
SLOT(agent_statusChanged(QString)));
connect(obj, SIGNAL(openedPhoneSession(QAxObject*)),
SLOT(agent_sessionOpened(QAxObject*)));
AxEventConnector connector = new AxEventConnector(agent);
connector->connectEvent(events);