当我们把网线插到计算机上时,WINDOWS任务栏的托盘图标都会更改相应的网络图标,拔掉也会有相应的处理。一直都对这个机制感兴趣,却不知道如何做,而且公司的某个产品也需要这么一个功能。昨天在家测试某个程序的时候,发现了其中一个线程的栈中有一个叫wininet!CheckForNetworkChange的函数,IDA分析了WINNET.DLL后,有了本文。
微软在WINDOWS VISTA之后提供了一个叫NLA(Network List Manager API)的接口,用于获取网络状态变化通知的一个接口。以COM技术实现。
主要导出的COM接口如下:
IEnumNetworkConnections
IEnumNetworks
INetwork
INetworkConnection
INetworkConnectionEvents
INetworkEvents
INetworkListManager
INetworkListManagerEvents
其中INetworkListManager是一个根对象,可以获取计算机是否连接到因特网(INetworkListManager->get_IsConnectedToInternet)。还可以查询有哪些可用的网络和连接.更关键的是INetworkListManagerEvents和INetworkEvents两个类。这两个类在MSDN文档里的描述如下:
is a message sink interface that a client implements to get overall machine state related events.
也就是说我们要自己实现这两个类。而回调的方式是通过COM技术中特有的机制IConnectionPoint来搞定。实现方式如下:
001 |
class CNetworkListManagerEvent : public INetworkListManagerEvents |
004 |
CNetworkListManagerEvent() : m_ref(1) |
009 |
~CNetworkListManagerEvent() |
014 |
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) |
016 |
HRESULT Result = S_OK; |
017 |
if (IsEqualIID(riid, IID_IUnknown)) |
019 |
*ppvObject = (IUnknown *)this; |
021 |
else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) |
023 |
*ppvObject = (INetworkListManagerEvents *)this; |
027 |
Result = E_NOINTERFACE; |
033 |
ULONG STDMETHODCALLTYPE AddRef() |
035 |
return (ULONG)InterlockedIncrement(&m_ref); |
038 |
ULONG STDMETHODCALLTYPE Release() |
040 |
LONG Result = InterlockedDecrement(&m_ref); |
043 |
return (ULONG)Result; |
046 |
virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged( |
047 |
/* [in] */ NLM_CONNECTIVITY newConnectivity) |
057 |
int _tmain(int argc, TCHAR** argv, TCHAR** Env) |
064 |
IUnknown *pUnknown = NULL; |
066 |
HRESULT Result = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_IUnknown, (void **)&pUnknown); |
067 |
if (SUCCEEDED(Result)) |
069 |
INetworkListManager *pNetworkListManager = NULL; |
070 |
Result = pUnknown->QueryInterface(IID_INetworkListManager, (void **)&pNetworkListManager); |
071 |
if (SUCCEEDED(Result)) |
073 |
VARIANT_BOOL IsConnect = VARIANT_FALSE; |
074 |
Result = pNetworkListManager->get_IsConnectedToInternet(&IsConnect); |
075 |
if (SUCCEEDED(Result)) |
077 |
printf("IsConnect Result %s\n", IsConnect == VARIANT_TRUE ? "TRUE" : "FALSE"); |
080 |
IConnectionPointContainer *pCPContainer = NULL; |
081 |
Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPContainer); |
082 |
if (SUCCEEDED(Result)) |
084 |
IConnectionPoint *pConnectPoint = NULL; |
085 |
Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); |
086 |
if(SUCCEEDED(Result)) |
089 |
CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; |
090 |
Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); |
091 |
if (SUCCEEDED(Result)) |
093 |
printf("Loop Message\n"); |
095 |
while(GetMessage(&msg, NULL, 0, 0)) |
097 |
TranslateMessage(&msg); |
098 |
DispatchMessage(&msg); |
100 |
if (msg.message == WM_QUIT) |
106 |
pConnectPoint->Unadvise(Cookie); |
108 |
pConnectPoint->Release(); |
112 |
pCPContainer->Release(); |
115 |
pNetworkListManager->Release(); |
因为NLA API是WINDOWS VISTA之后才有的,对于WINDOWS XP是不兼容的,但是WINDOWS XP下有一个方法可以达到同样的效果,可以参考文章Network Awareness in Windows XP,这个文章里的代码是C#的,其中关键的代码转换成C++
01 |
void WaitForNetworkChnages() |
03 |
WSAQUERYSET querySet = {0}; |
04 |
querySet.dwSize = sizeof(WSAQUERYSET); |
05 |
querySet.dwNameSpace = NS_NLA; |
07 |
HANDLE LookupHandle = NULL; |
08 |
WSALookupServiceBegin(&querySet, LUP_RETURN_ALL, &LookupHandle); |
09 |
DWORD BytesReturned = 0; |
10 |
WSANSPIoctl(LookupHandle, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &BytesReturned, NULL); |
11 |
WSALookupServiceEnd(LookupHandle); |
17 |
WSAStartup(MAKEWORD(2, 0), &data); |
21 |
printf("BeginWait %d\n", i++); |
22 |
WaitForNetworkChnages(); |