当我们把网线插到计算机上时,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来搞定。实现方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| class CNetworkListManagerEvent : public INetworkListManagerEvents { public: CNetworkListManagerEvent() : m_ref(1) {
}
~CNetworkListManagerEvent() {
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { HRESULT Result = S_OK; if (IsEqualIID(riid, IID_IUnknown)) { *ppvObject = (IUnknown*)this; } else if (IsEqualIID(riid, IID_INetworkListManagerEvents)) { *ppvObject = (INetworkListManagerEvents*)this; } else { Result = E_NOINTERFACE; }
return Result; }
ULONG STDMETHODCALLTYPE AddRef() { return (ULONG)InterlockedIncrement(&m_ref); }
ULONG STDMETHODCALLTYPE Release() { LONG Result = InterlockedDecrement(&m_ref); if (Result == 0) delete this; return (ULONG)Result; }
virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged( NLM_CONNECTIVITY newConnectivity) { return S_OK; }
private: LONG m_ref; };
int _tmain(int argc, TCHAR** argv, TCHAR** Env) { CoInitialize(NULL);
IUnknown* pUnknown = NULL;
HRESULT Result = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnknown); if (SUCCEEDED(Result)) { INetworkListManager* pNetworkListManager = NULL; Result = pUnknown->QueryInterface(IID_INetworkListManager, (void**)&pNetworkListManager); if (SUCCEEDED(Result)) { VARIANT_BOOL IsConnect = VARIANT_FALSE; Result = pNetworkListManager->get_IsConnectedToInternet(&IsConnect); if (SUCCEEDED(Result)) { printf("IsConnect Result %s\n", IsConnect == VARIANT_TRUE ? "TRUE" : "FALSE"); }
IConnectionPointContainer* pCPContainer = NULL; Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPContainer); if (SUCCEEDED(Result)) { IConnectionPoint* pConnectPoint = NULL; Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); if (SUCCEEDED(Result)) { DWORD Cookie = NULL; CNetworkListManagerEvent* NetEvent = new CNetworkListManagerEvent; Result = pConnectPoint->Advise((IUnknown*)NetEvent, &Cookie); if (SUCCEEDED(Result)) { printf("Loop Message\n"); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
if (msg.message == WM_QUIT) { break; } }
pConnectPoint->Unadvise(Cookie);
pConnectPoint->Release(); } }
pCPContainer->Release(); }
pNetworkListManager->Release(); }
pUnknown->Release(); }
CoUninitialize(); return 1; }
|
因为NLA API是WINDOWS VISTA之后才有的,对于WINDOWS XP是不兼容的,但是WINDOWS XP下有一个方法可以达到同样的效果,可以参考文章Network Awareness in Windows XP,这个文章里的代码是C#的,其中关键的代码转换成 C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void WaitForNetworkChnages() { WSAQUERYSET querySet = { 0 }; querySet.dwSize = sizeof(WSAQUERYSET); querySet.dwNameSpace = NS_NLA;
HANDLE LookupHandle = NULL; WSALookupServiceBegin(&querySet, LUP_RETURN_ALL, &LookupHandle); DWORD BytesReturned = 0; WSANSPIoctl(LookupHandle, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &BytesReturned, NULL); WSALookupServiceEnd(LookupHandle); }
void Test() { WSAData data = { 0 }; WSAStartup(MAKEWORD(2, 0), &data); int i = 0; while (1) { printf("BeginWait %d\n", i++); WaitForNetworkChnages(); printf("EndWait\n"); }
WSACleanup(); }
|