当我们把网线插到计算机上时,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 |
002 |
{ |
003 |
public : |
004 |
CNetworkListManagerEvent() : m_ref(1) |
005 |
{ |
006 |
007 |
} |
008 |
009 |
~CNetworkListManagerEvent() |
010 |
{ |
011 |
012 |
} |
013 |
014 |
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) |
015 |
{ |
016 |
HRESULT Result = S_OK; |
017 |
if (IsEqualIID(riid, IID_IUnknown)) |
018 |
{ |
019 |
*ppvObject = (IUnknown *) this ; |
020 |
} |
021 |
else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) |
022 |
{ |
023 |
*ppvObject = (INetworkListManagerEvents *) this ; |
024 |
} |
025 |
else |
026 |
{ |
027 |
Result = E_NOINTERFACE; |
028 |
} |
029 |
030 |
return Result; |
031 |
} |
032 |
033 |
ULONG STDMETHODCALLTYPE AddRef() |
034 |
{ |
035 |
return ( ULONG )InterlockedIncrement(&m_ref); |
036 |
} |
037 |
038 |
ULONG STDMETHODCALLTYPE Release() |
039 |
{ |
040 |
LONG Result = InterlockedDecrement(&m_ref); |
041 |
if (Result == 0) |
042 |
delete this ; |
043 |
return ( ULONG )Result; |
044 |
} |
045 |
046 |
virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged( |
047 |
/* [in] */ NLM_CONNECTIVITY newConnectivity) |
048 |
{ |
049 |
return S_OK; |
050 |
} |
051 |
052 |
private : |
053 |
054 |
LONG m_ref; |
055 |
}; |
056 |
057 |
int _tmain( int argc, TCHAR ** argv, TCHAR ** Env) |
058 |
{ |
059 |
CoInitialize(NULL); |
060 |
061 |
// |
062 |
// 通过NLA接口获取网络状态 |
063 |
// |
064 |
IUnknown *pUnknown = NULL; |
065 |
066 |
HRESULT Result = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_IUnknown, ( void **)&pUnknown); |
067 |
if (SUCCEEDED(Result)) |
068 |
{ |
069 |
INetworkListManager *pNetworkListManager = NULL; |
070 |
Result = pUnknown->QueryInterface(IID_INetworkListManager, ( void **)&pNetworkListManager); |
071 |
if (SUCCEEDED(Result)) |
072 |
{ |
073 |
VARIANT_BOOL IsConnect = VARIANT_FALSE; |
074 |
Result = pNetworkListManager->get_IsConnectedToInternet(&IsConnect); |
075 |
if (SUCCEEDED(Result)) |
076 |
{ |
077 |
printf ( "IsConnect Result %s\n" , IsConnect == VARIANT_TRUE ? "TRUE" : "FALSE" ); |
078 |
} |
079 |
080 |
IConnectionPointContainer *pCPContainer = NULL; |
081 |
Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, ( void **)&pCPContainer); |
082 |
if (SUCCEEDED(Result)) |
083 |
{ |
084 |
IConnectionPoint *pConnectPoint = NULL; |
085 |
Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); |
086 |
if (SUCCEEDED(Result)) |
087 |
{ |
088 |
DWORD Cookie = NULL; |
089 |
CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; |
090 |
Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); |
091 |
if (SUCCEEDED(Result)) |
092 |
{ |
093 |
printf ( "Loop Message\n" ); |
094 |
MSG msg; |
095 |
while (GetMessage(&msg, NULL, 0, 0)) |
096 |
{ |
097 |
TranslateMessage(&msg); |
098 |
DispatchMessage(&msg); |
099 |
100 |
if (msg.message == WM_QUIT) |
101 |
{ |
102 |
break ; |
103 |
} |
104 |
} |
105 |
106 |
pConnectPoint->Unadvise(Cookie); |
107 |
108 |
pConnectPoint->Release(); |
109 |
} |
110 |
} |
111 |
112 |
pCPContainer->Release(); |
113 |
} |
114 |
115 |
pNetworkListManager->Release(); |
116 |
} |
117 |
118 |
pUnknown->Release(); |
119 |
} |
120 |
121 |
CoUninitialize(); |
122 |
return 1; |
123 |
} |
因为NLA API是WINDOWS VISTA之后才有的,对于WINDOWS XP是不兼容的,但是WINDOWS XP下有一个方法可以达到同样的效果,可以参考文章Network Awareness in Windows XP,这个文章里的代码是C#的,其中关键的代码转换成C++
01 |
void WaitForNetworkChnages() |
02 |
{ |
03 |
WSAQUERYSET querySet = {0}; |
04 |
querySet.dwSize = sizeof (WSAQUERYSET); |
05 |
querySet.dwNameSpace = NS_NLA; |
06 |
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); |
12 |
} |
13 |
14 |
void Test() |
15 |
{ |
16 |
WSAData data = {0}; |
17 |
WSAStartup(MAKEWORD(2, 0), &data); |
18 |
int i = 0; |
19 |
while (1) |
20 |
{ |
21 |
printf ( "BeginWait %d\n" , i++); |
22 |
WaitForNetworkChnages(); |
23 |
printf ( "EndWait\n" ); |
24 |
} |
25 |
26 |
WSACleanup(); |
27 |
} |