Event sent by Windows when connected to internet - c++

I need to know in my program when the connection to internet is back.
Actually I can check if it is connected with something like
CComPtr<INetworkListManager> pNLM;
HRESULT hr = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, __uuidof(INetworkListManager), (LPVOID*)&pNLM);
if (SUCCEEDED(hr))
{
VARIANT_BOOL isConnected;
pNLM->get_IsConnectedToInternet(&isConnected);
if (isConnected == VARIANT_TRUE)
But I cannot find a nice way to get informed that the connection is back.
Polling is not a nice way, to me.
I've found ::NotifyAddrChange() but this is triggered whenever change occurs in the table that maps IPv4 addresses to interfaces.
Is there a specific event that is sent when the connection is established?

Thanks to a colleague of mine, I have the answer.
This is the class that will call the callback
#include "Event.h"
Event::Event(const std::function<void()>& cb)
: methodTobeCalled(cb)
{
}
HRESULT Event::NetworkConnectionConnectivityChanged(GUID, NLM_CONNECTIVITY)
{
if (methodTobeCalled)
methodTobeCalled();
return S_OK;
}
HRESULT Event::NetworkConnectionPropertyChanged(GUID, NLM_CONNECTION_PROPERTY_CHANGE)
{
return S_OK;
}
STDMETHODIMP Event::QueryInterface(REFIID refIID, void** pIFace)
{
HRESULT hr = S_OK;
*pIFace = nullptr;
if (IsEqualIID(refIID, IID_IUnknown))
{
*pIFace = (IUnknown *)this;
((IUnknown *)*pIFace)->AddRef();
}
else if (IsEqualIID(refIID, IID_INetworkConnectionEvents))
{
*pIFace = (INetworkConnectionEvents *)this;
((IUnknown *)*pIFace)->AddRef();
}
else
{
hr = E_NOINTERFACE;
}
return hr;
}
ULONG Event::AddRef(void)
{
return static_cast<ULONG>(InterlockedIncrement(&m_ref));
}
ULONG Event::Release(void)
{
LONG Result = InterlockedDecrement(&m_ref);
return static_cast<ULONG>(Result);
}
In the ctor of the main class (no error handling here):
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
::CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_ALL, IID_INetworkListManager, (LPVOID *)&m_pNLM);
m_pNLM->QueryInterface(IID_IConnectionPointContainer, (void **)&m_pCpc);
m_pCpc->FindConnectionPoint(IID_INetworkConnectionEvents, &m_pConnectionPoint);
m_event = std::make_unique<NetworkEvent>(std::bind(&MainClass::methodTobeCalled, this));
hr = m_pConnectionPoint->Advise((IUnknown *)m_event.get(), &m_cookie);
The variables needed:
std::unique_ptr<NetworkEvent> m_event;
DWORD m_cookie;
CComPtr<IConnectionPoint> m_pConnectionPoint;
CComPtr<INetworkListManager> m_pNLM;
CComPtr<IConnectionPointContainer> m_pCpc;
The method that will be called at each network event, will be
MainClass::methodTobeCalled()
In this method one can then check if the connection is available or not.
O can check the NLM_CONNECTIVITY flags in NetworkConnectionConnectivityChanged()

Related

Programmatically pre-select using IFileDialog in C++

I am perplexed on whether IFileDialog has the capability of programmatically selecting an item inside the dialog even with out the user selecting.
Ex.
I was hoping to achieve opening IFileDialog then selecting a default item/folder inside the dialog.
Ex.
By the way, in the picture above. I did manually click/select the folder.
But I was hoping to implement a defaultly selected item inside the IFileDialog.
The procedure suggested by zett42 works. You need to implement your own version of IFileDialogEvents. After hooking with IFileDialog::Advise you can query your way to IShellView and that lets you change the selection.
This example is a little silly because I'm forcing the directory as well to be sure I have a file I can select.
struct MyIFileDialogEvents : public IFileDialogEvents {
bool forcedDir, forcedSel;
MyIFileDialogEvents() : forcedDir(false), forcedSel(false) {}
...
};
STDMETHODIMP MyIFileDialogEvents::OnFolderChanging( IFileDialog *pfd, IShellItem*psiFolder)
{
if (forcedDir) return S_OK; else forcedDir = true;
IShellItem*psiwindir;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = pfd->SetFolder(psiwindir); // MSDN says it is OK to change the folder in OnFolderChanging with SetFolder
psiwindir->Release();
}
if (FAILED(hr)) forcedSel = true;
return S_OK;
}
STDMETHODIMP MyIFileDialogEvents::OnFolderChange(IFileDialog *pfd)
{
if (forcedSel || !forcedDir) return S_OK; else forcedSel = true;
IShellItem*psiwindir, *psiexp;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = SHCreateItemFromRelativeName(psiwindir, L"Explorer.exe", NULL, IID_IShellItem, (void**) &psiexp);
psiwindir->Release();
if (!hr)
{
IServiceProvider*pSP;
IShellBrowser*pSB;
IShellView*pSV;
if (!pfd->QueryInterface(IID_IServiceProvider, (void**) &pSP))
{
if (!pSP->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&pSB))
{
if (!pSB->QueryActiveShellView(&pSV))
{
PIDLIST_ABSOLUTE pidl;
if (!SHGetIDListFromObject(psiexp, &pidl))
{
pSV->SelectItem(ILFindLastID(pidl), SVSI_SELECT|SVSI_ENSUREVISIBLE|SVSI_FOCUSED|SVSI_DESELECTOTHERS);
CoTaskMemFree(pidl);
}
pSV->Release();
}
pSB->Release();
}
pSP->Release();
}
psiexp->Release();
}
}
return S_OK;
}

