Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  1. 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.

  2. 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?

  1. 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, 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" );
}
like image 354
rimono Avatar asked Oct 19 '10 13:10

rimono


1 Answers

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.

like image 139
sharptooth Avatar answered Nov 14 '22 23:11

sharptooth