You can use the INetworkListManager
to request INetworkConnectionEvents
whenever there are changes in connectivity. To put this together you'll have to do the following:
- Implement an event sink of type
INetworkConnectionEvents
.
- Instantiate an
INetworkListManager
object.
- Connect the event sink.
The final step is what can be confusing, if you don't understand connection points. Raymond Chen has published a highly educational blog post on the topic if you need to catch up: An introduction to COM connection points.
The following roughly outlines how to implement this. It's using Microsoft's _com_ptr_t
so there's a fair amount of boilerplate code up front:
#if defined(_DEBUG)
# pragma comment(lib, "comsuppwd.lib")
#else
# pragma comment(lib, "comsuppw.lib")
#endif
#include <comdef.h>
#include <comip.h>
#include <netlistmgr.h>
_COM_SMARTPTR_TYPEDEF(INetworkListManager, __uuidof(INetworkListManager));
_COM_SMARTPTR_TYPEDEF(IConnectionPointContainer, __uuidof(IConnectionPointContainer));
_COM_SMARTPTR_TYPEDEF(IConnectionPoint, __uuidof(IConnectionPoint));
_COM_SMARTPTR_TYPEDEF(IUnknown, __uuidof(IUnknown));
This makes sure to link against the appropriate libraries, pulls in the required header files, and declares a handful of _com_ptr_t
-based smart pointer types. Next up is the INetworkConnectionEvents
implementation:
struct EventSink : INetworkListManagerEvents
{
HRESULT QueryInterface(REFIID riid, LPVOID* ppvObj)
{
if (!ppvObj)
{
return E_POINTER;
}
*ppvObj = nullptr;
if (riid == IID_IUnknown || riid == IID_INetworkListManagerEvents)
{
AddRef();
*ppvObj = reinterpret_cast<void*>(this);
return S_OK;
}
return E_NOINTERFACE;
}
ULONG AddRef() { return 1; }
ULONG Release() { return 1; }
HRESULT ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
{
if ((newConnectivity & (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET)) != 0)
{
printf("Internet connection available\n");
}
else
{
printf("Internet connection not available\n");
}
return S_OK;
}
HRESULT NetworkConnectionPropertyChanged(GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE) { return S_OK; }
};
This implements both the IUnknown
interface as well as the two INetworkConnectionEvents
members. The IUnknown
implementation is barely sufficient for our use-case; a production-quality implementation would need to provide proper reference counting through AddRef
and Release
.
The remaining code then instantiates an INetworkListManager
object and wires the event sink up to receive notifications:
int main()
{
// Initialize COM
auto hr { ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED) };
// Instantiate INetworkListManager object
INetworkListManagerPtr spManager { nullptr };
if SUCCEEDED (hr)
{
hr = spManager.CreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_ALL);
}
// Query for connection point container
IConnectionPointContainerPtr spConnectionPoints { nullptr };
if SUCCEEDED (hr)
{
hr = spManager.QueryInterface(IID_PPV_ARGS(&spConnectionPoints));
}
// Find connection point for the interesting event
IConnectionPointPtr spConnectionPoint { __nullptr };
if SUCCEEDED (hr)
{
hr = spConnectionPoints->FindConnectionPoint(IID_INetworkListManagerEvents, &spConnectionPoint);
}
// Construct event sink
EventSink sink {};
IUnknownPtr spSink { nullptr };
if (SUCCEEDED(hr))
{
hr = sink.QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&spSink));
}
// And wire it up to the connection point
DWORD cookie { 0 };
if SUCCEEDED (hr)
{
hr = spConnectionPoint->Advise(spSink, &cookie);
}
// At this point everything is set up to receive notifications
MSG msg {};
while (::GetMessageW(&msg, nullptr, 0, 0) > 0)
{
::DispatchMessageW(&msg);
}
// Cleanup
if (SUCCEEDED(hr))
{
hr = spConnectionPoint->Unadvise(cookie);
}
// Don't uninitialize COM since we have smart pointers that
// get cleaned up only after leaving this scope.
}