How to let Internet Explorer continue running while the thread waits [duplicate]

I have a problem just like JimEvans in Sinking DWebBrowserEvents2 events appears to hang programmatic navigation but I can't understand the anwser, can somebody tell me more about it??
I'm manipulating the IE Explorer by plaint C++. Some codes are copied from the Codeproject. But my IE got hung when I'm handling the Events, and I can't get the DISPID_NAVIGETCOMPLETE event before my main function finishes.
My code below:
#include <afxwin.h>
#include <afxdisp.h>
#include <iostream>
#include <MsHTML.h>
#include <Exdisp.h>
#include <ExDispid.h>
class IE_Events_Sinker : public DWebBrowserEvents2
{
public:
// No constructor or destructor is needed
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid,void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IDispatch methods
STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
STDMETHODIMP GetTypeInfo(UINT iTInfo,LCID lcid,ITypeInfo **ppTInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid,LPOLESTR *rgszNames,UINT cNames,LCID lcid,DISPID *rgDispId);
STDMETHODIMP Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,
DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr);
};
IE_Events_Sinker IESinker;
STDMETHODIMP IE_Events_Sinker::Invoke(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr )
{
switch(dispIdMember)
{
case DISPID_NAVIGATEERROR:
{
//Extract the status code from the DISPPARAMS structure
VARIANT * vt_statuscode = pDispParams->rgvarg[1].pvarVal;
DWORD dwStatusCode = vt_statuscode->lVal;
//Extract the event's IDispatch pointer
IDispatch *pdispFiredEvent = pDispParams->rgvarg[4].pdispVal;
printf("Status Code: %d\n", dwStatusCode);
break;
}
case DISPID_NAVIGATECOMPLETE2:
printf("Navigate Complete!\n");
break;
case DISPID_BEFORENAVIGATE2:
printf("Before Navigate is fired!\n");
break;
default:
//MessageBox(NULL, L"A Message", NULL, NULL);
printf("A Message !\n the dispIdMemberis %d\n", dispIdMember);
break;
}
return S_OK;
}
STDMETHODIMP IE_Events_Sinker::QueryInterface(REFIID riid,void **ppvObject)
{
// Check if ppvObject is a valid pointer
if(IsBadWritePtr(ppvObject,sizeof(void*))) return E_POINTER;
// Set *ppvObject to NULL
(*ppvObject)=NULL;
// See if the requested IID matches one that we support
// If it doesn't return E_NOINTERFACE
if(!IsEqualIID(riid,IID_IUnknown) && !IsEqualIID(riid,IID_IDispatch) && !IsEqualIID(riid,DIID_DWebBrowserEvents2)) return E_NOINTERFACE;
// If it's a matching IID, set *ppvObject to point to the global EventSink object
(*ppvObject)=(void*)&IESinker;
return S_OK;
}
STDMETHODIMP_(ULONG) IE_Events_Sinker::AddRef()
{
return 1; // We always have just one static object
}
STDMETHODIMP_(ULONG) IE_Events_Sinker::Release()
{
return 1; // Ditto
}
// We don't need to implement the next three methods because we are just a pure event sink
// We only care about Invoke() which is what IE calls to notify us of events
STDMETHODIMP IE_Events_Sinker::GetTypeInfoCount(UINT *pctinfo)
{
UNREFERENCED_PARAMETER(pctinfo);
return E_NOTIMPL;
}
STDMETHODIMP IE_Events_Sinker::GetTypeInfo(UINT iTInfo,LCID lcid,ITypeInfo **ppTInfo)
{
UNREFERENCED_PARAMETER(iTInfo);
UNREFERENCED_PARAMETER(lcid);
UNREFERENCED_PARAMETER(ppTInfo);
return E_NOTIMPL;
}
STDMETHODIMP IE_Events_Sinker::GetIDsOfNames(REFIID riid,LPOLESTR *rgszNames,UINT cNames,LCID lcid,DISPID *rgDispId)
{
UNREFERENCED_PARAMETER(riid);
UNREFERENCED_PARAMETER(rgszNames);
UNREFERENCED_PARAMETER(cNames);
UNREFERENCED_PARAMETER(lcid);
UNREFERENCED_PARAMETER(rgDispId);
return E_NOTIMPL;
}
int main()
{
HRESULT hr;
IWebBrowser2* IWbr;
IConnectionPointContainer* pCPContainer;
IConnectionPoint* m_pConnectionPoint;
CoInitialize(NULL);
//hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, (void**)&IWbr);
hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, (void**)&IWbr);
IWbr->put_Visible(TRUE);
hr = IWbr->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPContainer);
hr = pCPContainer->FindConnectionPoint(DIID_DWebBrowserEvents2, &m_pConnectionPoint);
//Important Point!
DWORD m_dwCookie;
m_pConnectionPoint->Advise(&IESinker, &m_dwCookie);
COleVariant vtEmpty;
BSTR url2 = SysAllocString(L"http://www.qq.com/");
IWbr->Navigate(url2, &vtEmpty, &vtEmpty, &vtEmpty, &vtEmpty);
Sleep(20000);
//hr = IWbr->Quit();
//while(FAILED(hr))
//{
//a++;
//hr = IWbr->Quit();
//}
//printf("shut down %d times\n", a);
m_pConnectionPoint->Unadvise(m_dwCookie);
m_pConnectionPoint->Release();
IWbr->Release();
pCPContainer->Release();
CoUninitialize();
}
The problem, and the answer over there in the question you are referring to, is about processing window messages on the thread with your activity, so called "message pump". You do:
Sleep(20000);
And this is the cause of the problem. Instead you should be waiting AND you should be processing window messages while waiting. It should instead be a loop with dispatching messages like this:
MSG Message;
while(PeekMessage(&Message, NULL, WM_NULL, WM_NULL, PM_REMOVE))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
So that window messages that IE or COM could have posted as a part of processing your request, would reach the target windows and would be handled. Instead you are locking the thread giving it no option to do what it should do. The loop above only dispatches messages once, however, so if you need a timeout, you could do it as an infinite loop waking up on some indication of success or failure in processing, or a event-an-message based loop that wakes up on a message and falls back sleeping without wasting CPU cycles otherwise.
That is, your Sleep for 20 seconds still processing window messages could have been this (the snippet should be good for copy/pasting for replacement):
const ULONG nTimeoutTime = GetTickCount() + 20 * 1000; // In 20 seconds
const HANDLE hFakeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
for(; ; )
{
const LONG nWaitTime = nTimeoutTime - GetTickCount();
if(nWaitTime <= 0)
break; // Timeout
const DWORD nWaitResult = MsgWaitForMultipleObjects(1, &hFakeEvent, FALSE, nWaitTime, QS_ALLINPUT | QS_ALLPOSTMESSAGE);
//ATLTRACE(_T("nWaitResult 0x%x\n"), nWaitResult);
//ATLASSERT(nWaitResult == WAIT_OBJECT_0 + 1 || nWaitResult == WAIT_TIMEOUT);
if(nWaitResult == WAIT_TIMEOUT)
break; // Timeout
MSG Message;
while(PeekMessage(&Message, NULL, WM_NULL, WM_NULL, PM_REMOVE))
{
//ATLTRACE(_T("Message.hwnd 0x%p, Message.message 0x%04x\n"), Message.hwnd, Message.message);
TranslateMessage(&Message);
DispatchMessage(&Message);
}
}
CloseHandle(hFakeEvent);

