赞
踩
最近项目中需要扫描和连接windows系统的WiFi无线热点,在网上CSDN了一些,发现基本上都是基于 QT---Native Wifi functions 应用(WiFi有密码连接) 这篇文章转来转去的,这篇文章比较笼统,大概就是API接口的链接放上去;考虑到WiFi管理功能在windows程序开发中遇到的机会较多,Native Wifi 的API接口在MSDN里都有,但是一些接口的使用以及状态的获取,没有一个总结性的说明,导致没办法短时间做出比较完整的WiFi管理功能。所以,我总结一套比较常用的windows下WiFi管理逻辑,满足 扫描、连接、断开、状态判断、获取密码的功能。
0.WiFi管理的准备
windows管理WiFi需要程序代码创建一个Client,接口是WlanOpenHandle,返回一个ClientHandle供程序使用,同时通过接口WlanRegisterNotification,注册回调函数,获取所有无线接口的状态通知,比如扫描完成、正在断开连接、连接完成之类的,都可以从这个接口获取到,代码示例:
- bool WiFiManager::InitialHandle()
- {
- DWORD dwResult = 0;
- DWORD dwCurVersion = 0;
- DWORD dwMaxClient = 2;
- if (m_wlanHandle == NULL)
- {
- if ((dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &m_wlanHandle)) != ERROR_SUCCESS)
- {
- std::cout << "wlanOpenHandle failed with error: " << dwResult << std::endl;
- m_wlanHandle = NULL;
- return false;
- }
-
- // 注册消息通知回调
- if ((dwResult = WlanRegisterNotification(m_wlanHandle, WLAN_NOTIFICATION_SOURCE_ALL, TRUE, WLAN_NOTIFICATION_CALLBACK(OnNotificationCallback), NULL, nullptr, nullptr)) != ERROR_SUCCESS)
- {
- std::cout << "wlanRegisterNotification failed with error: " << dwResult << std::endl;
- m_wlanHandle = NULL;
- return false;
- }
- }
- return true;
- }

1.首先是WiFi的扫描功能。
桌面右下角的那个无线图标,点开弹出无线热点界面后,会发起对WLAN无线网卡的扫描,来获取当前无线网卡探测到的热点。win10系统,只有点开这个界面才会扫描获取新的热点列表(这也是我们之前调用命令行管理WiFi遇到的列表无法更新的问题)。对于系统,我试过win10的,系统不会主动对网卡进行扫描,只会每隔1分钟扫描当前列表的状态(虽然回调函数也会返回WlanScan的扫描完成信号),所以我们的代码不主动发起对网卡扫描,就无法获得新的热点列表。
Native Wifi实现对网卡列表扫描的接口是 WlanScan ,API中有一句话(complete a WlanScan function request in 4 seconds.),所以这个扫描过程会在4s内完成,扫描完成的信号可以通过回调函数获得。示例代码如下,获取网卡接口,再对每个网卡接口扫描热点:
- //获取Wlan的网卡接口数据
- dwResult = WlanEnumInterfaces(m_wlanHandle, NULL, &pIfList);
- if (dwResult != ERROR_SUCCESS)
- {
- return false;
- }
-
- for (i = 0; i < (int)pIfList->dwNumberOfItems; i++) //遍历每个网卡
- {
- pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
-
- dwResult = StringFromGUID2(pIfInfo->InterfaceGuid, (LPOLESTR)&GuidString,
- sizeof(GuidString) / sizeof(*GuidString));
-
- // 向无线网卡发送探测请求,WlanScan探测会在4秒内陆续更新WIFIList
- dwResult = WlanScan(m_wlanHandle, (const GUID*)(&pIfInfo->InterfaceGuid), NULL, NULL, NULL);
- if (dwResult != ERROR_SUCCESS)
- {
- return false;
- }
- }

