如何监听独立启动的Office应用程序的COM事件?

发布于 2024-09-27 23:45:29 字数 5530 浏览 5 评论 0原文

我想要做什么:

编写一个侦听 Office 事件的应用程序。我想监听机器上打开的任何实例的事件。例如,如果我正在 Word 中监听 BeforeDocumentSave,那么我希望每当主机上的任何 Word 实例保存文档时都激活此方法的接收器。

另一个要求是我用 C++ 编写而不使用 MFC 或 ATL。

我所做的:

我编写了一个应该侦听 Word 事件的程序。请参阅下面的代码。

问题:

它不起作用 - 尽管我打开一个文字应用程序并执行应触发事件的操作,但从未输入事件处理程序。

我有一些具体问题,当然也非常欢迎任何其他意见!

问题:

  1. 是否可以监听非我启动的应用程序中的事件?在我找到的所有示例中,侦听应用程序都会启动它想要侦听的 Office 应用程序。

  2. 在 Microsoft howto (http://support.microsoft.com/kb/183599/EN-US/) 中,我发现了以下评论:

但是,大多数事件,例如 Microsoft Excel 的工作簿事件,执行 不从 DISPID 1 开始。在这种情况下 情况下,您必须显式修改 将 MyEventSink.cpp 中的映射调度到 将 DISPID 与正确的匹配 方法。

如何修改调度地图?

  1. 目前我只定义了 Startup、Quit 和 DocumentChange,它们不带任何参数。我真正需要的方法确实需要参数,特别是 Document 类型之一。如果我不使用 MFC,如何定义这种类型的参数?

代码:

这是我的项目的头文件,后面是 C 文件:

#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_

C 文件:

#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" );
}

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" );
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一张白纸 2024-10-04 23:45:29

您的第一个问题是您没有在主线程中运行消息循环,并且 这会导致事件永远不会到达你的接收器对象。 COM 服务器对接收器对象的调用是使用 Windows 消息调度的,因此您必须运行消息循环而不是简单地调用 Sleep() ,以便传入事件最终调度到接收器对象。

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.

你的他你的她 2024-10-04 23:45:29

我在此处给出的代码中发现了两个问题:

  1. 正如 Sharptooth 指出的那样,Sleep 在这里是错误的。它甚至会导致我收听的应用程序挂起或休眠。我按照 Sharptooth 的建议放入了一个消息循环。
  2. 我没有实施“调用”。我认为如果我自己实现事件方法,它们将由应用程序调用,但事实并非如此。我必须实现调用并使其打开 dispid 并调用不同的事件方法。我在 typelib 中找到了 dispid。查看 OLE 查看器中的方法,我使用了“id”,它显然是 dispid。

以下是更正后的 .cpp 文件。在 .h 文件中,唯一的更正是将 Invoke 更改为声明而不是实现。

关于代码的两个注释:

  1. 我根据发现的各种示例构建了它,因此您可能会发现看起来不相关的名称或注释,因为它们是从其他地方获取的。
  2. 为了获取 COM 对象,我使用了 GetActiveObject 或 CoCreateInstance。前者仅在事件触发应用程序已运行时才有效。后者创建了它的一个不可见的实例。对于 Word,这效果很好,可能是因为如果我打开另一个 Word 实例,它是同一过程的一部分。我还没有检查如果我打开一个新进程,事件是否仍然会被触发,会发生什么。

希望这有帮助!

#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;
 }

I found two problems in the code I gave here:

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

  1. 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.
  2. To get the COM object I used either GetActiveObject or CoCreateInstance. 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;
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文