How to listen to COM events from Office applications that were started independently?
What I want to do:
Write an application that listens to Office events. I want to listen to events from any instance opened on the machine. E.g. if I'm listening to BeforeDocumentSave in Word, then I want my sink for this method to be activated whenever any instance of Word on the host saves a document.
Another requirement is that I'm writing in C++ without MFC or ATL.
What I've done:
I've written a program that's supposed to listen to Word events. See the code below.
The problem:
It doesn't work - the event handlers are never entered, although I open a word application and do the actions that should trigger the events.
I have some specific questions, and of course any other input would be very welcome!
Questions:
Is it possible to listen to events from an application that wasn't started by me? In all the examples I found, the listening application starts the office application it wants to listen to.
In a microsoft howto (http://support.microsoft.com/kb/183599/EN-US/) I found the following comment:
However, most events, such as Microsoft Excel's Workbook events, do not start with DISPID 1. In such cases, you must explicitly modify the dispatch map in MyEventSink.cpp to match the DISPIDs with the correct methods.
How do I modify the dispatch map?
- For now I've defined only Startup, Quit and DocumentChange, which take no arguments. The methods I really need do take arguments, specifically one of type Document. How do I define parameters of this type if I'm not using MFC?
Code:
Here's the header file for my project, followed by the C file:
#ifndef _OFFICEEVENTHANDLER_H_
#define _OFFICEEVENTHANDLER_H_
// 000209FE-0000-0000-C000-000000000046
static const GUID IID_IApplicationEvents2 =
{0x000209FE,0x0000,0x0000, {0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct IApplicationEvents2 : public IDispatch // Pretty much copied from typelib
{
/*
* IDispatch methods
*/
STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj) = 0;
STDMETHODIMP_(ULONG) AddRef() = 0;
STDMETHODIMP_(ULONG) Release() = 0;
STDMETHODIMP GetTypeInfoCount(UINT *iTInfo) = 0;
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) = 0;
STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames,
UINT cNames, LCID lcid, DISPID *rgDispId) = 0;
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
UINT* puArgErr) = 0;
/*
* IApplicationEvents2 methods
*/
STDMETHODIMP Startup();
STDMETHODIMP Quit();
STDMETHODIMP DocumentChange();
};
class COfficeEventHandler : IApplicationEvents2
{
public:
DWORD m_dwEventCookie;
COfficeEventHandler
(
) :
m_cRef(1),
m_dwEventCookie(0)
{
}
STDMETHOD_(ULONG, AddRef)()
{
InterlockedIncrement(&m_cRef);
return m_cRef;
}
STDMETHOD_(ULONG, Release)()
{
InterlockedDecrement(&m_cRef);
if (m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
STDMETHOD(QueryInterface)(REFIID riid, void ** ppvObj)
{
if (riid == IID_IUnknown){
*ppvObj = static_cast<IApplicationEvents2*>(this);
}
else if (riid == IID_IApplicationEvents2){
*ppvObj = static_cast<IApplicationEvents2*>(this);
}
else if (riid == IID_IDispatch){
*ppvObj = static_cast<IApplicationEvents2*>(this);
}
else
{
char clsidStr[256];
WCHAR wClsidStr[256];
char txt[512];
StringFromGUID2(riid, (LPOLESTR)&wClsidStr, 256);
// Convert down to ANSI
WideCharToMultiByte(CP_ACP, 0, wClsidStr, -1, clsidStr, 开发者_JS百科256, NULL, NULL);
sprintf_s(txt, 512, "riid is : %s: Unsupported Interface", clsidStr);
*ppvObj = NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppvObj)->AddRef();
return S_OK;
}
STDMETHOD(GetTypeInfoCount)(UINT* pctinfo)
{
return E_NOTIMPL;
}
STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
{
return E_NOTIMPL;
}
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
LCID lcid, DISPID* rgdispid)
{
return E_NOTIMPL;
}
STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
return E_NOTIMPL;
}
// IApplicationEvents2 methods
void Startup();
void Quit();
void DocumentChange();
protected:
LONG m_cRef;
};
#endif // _OFFICEEVENTHANDLER_H_
The C file:
#include <windows.h>
#include <stdio.h>
#include "OfficeEventHandler.h"
#include "OCIdl.h"
int main()
{
CLSID clsid; // CLSID of automation object
HRESULT hr;
LPUNKNOWN punk = NULL; // IUnknown of automation object
LPDISPATCH pdisp = NULL; // IDispatch of automation object
IConnectionPointContainer *pConnPntCont;
IConnectionPoint *pConnPoint;
IUnknown *iu;
IID id;
COfficeEventHandler *officeEventHandler = new COfficeEventHandler;
CoInitialize(NULL);
hr = CLSIDFromProgID(OLESTR("Word.Application"), &clsid);
hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER,
IID_IUnknown, (void FAR* FAR*)&punk);
hr = punk->QueryInterface(IID_IConnectionPointContainer, (void FAR* FAR*)&pConnPntCont);
// IID for ApplicationEvents2
hr = IIDFromString(L"{000209FE-0000-0000-C000-000000000046}",&id);
hr = pConnPntCont->FindConnectionPoint( id, &pConnPoint );
hr = officeEventHandler->QueryInterface( IID_IUnknown, (void FAR* FAR*)&iu);
hr = pConnPoint->Advise( iu, &officeEventHandler->m_dwEventCookie );
Sleep( 360000 );
hr = pConnPoint->Unadvise( officeEventHandler->m_dwEventCookie );
if (punk) punk->Release();
if (pdisp) pdisp->Release();
CoUninitialize();
return hr;
}
// IApplicationEvents2 methods
void COfficeEventHandler::Startup()
{
printf( "In Startup\n" );
}
void COfficeEventHandler::Quit()
{
printf( "In Quit\n" );
}
void COfficeEventHandler::DocumentChange()
{
printf( "In DocumentChnage\n" );
}
Your number one problem is you don't run the message loop in the main thread and that causes the events to never reach your sink object. Calls form the COM server to your sink object are dispatched using Windows messages, so you have to run the message loop instead of simply calling Sleep()
so that the incoming events are eventually dispatched to the sink object.
I found two problems in the code I gave here:
- As sharptooth pointed out, Sleep is wrong here. It even causes the application I listen to to hang, or sleep. I put in a message loop as sharptooth suggested.
- I hadn't implemented 'Invoke'. I thought that if I implemented the event methods themselves, they would be called by the application, but this is not the case. I had to implement invoke and make it switch on the dispid and call the different event methods. I found the dispid in the typelib. Looking at a method in the OLE viewer, I used the 'id' which apparently is the dispid.
Following is a corrected .cpp file. In the .h file the only correction is to change Invoke into a declaration instead of an implementation.
Two notes on the code:
- I constructed it out of various examples I found, so you may find names or comments that seem irrelevant because they were taken from elsewhere.
- To get the COM object I used either
GetActiveObject
orCoCreateInstance
. The former works only if the event firing application is already running. The latter creates an invisible instance of it. For Word this worked well, possibly because if I open another Word instance its part of the same process. I haven't checked yet what happens if I open a new process, if the events would still be triggered.
Hope this helps!
#include <windows.h>
#include <stdio.h>
#include "OfficeEventHandler.h"
#include "OCIdl.h"
int main()
{
CLSID clsid; // CLSID of automation object
HRESULT hr;
LPUNKNOWN punk = NULL; // IUnknown of automation object
LPDISPATCH pdisp = NULL; // IDispatch of automation object
IConnectionPointContainer *pConnPntCont;
IConnectionPoint *pConnPoint;
IUnknown *iu;
IID id;
COfficeEventHandler *officeEventHandler = new COfficeEventHandler;
CoInitialize(NULL);
hr = CLSIDFromProgID(OLESTR("Word.Application"), &clsid);
/* hr = GetActiveObject( clsid, NULL, &punk );
*/ hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER,
IID_IUnknown, (void FAR* FAR*)&punk);
hr = punk->QueryInterface(IID_IConnectionPointContainer, (void FAR* FAR*)&pConnPntCont);
// IID for ApplicationEvents2
hr = IIDFromString(L"{000209FE-0000-0000-C000-000000000046}",&id);
hr = pConnPntCont->FindConnectionPoint( id, &pConnPoint );
hr = officeEventHandler->QueryInterface( IID_IUnknown, (void FAR* FAR*)&iu);
hr = pConnPoint->Advise( iu, &officeEventHandler->m_dwEventCookie );
MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0 )
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if( msg.message == WM_QUERYENDSESSION || msg.message == WM_QUIT || msg.message == WM_DESTROY )
{
break;
}
}
hr = pConnPoint->Unadvise( officeEventHandler->m_dwEventCookie );
if (punk) punk->Release();
if (pdisp) pdisp->Release();
CoUninitialize();
return hr;
}
// IApplicationEvents2 methods
void COfficeEventHandler::Startup()
{
printf( "In Startup\n" );
}
void COfficeEventHandler::Quit()
{
printf( "In Quit\n" );
}
void COfficeEventHandler::DocumentChange()
{
printf( "In DocumentChnage\n" );
}
STDMETHODIMP COfficeEventHandler::Invoke(DISPID dispIdMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
//Validate arguments
if ((riid != IID_NULL))
return E_INVALIDARG;
HRESULT hr = S_OK; // Initialize
/* To see what Word sends as dispid values */
static char myBuf[80];
memset( &myBuf, '\0', 80 );
sprintf_s( (char*)&myBuf, 80, " Dispid: %d :", dispIdMember );
switch(dispIdMember){
case 0x01: // Startup
Startup();
break;
case 0x02: // Quit
Quit();
break;
case 0x03: // DocumentChange
DocumentChange();
break;
}
return S_OK;
}
精彩评论