2.获取WiFi热点列表。
和扫描差不多,先调用WlanEnumInterfaces 获取电脑使能的无线网卡信息,然后对每个无线网卡调用WlanGetAvailableNetworkList 获取每个网卡上可用的网络列表,列表结构为 WLAN_AVAILABLE_NETWORK_LIST ,每个List里的项为 WLAN_AVAILABLE_NETWORK,这个结构里包含了每个热点的基本信息:
- typedef struct _WLAN_AVAILABLE_NETWORK {
- WCHAR strProfileName[WLAN_MAX_NAME_LENGTH];//配置文件名称
- DOT11_SSID dot11Ssid; //热点名称
- DOT11_BSS_TYPE dot11BssType;
- ULONG uNumberOfBssids;
- BOOL bNetworkConnectable; //是否可连接
- WLAN_REASON_CODE wlanNotConnectableReason; //不可连接的原因
- ULONG uNumberOfPhyTypes;
- DOT11_PHY_TYPE dot11PhyTypes[WLAN_MAX_PHY_TYPE_NUMBER];
- BOOL bMorePhyTypes;
- WLAN_SIGNAL_QUALITY wlanSignalQuality;//信号质量
- BOOL bSecurityEnabled;
- DOT11_AUTH_ALGORITHM dot11DefaultAuthAlgorithm;
- DOT11_CIPHER_ALGORITHM dot11DefaultCipherAlgorithm;
- DWORD dwFlags;//连接状态与是否有配置文件的位或
- DWORD dwReserved;
- } WLAN_AVAILABLE_NETWORK, *PWLAN_AVAILABLE_NETWORK;

解析对应字段基本可以满足常见的WiFi信息功能使用,获取代码参考如下:
- for (i = 0; i < (int)pIfList->dwNumberOfItems; i++)
- {
- pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
- dwResult = WlanGetAvailableNetworkList(m_wlanHandle,
- &pIfInfo->InterfaceGuid,
- 2,
- NULL,
- &pBssList);
-
- if (dwResult != ERROR_SUCCESS)
- {
- std::cout << "WlanGetAvailableNetworkList failed with error:" << dwResult;
- return false;
- }
-
- for (j = 0; j < pBssList->dwNumberOfItems; j++)
- {
- pBssEntry = (WLAN_AVAILABLE_NETWORK*)&pBssList->Network[j];
-
- WiFiInfo info;
-
- //获取SSID
- char ssid[36];
- memcpy(ssid, pBssEntry->dot11Ssid.ucSSID, pBssEntry->dot11Ssid.uSSIDLength);
- ssid[pBssEntry->dot11Ssid.uSSIDLength] = 0;
- Utf8ToUnicode(info.cstrSSID, ssid);
-
- //信号强度
- info.nSignalValue = pBssEntry->wlanSignalQuality;
-
- //连接状态
- info.bLinked = pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED;
- }
- ....
- }

3.WiFi热点密码的设置与修改
这个功能必须要说WiFi的配置文件了,在windows命令行输入 :
netsh wlan show profile
可以获取系统已经生成的WiFi配置文件,对于用户已经连接的热点,系统会生成一个热点对应的配置文件,保存热点的密码等相关设置,WiFi的自动连接也是读取这些记录的profile,它的格式是一个xml文件,字段解析详见 WLAN_profile Schema,每个字段对应的配置项不再赘述。
我们只说获取和修改WiFI密码字段,其实就是获取和修改xml中的 keyMaterial (sharedKey) Element 项。
获取WiFi热点的profile xml文件通过接口 WlanGetProfile ,这里需要注意 pdwFlags 参数要设置WLAN_PROFILE_GET_PLAINTEXT_KEY ,
DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;
否则获取到的密码是被系统加密过的,被加密过的字段需要使用 CryptUnprotectData 系统函数解码,比较麻烦,所以直接获取密码明文。获取WiFi密码的代码示例:
- DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;
- DWORD dwGrantedAccess = WLAN_READ_ACCESS;
- DWORD dwResult = WlanGetProfile(m_wlanHandle,
- &pIfInfo->InterfaceGuid,
- info.cstrSSID,
- NULL,
- &info.pProfileXml,
- &dwFlags,
- &dwGrantedAccess);
- if (dwResult == ERROR_SUCCESS)
- {
- //获取密码
- CString cstrXml = info.pProfileXml;
- int nFirstIndex = cstrXml.Find(_T("<keyMaterial>"));
- if (nFirstIndex != -1)
- {
- int nLastIndex = cstrXml.Find(_T("</keyMaterial>"));
- CString strKey = CString(((LPCTSTR)cstrXml) + nFirstIndex + 13, nLastIndex - (nFirstIndex + 13));
-
- //解码key,如果字段被加密了,设置过WLAN_PROFILE_GET_PLAINTEXT_KEY 这个不用
- BYTE byteKey[1024] = { 0 };
- DWORD dwLength = 1024;
- DATA_BLOB dataOut, dataVerify;
-
- BOOL bRes = CryptStringToBinary(strKey, strKey.GetLength(), CRYPT_STRING_HEX, byteKey, &dwLength, 0, 0);
- if (bRes)
- {
- dataOut.cbData = dwLength;
- dataOut.pbData = (BYTE*)byteKey;
- if (CryptUnprotectData(&dataOut, NULL, NULL, NULL, NULL, 0, &dataVerify))
- {
- TCHAR str[MAX_PATH] = { 0 };
- wsprintf(str, L"%hs", dataVerify.pbData);
- strKey = str;
- }
- }
- info.cstrPassword = strKey;
- }
- }