What to connect/link my DDiscMaster2Events to for drive change events. Microsoft COM objects

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.

How to load & call a VBScript function from within C++?

We have customers asking for VBScript functions to be called when particular actions occur within our product. I've been trying to research the Windows Scripting technologies but I'm having difficulty finding exactly what I need. Hopefully some of you can help.
Our product is a native C++ Windows product. The customer would specify a VBScript file, which we would load, and whenever a particular event occurs, we'd call a particular function in the VBScript and let it do its thing. We may provide objects within the script's namespace for it to access information about our product as well.
I found some information on MSDN about the IActiveScript interface, and some related things, but cannot find any examples of instantiating a COM object that implements this interface for VBScript.
I know that PowerShell would probably be a better option for this these days, but our customers are stuck in a lot of legacy systems and VBScript is what they know.
Any help you can provide (links or otherwise) would be appreciated!
I've put together a "Hello World" IActiveScript C++ ATL console application that:
Define CSimpleScriptSite class
Implement IActiveScriptSite interface (mandatory)
Implement IActiveScriptSiteWindow interface (optional)
Minimum implementation with most functions implemented with a dummy stub
Has no error handling. Consult MSDN IActiveScriptError.
Use CoCreateInstance a new IActiveSite object
Create instances of both VBScript and JScript
Link the IActiveSite to IActiveScriptSite using IActiveSite::SetScriptSite
Call QueryInterface to get an IActiveScriptParse interface
Use IActiveScriptParse to execute VBScript or JScript code
The sample:
Evaluates an expression in JScript
Evaluates an expression in VBScript
Runs a command in VBScript
Code:
#include "stdafx.h"
#include <atlbase.h>
#include <activscp.h>
class CSimpleScriptSite :
public IActiveScriptSite,
public IActiveScriptSiteWindow
{
public:
CSimpleScriptSite() : m_cRefCount(1), m_hWnd(NULL) { }
// IUnknown
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
// IActiveScriptSite
STDMETHOD(GetLCID)(LCID *plcid){ *plcid = 0; return S_OK; }
STDMETHOD(GetItemInfo)(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppiunkItem, ITypeInfo **ppti) { return TYPE_E_ELEMENTNOTFOUND; }
STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) { *pbstrVersion = SysAllocString(L"1.0"); return S_OK; }
STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult, const EXCEPINFO *pexcepinfo) { return S_OK; }
STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) { return S_OK; }
STDMETHOD(OnScriptError)(IActiveScriptError *pIActiveScriptError) { return S_OK; }
STDMETHOD(OnEnterScript)(void) { return S_OK; }
STDMETHOD(OnLeaveScript)(void) { return S_OK; }
// IActiveScriptSiteWindow
STDMETHOD(GetWindow)(HWND *phWnd) { *phWnd = m_hWnd; return S_OK; }
STDMETHOD(EnableModeless)(BOOL fEnable) { return S_OK; }
// Miscellaneous
HRESULT SetWindow(HWND hWnd) { m_hWnd = hWnd; return S_OK; }
public:
LONG m_cRefCount;
HWND m_hWnd;
};
STDMETHODIMP_(ULONG) CSimpleScriptSite::AddRef()
{
return InterlockedIncrement(&m_cRefCount);
}
STDMETHODIMP_(ULONG) CSimpleScriptSite::Release()
{
if (!InterlockedDecrement(&m_cRefCount))
{
delete this;
return 0;
}
return m_cRefCount;
}
STDMETHODIMP CSimpleScriptSite::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IActiveScriptSiteWindow)
{
*ppvObject = (IActiveScriptSiteWindow *) this;
AddRef();
return NOERROR;
}
if (riid == IID_IActiveScriptSite)
{
*ppvObject = (IActiveScriptSite *) this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Initialize
CSimpleScriptSite* pScriptSite = new CSimpleScriptSite();
CComPtr<IActiveScript> spJScript;
CComPtr<IActiveScriptParse> spJScriptParse;
hr = spJScript.CoCreateInstance(OLESTR("JScript"));
hr = spJScript->SetScriptSite(pScriptSite);
hr = spJScript->QueryInterface(&spJScriptParse);
hr = spJScriptParse->InitNew();
CComPtr<IActiveScript> spVBScript;
CComPtr<IActiveScriptParse> spVBScriptParse;
hr = spVBScript.CoCreateInstance(OLESTR("VBScript"));
hr = spVBScript->SetScriptSite(pScriptSite);
hr = spVBScript->QueryInterface(&spVBScriptParse);
hr = spVBScriptParse->InitNew();
// Run some scripts
CComVariant result;
EXCEPINFO ei = { };
hr = spJScriptParse->ParseScriptText(OLESTR("(new Date()).getTime()"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("Now"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("MsgBox \"Hello World! The current time is: \" & Now"), NULL, NULL, NULL, 0, 0, 0, &result, &ei);
// Cleanup
spVBScriptParse = NULL;
spVBScript = NULL;
spJScriptParse = NULL;
spJScript = NULL;
pScriptSite->Release();
pScriptSite = NULL;
::CoUninitialize();
return 0;
}
A version of the above code can be found here:
https://github.com/stephenquan/RunScriptDemo
IActiveScript and related interfaces work very well. I use them in my product exactly the same way you have described. Some of out customers write their own VBScript and JScript scripts to analyze and update application data before it gets posted to a database.
You use CoCreateInstance() to instantiate IActiveScript, like you would any other COM object. You would then call its QueryInterface() method to obtain an IActiveScriptParse interface for loading snippets of scripting code, and then you update the IActiveScript's state to execute the code.
You can add custom objects to the script by implementing IDispatch-derived classes and then passing them to the engine using IActiveScript::AddNamedItem() and an IActiveScriptSite::GetItemInfo() callback.
There are examples of IActiveScript usage available on MSDN.

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