Im working on a Browser Helper Object, and I am trying to access the IWebBrowser2 that fires an event. With NavigateComplete2 and other events I can easly do it because I get the pointer on the parameters of Invoke.
But I was reading this on msdn and it says the only parameter for TitleChange event is the title, so how do I get the pointer to the webbrowser interface from the event TitleChange?
Here's how I am getting it with other events:
HRESULT STDMETHODCALLTYPE CSiteEvents::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS __RPC_FAR *Params, VARIANT __RPC_FAR *pVarResult,
EXCEPINFO __RPC_FAR *pExcepInfo, UINT __RPC_FAR *puArgErr )
{
switch ( dispIdMember )
{
case DISPID_DOCUMENTCOMPLETE:
{
IWebBrowser2 *pBrowser = GetBrowser(Params->rgvarg[1]);
// stuff
pBrowser->Release();
}
break;
}
}
IWebBrowser2* GetBrowser(const VARIANT &_Argument)
{
IWebBrowser2 *pBrowser = NULL;
if (_Argument.vt == VT_DISPATCH)
{
HRESULT hr;
IDispatch *pDisp = _Argument.pdispVal;
if (pDisp)
{
hr = pDisp->QueryInterface( IID_IWebBrowser2, reinterpret_cast<void **>(&pBrowser) );
if ( FAILED(hr) )
pBrowser = NULL;
}
}
return pBrowser;
}
I am using Visual Studio 2010.
Isn't the IDispatch context here implicit? With the other events, you have to distinguish whereabouts in the control the event happened, while for TitleChange it's at the top level - this means this is an IDispatch* that can be queried to get the interface you need.
DWebBrowserEvents2 inherits from IDispatch but also encapsulate another IDispatch for each component of the window.
Title can be changed only in main window, so you can use IWebBrowser2, retrieved from IUnknown passed to your SetSite implementation.
STDMETHODIMP CMyBHO::SetSite(IUnknown *punkSite)
{
if(punkSite != NULL)
{
// CComPtr<IWebBrowser2> m_pWebBrowser is member of CMyBHO class
CComQIPtr<IServiceProvider> pServiceProvider = punkSite;
if(pServiceProvider != NULL)
pServiceProvider->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (void**)&m_pWebBrowser);
}
else
{
if(m_pWebBrowser != NULL)
{
m_pWebBrowser = NULL;
}
}
return IObjectWithSiteImpl<CMyBHO>::SetSite(punkSite);
}
Related
I'm currently making a program to add tabs to the Windows file explorer using the win32 API since I'm not satisfied by any of the programs that currently do that (Clover, Groupy to name a few).
To do that I obviously need to get all explorer windows that are currently opened and to make the program watch for new windows being created.
The way I currently do it is by calling EnumWindows in my messages loop, and attaching to my program's main window any explorer window that isn't attached yet.
while(GetMessage(&uMsg, NULL, 0, 0) > 0)
{
TranslateMessage(&uMsg);
DispatchMessage(&uMsg);
EnumWindows((WNDENUMPROC)findExplorerWindows, (LPARAM)mainWindow);
}
This is obviously not optimal (new windows only get attached to my program when a message is sent to my program, and overall it slows everything down quite a bit especially when there's already a lot of opened windows) and I'd like to know if there is a way to watch the opened windows list to fire an event whenever a new window is created or anything of that sort.
Here is some sample Console code (uses COM) that uses the IShellWindows interface.
First it dumps the current explorer windows ("views"), then it hooks events raised by the companion interface DShellWindowsEvents
#include <atlbase.h>
#include <atlcom.h>
#include <shobjidl_core.h>
#include <shlobj_core.h>
#include <exdispid.h>
// a COM class that handles DShellWindowsEvents
class WindowsEvents : public IDispatch
{
// poor man's COM object... we don't care, we're basically a static thing here
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
{
if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
if (IsEqualIID(riid, IID_IDispatch))
{
*ppvObject = static_cast<IDispatch*>(this);
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() { return 1; }
STDMETHODIMP_(ULONG) Release() { return 1; }
// this is what's called by the Shell (BTW, on the same UI thread)
// there are only two events "WindowRegistered" (opened) and "WindowRevoked" (closed)
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
// first parameter is window's registration cookie
int cookie = V_I4(&pDispParams->rgvarg[0]);
if (dispIdMember == DISPID_WINDOWREGISTERED) // needs exdispid.h
{
wprintf(L"Window registered, cookie:%u\n", cookie);
}
else if (dispIdMember == DISPID_WINDOWREVOKED)
{
wprintf(L"Window revoked, cookie:%u\n", cookie);
}
// currently the cookie is not super useful, it's supposed to be usable by FindWindowSW
CComVariant empty;
long hwnd;
CComPtr<IDispatch> window;
HRESULT hr = Windows->FindWindowSW(&pDispParams->rgvarg[0], &empty, 0, &hwnd, SWFO_COOKIEPASSED, &window);
// always returns S_FALSE... it does not seem to work
// so, you'll have to ask for all windows again...
return S_OK;
}
// the rest is left not implemented
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; }
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { return E_NOTIMPL; }
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }
public:
CComPtr<IShellWindows> Windows;
};
int main()
{
CoInitialize(NULL);
{
CComPtr<IShellWindows> windows;
if (SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)))
{
// dump current windows
long count = 0;
windows->get_Count(&count);
for (long i = 0; i < count; i++)
{
CComPtr<IDispatch> window;
if (SUCCEEDED(windows->Item(CComVariant(i), &window)))
{
// get the window handle
CComPtr<IWebBrowserApp> app;
if (SUCCEEDED(window->QueryInterface(&app)))
{
HWND hwnd = NULL;
app->get_HWND((SHANDLE_PTR*)&hwnd);
wprintf(L"HWND[%i]:%p\n", i, hwnd);
}
}
}
// now wait for windows to open
// get the DShellWindowsEvents dispinterface for events
CComPtr<IConnectionPointContainer> cpc;
if (SUCCEEDED(windows.QueryInterface(&cpc)))
{
// https://learn.microsoft.com/en-us/windows/win32/shell/dshellwindowsevents
CComPtr<IConnectionPoint> cp;
if (SUCCEEDED(cpc->FindConnectionPoint(DIID_DShellWindowsEvents, &cp)))
{
WindowsEvents events;
events.Windows = windows;
DWORD cookie = 0;
// hook events
if (SUCCEEDED(cp->Advise(&events, &cookie)))
{
// pump COM messages to make sure events arrive
do
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} while (TRUE);
// normally we should get here if someone sends a PostQuitMessage(0) to the current thread
// but this is a console sample...
// unhook events
cp->Unadvise(cookie);
}
}
}
}
}
CoUninitialize();
return 0;
}
In advance sorry for the TOO Long post, but given that there are many parts involved, and I am not sure where I can be making a mistake, hence, I need to post all the parts involved. Please do not assume anything, I can be making any type of error here.
Problem:
1) Real problem: Just learning COM.
2) I need to pass a IDispatch pointer from my console client (ConsoleClient) in C++ as parameter to a COM function in a Windows Service (WinService). This IDispatch pointer acts as callback. The WinService knows the name of the function to be called. All my attempts have been unsuccessulf. I am getting different errors like: No implemented, RPC is unavailable, etc..
NOTE: The following error is correct! I edited after #Igor pointed out my error: to have a do-while (which I removed from the main()) in a STA thread. I added a message pump (GetMessage, TranslateMessage and DispatchMessage) in the main() thread (instead of the do-while) and my problem was fixed!
I have a WinService exposing the following COM interface (WinService's idl file):
[
object,
uuid(DBE8BC31-9D2B-4F4B-903A-B40473408DE9),
dual,
nonextensible,
pointer_default(unique)
]
interface IWinService : IDispatch
{
///Here there are more functions..
[id(4), helpstring("Interface HELP")]
HRESULT WinServiceCOMfunction( [in] VARIANT vCallback, [out, retval] LONG* pReturn );
//Here there even more functions...
};
[
uuid(DEF3BFAE-ADF4-493B-8D01-E47A279225C5),
version(1.0),
helpstring("LIB HELP")
]
library WinServiceLib
{
importlib("stdole2.tlb");
[
uuid(AC290DC9-8CB4-4502-A73C-2BDAEC4B215A)
]
coclass CoWinService
{
[default] interface IWinService;
};
}
The WinService internally expects that vCallBack.vt = VT_DISPATCH, i.e., that is the callback pointer I need to pass in when I invoke the WinServiceCOMfunction from my ConsoleClient.EXE app. WinService knows the name of the function that acts as callback. I.e., WinService calls from the IDispatch-pointer-callback GetIDsOfName with "FunctionCallback" as parameter. However, here I am getting different errors in the WinService like: No implemented, RPC is unavailable, etc.. Then no callback is executed (the ConsoleClient does not receive anything back).
What I have done so far (all the following source code is found in the ConsoleClient.EXE)
ConsoleClient.EXE main.cpp:
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
//To be brief: Let's think that I already have an IDispatch pointer to the WinService interface IWinService:
CComPtr<IDispatch> pIWinService; //pIWinService
//Here I queried the IWinService interface...It was successful!!!
OLECHAR * winServiceNameFunction = L"WinServiceCOMfunction";
DISPID dispid;
hr = pIWinService->GetIDsOfNames( IID_NULL, &winServiceNameFunction, 1, LOCALE_USER_DEFAULT, &dispid );
if ( FAILED( hr ) )
{
wprintf( L"GetIDsOfNames failed" );
return 1;
}
else
{
wprintf( L"GetIDsOfNames succeeded!" );
}
CComPtr<IDispatch> consoleClientCallback( new CConsoleClientInterface() );
CConsoleClientInterface *sanity = dynamic_cast<CConsoleClientInterface *>( consoleClientCallback.p );
if ( nullptr == pAutoCallback )
{
wprintf( L"CConsoleClientInterface pointer failed\n" );
return 1;
}
else
{
wprintf( L"CConsoleClientInterface pointer succeeded\n" );
}
DWORD dwRegister;
hr = CoRegisterClassObject(CLSID_CoConsoleClient, consoleClientCallback.p,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister);
if(FAILED(hr))
{
wprintf(L"CoRegisterClassObject Failed\n");
return 1;
}
else
{
wprintf(L"CoRegisterClassObject Succeeded\n");
}
CComVariant paramCallback( consoleClientCallback.Detach() );
VARIANTARG varParams[] = { paramCallback };
DISPPARAMS dispparams = { vArgs, NULL, 1, 0 };
hr = pPtr->Invoke(
dispid,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&dispparams,
NULL,
NULL,
NULL);
if ( FAILED( hr ) )
{
wprintf( L"Invoke failed" );
return 1;
}
else
{
wprintf( L"Invoke Succeeded" );
}
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoRevokeClassObject(dwRegister);
CoUninitialize();
return 0;
}
All the main is succesfully executed. However, I never get anything from the callback. When the WinService tries to QueryInterface from teh IDispatch pointer callback, it throws erros like: No implemented, RPC is unavailable, among others...
Template class to create the ConsoleClient IDispatch interface
From: http://blogs.msdn.com/b/oldnewthing/archive/2013/06/12/10425215.aspx
disInterfaceBase.h:
template<typename DispInterface>
class CDispInterfaceBase : public DispInterface
{
public:
CDispInterfaceBase() : m_cRef(1), m_dwCookie(0) { }
/* IUnknown */
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
*ppv = nullptr;
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IUnknown || riid == IID_IDispatch ||
riid == __uuidof(DispInterface))
{
*ppv = static_cast<DispInterface *>
(static_cast<IDispatch*>(this));
AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0) delete this;
return cRef;
}
// *** IDispatch ***
IFACEMETHODIMP GetTypeInfoCount(UINT *pctinfo)
{
*pctinfo = 0;
return E_NOTIMPL;
}
IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid,
ITypeInfo **ppTInfo)
{
*ppTInfo = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
return E_NOTIMPL;
}
IFACEMETHODIMP Invoke(
DISPID dispid, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
if (pvarResult) VariantInit(pvarResult);
return SimpleInvoke(dispid, pdispparams, pvarResult);
}
// Derived class must implement SimpleInvoke
virtual HRESULT SimpleInvoke(DISPID dispid,
DISPPARAMS *pdispparams, VARIANT *pvarResult) = 0;
public:
HRESULT Connect(IUnknown *punk)
{
HRESULT hr = S_OK;
CComPtr<IConnectionPointContainer> spcpc;
if (SUCCEEDED(hr)) {
hr = punk->QueryInterface(IID_PPV_ARGS(&spcpc));
}
if (SUCCEEDED(hr)) {
hr = spcpc->FindConnectionPoint(__uuidof(DispInterface), &m_spcp);
}
if (SUCCEEDED(hr)) {
hr = m_spcp->Advise(this, &m_dwCookie);
}
return hr;
}
void Disconnect()
{
if (m_dwCookie) {
m_spcp->Unadvise(m_dwCookie);
m_spcp.Release();
m_dwCookie = 0;
}
}
private:
LONG m_cRef;
CComPtr<IConnectionPoint> m_spcp;
DWORD m_dwCookie;
};
The actual IDispatch implementation in the ConsoleClient
ConsoleClient.h
class CConsoleClientInterface : public CDispInterfaceBase<ICONSOLEINTERFACE>
{
public:
CConsoleClientInterface() { }
~CConsoleClientInterface() { }
STDMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
HRESULT hr = E_FAIL;
if(_wcsicmp(*rgszNames, L"FunctionCallback") == 0)
{
*rgDispId = 1;
hr= S_OK;
}
else
{
hr= DISP_E_UNKNOWNINTERFACE;
}
if(FAILED(hr))
{
std::cout << L"FAILED\n";
}
return hr;
}
HRESULT SimpleInvoke(
DISPID dispid, DISPPARAMS *pdispparams, VARIANT *pvarResult)
{
// switch (dispid)
// {
// case 4:
std::cout << L"SimpleInvoke" << std::endl; //This is never printed (for the error that I am trying to figure out)
HRESULT hr = FunctionCallback( pdispparams->rgvarg[1].intVal, pdispparams->rgvarg[0].parray );
// break;
// }
return hr;
}
HRESULT FunctionCallback( LONG longValue, LPSAFEARRAY safearray )
{
//So simple, I just want that this value is printed in the ConsoleClient's console! Unfortunately this is not happening!
std::cout << longValue << std::endl;
return S_OK;
}
};
And the ConsoleClient.idl
[
object,
uuid(CD08B160-558A-4251-885C-173A08A461F1),
dual,
nonextensible,
helpstring("ICONSOLEINTERFACE Interface"),
pointer_default(unique)
]
interface ICONSOLEINTERFACE : IDispatch{
[id(1), helpstring("method FunctionCallback")]
HRESULT FunctionCallback([in] LONG longValue, [in] LPSAFEARRAY safeArray );
};
[
uuid(F3445A9E-555B-4729-952B-8B72B8DB2E37),
version(1.0),
helpstring("ConsoleClientLib Help Lib")
]
library ConsoleClientLib
{
importlib("stdole2.tlb");
[
uuid(EFF9EC78-3031-4558-9BA3-5B2641CCB304),
helpstring("CoConsoleClient Class")
]
coclass ConsoleClientInterface
{
[default] interface ICONSOLEINTERFACE;
};
};
I have created:
HKEY_CLASSES_ROOT\CLSID\{EFF9EC78-3031-4558-9BA3-5B2641CCB304} //CLSID
(Default) REG_SZ = C:\Program Files\Common Files\ConsoleClient.EXE
HKEY_CLASSES_ROOT\CLSID\{EFF9EC78-3031-4558-9BA3-5B2641CCB304}\LocalServer32
ThreadingModel REG_SZ = Both
Also I have registered the tlb using regtlibv12.exe
Sorry for the TOO LONG post, but COM forces me to do this, in particular that I am new with COM and I do not know where I can be making a mistake.
NOTE: I am sure that the Invoke (from the ConsoleClient.EXE) to the WinService COM function (WinServiceCOMfunction) is successful (I debugged it and hits inside this function after the Invoke).
NOTE2: I am sure that the WinService using the callback works. There are other implementations (in different programming languages) making use of this mechanism via the same Function (WinServiceCOMfunction) in WinService.
Any help? Thanks in advance!
I am having trouble implementing an event sink for DShellWindowsEvents from SHDocVw.dll in Microsoft Visual C++.
The problem arises inside my implementation of IDispatch::Invoke. I implemented it by delegating its call to ITypeInfo::Invoke (as suggested by Microsoft on remarks section), but it always fails with error code 0x80020003 (Member not found).
It is interesting though that DShellWindowsEvents has only two methods (WindowRegistered and WindowRevoked) and ITypeInfo::GetIDsOfNames works successfully for both, returning the expected DISPID. But then how does it give "Member not found" error for Invoke?
The relevant part of the type library:
[
uuid(FE4106E0-399A-11D0-A48C-00A0C90A8F39),
helpstring("Event interface for IShellWindows")
]
dispinterface DShellWindowsEvents {
properties:
methods:
[id(0x000000c8), helpstring("A new window was registered.")]
void WindowRegistered([in] long lCookie);
[id(0x000000c9), helpstring("A new window was revoked.")]
void WindowRevoked([in] long lCookie);
};
[
uuid(9BA05972-F6A8-11CF-A442-00A0C90A8F39),
helpstring("ShellDispatch Load in Shell Context")
]
coclass ShellWindows {
[default] interface IShellWindows;
[default, source] dispinterface DShellWindowsEvents;
};
and my actual code where it all happens:
class CDShellWindowsEvents : public DShellWindowsEvents {
public:
// IUnknown implementation
virtual HRESULT __stdcall QueryInterface( REFIID riid, LPVOID *ppvObj )
{
if( ppvObj == NULL ) return E_INVALIDARG;
*ppvObj = NULL;
if( riid == IID_IUnknown || riid == IID_IDispatch || riid == DIID_DShellWindowsEvents )
{
*ppvObj = this;
AddRef( );
return NOERROR;
}
return E_NOINTERFACE;
}
virtual ULONG __stdcall AddRef( )
{
InterlockedIncrement( &m_cRef );
return m_cRef;
}
virtual ULONG __stdcall Release( )
{
ULONG ulRefCount = InterlockedDecrement( &m_cRef );
if( 0 == m_cRef )
delete this;
return ulRefCount;
}
// IDispatch implementation
virtual HRESULT __stdcall GetTypeInfoCount( unsigned int * pctinfo )
{
if( pctinfo == NULL ) return E_INVALIDARG;
*pctinfo = 1;
return NOERROR;
}
virtual HRESULT __stdcall GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo ** ppTInfo )
{
if( ppTInfo == NULL ) return E_INVALIDARG;
*ppTInfo = NULL;
if( iTInfo != 0 ) return DISP_E_BADINDEX;
*ppTInfo = m_pTypeInfo;
m_pTypeInfo->AddRef();
return NOERROR;
}
virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, OLECHAR ** rgszNames, unsigned int cNames, LCID lcid, DISPID * rgDispId )
{
return m_pTypeInfo->GetIDsOfNames( rgszNames, cNames, rgDispId );
}
virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, unsigned int * puArgErr )
{
// We could switch on dispIdMember here but we want to do this the flexible way, so we can easily implement it later for dispinterfaces with dozens of methods.
return m_pTypeInfo->Invoke( this, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr );
/*HRESULT ret;
switch( dispIdMember )
{
case 0x000000c8: ret = WindowRegistered( pDispParams->rgvarg[0].intVal ); break;
case 0x000000c9: ret = WindowRevoked( pDispParams->rgvarg[0].intVal ); break;
default: ret = DISP_E_MEMBERNOTFOUND;
}
if( pVarResult ) pVarResult->lVal = ret;
return ret;*/
}
// DShellWindowsEvents implementation
virtual HRESULT __stdcall WindowRegistered( int nCookie )
{
LOG( "CDShellWindowsEvents::WindowRegistered( nCookie=0x%X ) ." , nCookie );
return NOERROR;
}
virtual HRESULT __stdcall WindowRevoked( int nCookie )
{
LOG( "CDShellWindowsEvents::WindowRevoked( nCookie=0x%X ) ." , nCookie );
return NOERROR;
}
// Constructor
CDShellWindowsEvents( )
{
m_cRef = 1;
LoadRegTypeLib( LIBID_SHDocVw, 1, 0, LANG_NEUTRAL, &m_pTypeLib );
m_pTypeLib->GetTypeInfoOfGuid( DIID_DShellWindowsEvents, &m_pTypeInfo );
}
// Destructor
~CDShellWindowsEvents( )
{
m_pTypeInfo->Release( );
m_pTypeLib->Release( );
}
private:
ULONG m_cRef;
ITypeLib *m_pTypeLib;
ITypeInfo *m_pTypeInfo;
};
Is this a problem with the type library not specifying dual for the dispinterface? If yes, is there a way to force ITypeInfo::Invoke to threat it as dual since I know its vtable is in order.
Thanks in advance.
A dispinterface is an automation-only interface, not a dual interface. So, DShellWindowsEvents only natively has the 7 methods of IDispatch, not the extra 2 it declares. It's like declaring an IDispatch-only interface with a contract, such as predefined DISPIDs.
To use ITypeLib::Invoke in that fashion, you need to declare your own interface as [dual] with the extra methods.
[ object, dual, oleautomation, uuid(...) ]
interface IMyDualShellWindowsEvents : IDispatch
{
[id(0x000000c8), helpstring("A new window was registered.")]
HRESULT WindowRegistered([in] long lCookie);
[id(0x000000c9), helpstring("A new window was revoked.")]
HRESULT WindowRevoked([in] long lCookie);
}
You'll need provide or embed your own typelib and load it instead.
DShellWindowsEvents is a dispinterface, so your events object won't be QueryInterfaced for your internal interface, although you may handle that case anyway. As such, you don't need to register your typelib, just load it with LoadLibraryEx.
I think you may add hidden, restricted to the interface attributes, so it doesn't show up and can't be used in e.g. VBA, even by manually adding a reference to such a private typelib.
I am tying to implement Microsoft's COM objects DDiscMaster2Events on mingw to get disk drive change events. I have not been able to find any examples of this. I already got DDiskFormat2DataEvents to work so I expect it to be similar to that. In DDiskFormat2DataEvents I had to connect my DDiskFormat2DataEvents with a IDiskFormat2Data to get the events. This is normally done with the AfxConnectionAdvise method. What com object do I need to connect my DDiscMaster2Events events sink to, to receive disk change events? A Visual Studio c++ example should answer my question. Thanks
There is some information about how to receive COM events in this article COM Connection Points by Thottam R. Sriram.
Based on its contents, somethink like this might work for you:
#include <imapi2.h>
#include <iostream>
#include <cassert>
class DiscMaster2EventsSink : public DDiscMaster2Events {
public:
STDMETHOD(NotifyDeviceAdded)(IDispatch *object, BSTR uniqueId)
{
std::cout << "NotifyDeviceAdded(...)\n";
return S_OK;
}
STDMETHOD(NotifyDeviceRemoved)(IDispatch *object, BSTR uniqueId)
{
std::cout << "NotifyDeviceRemoved(...)\n";
return S_OK;
}
public:
// The uninteresting stuff follows...
DiscMaster2EventsSink()
: m_refCount(1)
{
}
STDMETHOD(QueryInterface)(REFIID riid, void **ppv)
{
*ppv = NULL;
if (riid == IID_IUnknown)
*ppv = static_cast<void*>(static_cast<IUnknown*>(this));
else if (riid == IID_IDispatch)
*ppv = static_cast<void*>(static_cast<IDispatch*>(this));
else if (riid == __uuidof(DDiscMaster2Events))
*ppv = static_cast<void*>(static_cast<DDiscMaster2Events*>(this));
if (*ppv) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHOD_(ULONG, AddRef)()
{
return InterlockedIncrement(&m_refCount);
}
STDMETHOD_(ULONG, Release)()
{
ULONG result = InterlockedDecrement(&m_refCount);
if (result == 0)
delete this;
return result;
}
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
return E_NOTIMPL;
}
STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
return E_NOTIMPL;
}
STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)
{
*pctinfo = 0;
return S_OK;
}
STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
return E_NOTIMPL;
}
private:
ULONG m_refCount;
};
int main()
{
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
assert(SUCCEEDED(hr));
IDiscMaster2* pDiscMaster2;
hr = CoCreateInstance(__uuidof(MsftDiscMaster2), NULL, CLSCTX_ALL, __uuidof(IDiscMaster2), (void**)&pDiscMaster2);
assert(SUCCEEDED(hr));
IConnectionPointContainer* pCPC;
hr = pDiscMaster2->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);
assert(SUCCEEDED(hr));
IConnectionPoint* pCP;
hr = pCPC->FindConnectionPoint(IID_DDiscMaster2Events, &pCP);
assert(SUCCEEDED(hr));
DiscMaster2EventsSink* pSink = new DiscMaster2EventsSink();
IUnknown* pSinkUnk;
hr = pSink->QueryInterface(IID_IUnknown, (void**)&pSinkUnk);
assert(SUCCEEDED(hr));
DWORD dwAdvise;
hr = pCP->Advise(pSinkUnk, &dwAdvise);
assert(SUCCEEDED(hr));
std::cout << "OK...\n";
std::cin.get();
pSinkUnk->Release();
pSink->Release();
pCP->Release();
pCPC->Release();
pDiscMaster2->Release();
CoUninitialize();
return 0;
}
It compiles and runs fine for me as far as I can see (S_OK all the way), but I cannot see any events - possibly because I doesn't have an external optical drive to mess with to create any device add/remove events.
(Also obviously with some C++ COM helper class it would be much nicer.)
Hopefully it might still help you, perhaps with a few changes even under MinGW.
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, 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 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;
}