与获取profile接口 WlanGetProfile 对应的是 WlanSetProfile 写入一个 xml文件。
- DWORD WlanSetProfile(
- HANDLE hClientHandle,
- const GUID *pInterfaceGuid,
- DWORD dwFlags,
- LPCWSTR strProfileXml,
- LPCWSTR strAllUserProfileSecurity,
- BOOL bOverwrite,
- PVOID pReserved,
- DWORD *pdwReasonCode
- );
我们将要设置的xml内容通过 strProfileXml 参数传入,对于修改已存在的profile,传入strProfileXml的同时,bOverwrite 要设置成 true表示重写,否则 WlanSetProfile 会调用失败,返回 ERROR_ALREADY_EXISTS错误。代码示例:
- void setProfile(CString& curSSID, CString targetKey, PWLAN_AVAILABLE_NETWORK pNet, GUID interfaceGuid)
- {
- CStringW szProfileXML(""); //Profile XML流
- wchar_t* wscProfileXML = NULL;
- /*组合参数XML码流*/
- CString szTemp("");
- /*头*/
- szProfileXML += _T("<?xml version=\"1.0\"?><WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"><name>");
- /*name,一般与SSID相同*/
- szTemp = curSSID;
- szProfileXML += szTemp;
- /*SSIDConfig*/
- szProfileXML += ("</name><SSIDConfig><SSID><name>");
- char ssid[36];
- memcpy(ssid, pNet->dot11Ssid.ucSSID, pNet->dot11Ssid.uSSIDLength);
- ssid[pNet->dot11Ssid.uSSIDLength] = 0;
- szProfileXML += ssid;
- szProfileXML += ("</name></SSID></SSIDConfig>");
- /*connectionType*/
- szProfileXML += ("<connectionType>");
- switch (pNet->dot11BssType)
- {
- case dot11_BSS_type_infrastructure:
- szProfileXML += "ESS";
- break;
- case dot11_BSS_type_independent:
- szProfileXML += "IBSS";
- break;
- case dot11_BSS_type_any:
- szProfileXML += "Any";
- break;
- default:
- wprintf(L"Unknown BSS type");
- return false;
- }
-
- szProfileXML += ("</connectionType><MSM><security><authEncryption><authentication>");
- switch (pNet->dot11DefaultAuthAlgorithm)
- {
- case DOT11_AUTH_ALGO_80211_OPEN:
- szProfileXML += "open";
- wprintf(L"Open 802.11 authentication\n");
- break;
- case DOT11_AUTH_ALGO_80211_SHARED_KEY:
- szProfileXML += "shared";
- wprintf(L"Shared 802.11 authentication");
- break;
- case DOT11_AUTH_ALGO_WPA:
- szProfileXML += "WPA";
- wprintf(L"WPA-Enterprise 802.11 authentication\n");
- break;
- case DOT11_AUTH_ALGO_WPA_PSK:
- szProfileXML += "WPAPSK";
- wprintf(L"WPA-Personal 802.11 authentication\n");
- break;
- case DOT11_AUTH_ALGO_WPA_NONE:
- szProfileXML += "none";
- wprintf(L"WPA-NONE,not exist in MSDN\n");
- break;
- case DOT11_AUTH_ALGO_RSNA:
- szProfileXML += "WPA2";
- wprintf(L"WPA2-Enterprise 802.11 authentication\n");
- break;
- case DOT11_AUTH_ALGO_RSNA_PSK:
- szProfileXML += "WPA2PSK";
- wprintf(L"WPA2-Personal 802.11 authentication\n");
- break;
- default:
- wprintf(L"Unknown authentication");
- return false;
- }
- szProfileXML += ("</authentication><encryption>");
- switch (pNet->dot11DefaultCipherAlgorithm)
- {
- case DOT11_CIPHER_ALGO_NONE:
- szProfileXML += "none";
- break;
- case DOT11_CIPHER_ALGO_WEP40:
- szProfileXML += "WEP";
- break;
- case DOT11_CIPHER_ALGO_TKIP:
- szProfileXML += "TKIP";
- break;
- case DOT11_CIPHER_ALGO_CCMP:
- szProfileXML += "AES";
- break;
- case DOT11_CIPHER_ALGO_WEP104:
- szProfileXML += "WEP";
- break;
- case DOT11_CIPHER_ALGO_WEP:
- szProfileXML += "WEP";
- break;
- case DOT11_CIPHER_ALGO_WPA_USE_GROUP:
- wprintf(L"USE-GROUP not exist in MSDN");
- default:
- wprintf(L"Unknown encryption");
- return false;
- }
- szProfileXML += ("</encryption></authEncryption><sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial>");
-
- szProfileXML += targetKey;
- /*尾*/
- szProfileXML += ("</keyMaterial></sharedKey></security></MSM></WLANProfile>");
- /*XML码流转换成双字节*/
- wscProfileXML = szProfileXML.GetBuffer();
- if (NULL == wscProfileXML)
- {
- wprintf(L"Change wscProfileXML fail\n");
- return false;
- }
- /*设置网络参数*/
- DWORD dwReasonCode = 0;
- DWORD dwResult = WlanSetProfile(m_wlanHandle, &interfaceGuid,
- 0x00, wscProfileXML, NULL, TRUE, NULL, &dwReasonCode);
- if (ERROR_SUCCESS != dwResult)
- {
- return false;
- }
- }

3.WiFi的连接
连接接口是 WlanConnect,连接之前,需要判断当前要连接的热点是否有profile(热点列表 WLAN_AVAILABLE_NETWORK字段里有),没有profile的话需要自己写一个xml,调用WlanSetProfile 配置下去,需要注意的是 WlanConnect 的返回值是立即返回的,表示调用的成功或失败原因,并不表示热点的连接状态,热点的连接状态需要我们前面提到的回调函数里获取。代码示例:
- //连接WIFi
- WLAN_AVAILABLE_NETWORK wlanAN = pBssList->Network[j];
- WLAN_CONNECTION_PARAMETERS wlanConnPara;
- wlanConnPara.wlanConnectionMode = wlan_connection_mode_profile; //YES,WE CONNECT AP VIA THE PROFILE
- wlanConnPara.strProfile = wlanAN.strProfileName; // set the profile name
- wlanConnPara.pDot11Ssid = NULL; // SET SSID NULL
- wlanConnPara.dot11BssType = dot11_BSS_type_infrastructure; //dot11_BSS_type_any,I do not need it this time.
- wlanConnPara.pDesiredBssidList = NULL; // the desired BSSID list is empty
- wlanConnPara.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK; //it works on my WIN78
- dwResult = WlanConnect(m_wlanHandle, &pIfInfo->InterfaceGuid, &wlanConnPara, NULL);
- if (dwResult == ERROR_SUCCESS)
- {
- //WlanConnect是立即返回的,所有返回值没意义,通过注册的回调函数OnNotificationCallback获取连接结果
- }
4.回调函数获取状态
对于回调函数 WLAN_NOTIFICATION_CALLBACK,参数1 PWLAN_NOTIFICATION_DATA,NotificationSource 字段 对WLAN系统消息进行分类,NotificationCode表示系统消息的具体消息类型
参数2是NotificationCode消息类型的数据指针,指向该类消息的具体内容;
我们可以通过回调函数获取WLAN连接的各种状态提示,示例如下:
- void OnNotificationCallback(PWLAN_NOTIFICATION_DATA data, PVOID context)
- {
- if (data != NULL && data->NotificationSource == WLAN_NOTIFICATION_SOURCE_ACM)
- {
- switch (data->NotificationCode)
- {
- case wlan_notification_acm_scan_complete:
- case wlan_notification_acm_scan_fail:
- WiFiManager::GetInstance().SetConnectResult(NULL, data->NotificationCode);
- break;
- case wlan_notification_acm_connection_start:
- case wlan_notification_acm_connection_complete:
- case wlan_notification_acm_connection_attempt_fail:
- case wlan_notification_acm_disconnecting:
- case wlan_notification_acm_disconnected:
- {
- PWLAN_CONNECTION_NOTIFICATION_DATA connection = (PWLAN_CONNECTION_NOTIFICATION_DATA)data->pData;
- WiFiManager::GetInstance().SetConnectResult(connection, data->NotificationCode);
- }
- break;
- default:
- break;
- }
- }
- }

下面是我写的WiFi管理工具源码,IDE是VS2015:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。