一、大概环境介绍
QT版本:5.12.1
编译器:MSVC2017
系统:Windows10_22H2
功能:扫描、连接(已配置直接连接,未配置输入密码连接,无密码WiFi连接),忘记WiFi等
ps.
(1)由于对wlanapi不太熟悉,所以这里wlanapi和windows控制台命令混合使用。
(2)代码写完有一段时间了,整理一下发出来当做一次复习,由于一些原因不能发源码,而且代码是截取出来的,直接复制可能会报错,仅当作给大家提供一个思路。
(3)代码注释可能不太全,但是每个我踩过坑的地方都有详尽的注释
(4)这里的代码相当于后端,给QML调用的,所以有些逻辑可能比较绕
二、实现功能&代码
1.包含文件&结构体&变量定义
(1)头文件
#include <QObject>
#include <QList>
#include <QString>
#include <QProcess>
#include <QTime>
#include <QCoreApplication>
#include "Windows.h"
#include "wlanapi.h"
#include "QNetworkInterface"
#include "QNetworkAddressEntry"
#include "sstream"
(2)wifi信息结构体
typedef struct _WifiListInfo{
QString ssid;//ID
QString level;//信号等级
QString keyType;//加密类型
}WifiListInfo;
(3)连接过的WiFi信息结构体
typedef struct _windowsWifiHistoryInfo {
QString ssid;//ID
QString passWord;//密码——这份代码没用到
}windowsWifiHistoryInfo;
(4)变量、指针定义
QList<WifiListInfo> wifiInfoList;//所有wifi信息列表
QProcess *m_Process;//windows控制台操作指针
//网络连接部分
HANDLE hClientHandle;//操作句柄
PWLAN_INTERFACE_INFO_LIST pInterfaceList;//系统的网口信息
QList<windowsWifiHistoryInfo>windowsWifiHistoryInfoList;//连接过的WiFi列表
(5)定义的WiFi连接XML文件
A、有密码
static const QString STR_PROFILE=
"<?xml version=\"1.0\"?> \
<WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\">\
<name>Home</name>\
<SSIDConfig>\
<SSID>\
<hex>home</hex>\
</SSID>\
</SSIDConfig>\
<connectionType>ESS</connectionType>\
<connectionMode>auto</connectionMode>\
<MSM>\
<security>\
<authEncryption>\
<authentication>WPA2PSK</authentication>\
<encryption>AES</encryption>\
<useOneX>false</useOneX>\
</authEncryption>\
<sharedKey>\
<keyType>passPhrase</keyType>\
<protected>false</protected>\
<keyMaterial>AAAAAA</keyMaterial>\
</sharedKey>\
</security>\
</MSM>\
</WLANProfile>";
B、无密码
static const QString STR_PROFILE_NoPossWord=
"<?xml version=\"1.0\"?> \
<WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\">\
<name>Home</name>\
<SSIDConfig>\
<SSID>\
<hex>home</hex>\
</SSID>\
</SSIDConfig>\
<connectionType>ESS</connectionType>\
<connectionMode>auto</connectionMode>\
<MSM>\
<security>\
<authEncryption>\
<authentication>open</authentication>\
<encryption>none</encryption>\
<useOneX>false</useOneX>\
</authEncryption>\
</security>\
</MSM>\
</WLANProfile>";
2、获取范围内WiFi信息
QStringList CWifiInfoManage::getWifiInfoList()
{
QStringList vWifiInfoList;
DWORD dwError = ERROR_SUCCESS;
DWORD dwNegotiatedVersion;
dwError = WlanOpenHandle(1, nullptr, &dwNegotiatedVersion, &hClientHandle);
if (dwError != ERROR_SUCCESS)
{
// qDebug()<<"未打开成功!";
WlanCloseHandle(hClientHandle,nullptr);
return vWifiInfoList;
}
dwError = WlanEnumInterfaces(hClientHandle, nullptr,&pInterfaceList);//获取计算机上可用LAN口
if ( dwError != ERROR_SUCCESS )
{
// qDebug()<<"未发现wifi相关列表!";
WlanFreeMemory(pInterfaceList);//释放列表
//释放内存,从Native Wifi函数返回的任何内存必须释放
WlanCloseHandle(hClientHandle,nullptr);//关闭wlan
return vWifiInfoList;
}
GUID &guid = pInterfaceList->InterfaceInfo[0].InterfaceGuid;//指定lan口,无线网卡一般是0
PWLAN_AVAILABLE_NETWORK_LIST pWLAN_AVAILABLE_NETWORK_LIST = nullptr;
dwError = WlanGetAvailableNetworkList(hClientHandle, &guid,2,nullptr, &pWLAN_AVAILABLE_NETWORK_LIST);//获取指定LAN口上的所有网络信息——扫描wifi
if (dwError != ERROR_SUCCESS)
{
// qDebug()<<"未发现可用的网络!";
WlanFreeMemory(pInterfaceList);
WlanFreeMemory(pWLAN_AVAILABLE_NETWORK_LIST);
WlanCloseHandle(hClientHandle,nullptr);
return vWifiInfoList;
}
WLAN_AVAILABLE_NETWORK wlanAN;
//结构包含可用的无线网络信息
//访问的是_WLAN_AUTH_CIPHER_PAIR_LIST结构体中的成员,表示可用网络的数量
DWORD numberOfItems = pWLAN_AVAILABLE_NETWORK_LIST->dwNumberOfItems;//储存可用网络的个数
QString NumberAvailWIFI = QString::number(numberOfItems,10);
if (numberOfItems > 0)//遍历所有的
{
wifiInfoList.clear();
for(DWORD i = 0; i <numberOfItems; i++)
{
//访问的是_WLAN_AUTH_CIPHER_PAIR_LIST结构体中的成员,表示可用网络的数目,每个ip都会存在Network数组里面
wlanAN = pWLAN_AVAILABLE_NETWORK_LIST->Network[i];
if (wlanAN.dot11Ssid.uSSIDLength != 0)
{
WifiListInfo wWifiListInfo;
//获取SSID
char *p = (char*) wlanAN.dot11Ssid.ucSSID;
wWifiListInfo.ssid = QString::fromUtf8(p);
bool uniqueName = true;
for (int i=0;i<wifiInfoList.size();i++)
{
if(wWifiListInfo.ssid == wifiInfoList[i].ssid)
{
uniqueName = false;
}
}
if(wWifiListInfo.ssid==""||uniqueName == false)
continue;
//获取信号等级
unsigned long int level = wlanAN.wlanSignalQuality;
if(level>0&&level<=25)
wWifiListInfo.level = "0";
else if (level>25&&level<=50)
wWifiListInfo.level = "1";
else if (level>50&&level<=75)
wWifiListInfo.level = "2";
else if (level>75&&level<=100)
wWifiListInfo.level = "3";
//是否加密
wWifiListInfo.keyType = "0";
if(wlanAN.bSecurityEnabled)
wWifiListInfo.keyType = "1";
wifiInfoList.append(wWifiListInfo);
QString sWifiInfo = wWifiListInfo.ssid+","+wWifiListInfo.level+","+wWifiListInfo.keyType;
vWifiInfoList.append(sWifiInfo);
}
}
}
return vWifiInfoList;
}
3、获取本机连接(配置)过的WiFi列表
没有找到wlanapi上的接口,使用命令行的方式。可是在CMD上实际试一试,可以很清楚的看到命令的作用
void CWifiInfoManage::getHistoryWifiList()
{
m_Process =new QProcess();
m_Process->start("netsh wlan show profiles");
m_Process->waitForStarted();
m_Process->waitForFinished();
QString strConfig = QString::fromLocal8Bit(m_Process->readAllStandardOutput());
strConfig.remove("\r");
strConfig.remove("\n");
strConfig.remove(" ");
QStringList strConfigList = strConfig.split("所有用户配置文件:");
windowsWifiHistoryInfoList.clear();
windowsWifiHistoryInfo fillaWifi;
fillaWifi.ssid = "fillaWifiInfo";
windowsWifiHistoryInfoList.append(fillaWifi);//使用一个无效参数填充0位
for (int i=1;i<strConfigList.length();i++)
{
windowsWifiHistoryInfo wifiConfig;
wifiConfig.ssid = strConfigList[i];
windowsWifiHistoryInfoList.append(wifiConfig);
}
}
4、连接
这里分几种情况
1、未配置WiFi(使用定义的XML文件连接)(代码中有对XML文件部分的解释,仔细看)
(1)有密码
(2)无密码
2、已配置WiFi(通过SSID获取系统的XML文件用于连接)
//参数解释:依次为WiFi名称,密码(已配置的WiFi传空就可以了),加密类型,是否配置过(连接过)
//有没有配置过直接遍历windowsWifiHistoryInfoList列表
bool CWifiInfoManage::connectWifi(QString wifiName, QString passWord, int keyType, int index)
{
//已配置wifi直接引用xml文件连接
if(index!=0)
{
LPWSTR profile;
DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;
DWORD dwGrantedAccess = WLAN_READ_ACCESS;
LPWSTR pProfileXml;
std::wstring wlpstrstd = wifiName.toStdWString();
LPCWSTR str = wlpstrstd.c_str();
//根据WiFi名称获取对应的XML文件
DWORD dwResult = WlanGetProfile(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
str,
NULL,
&pProfileXml,
&dwFlags,
&dwGrantedAccess);
if(dwResult == ERROR_SUCCESS)
{
profile = pProfileXml;
}
DWORD dwReasonCode = 0;
//使用这个XML文件进行连接
dwResult = WlanSetProfile(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
0,
profile,
NULL,
TRUE,
NULL,
&dwReasonCode);
if(dwResult == ERROR_SUCCESS)
{
wchar_t wifi[MAX_PATH] = {};
wifiName.toWCharArray(wifi);
WLAN_CONNECTION_PARAMETERS wlanConnPara;
wlanConnPara.wlanConnectionMode =wlan_connection_mode_profile ;
wlanConnPara.strProfile =wifi;
wlanConnPara.pDot11Ssid = NULL;
wlanConnPara.dot11BssType = dot11_BSS_type_infrastructure;
wlanConnPara.pDesiredBssidList = NULL;
wlanConnPara.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK;
DWORD dwResult = 0;
dwResult = WlanConnect(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
&wlanConnPara ,NULL);
if(dwResult!=ERROR_SUCCESS)
{
return false;
}
}
else
{
return false;
}
}
//未配置wifi手动配置xml文件连接
else
{
/*wifi名称为中文时会连接不上
* ssid节点的<name>%1</name>直接传入中文会连接不上(ssid节点无<hex>%1</hex>属性时)(原因:可能是编码问题,但检查配置文件看不出来~)
* 解决方法:在ssid节点有<hex>%1</hex>属性时,会优先调用此属性进行连接,所以这里直接将wifi名称转为HEX
*/
std::string stdWifiName = wifiName.toStdString();
const std::string hex = "0123456789ABCDEF";
std::stringstream ss;
for (std::string::size_type i = 0; i < stdWifiName.size(); ++i)
ss << hex[(unsigned char)stdWifiName[i] >> 4] << hex[(unsigned char)stdWifiName[i] & 0xf];
QString hexWifiName = QString::fromStdString(ss.str());
wchar_t profile[1024] = {};
if(keyType == 0)//未配置未加密wifi
{
QString strProfile = STR_PROFILE_NoPossWord;
QRegExp regName("<name>.*</name>");
regName.setMinimal(true);
strProfile.replace(regName, QString("<name>%1</name>").arg(wifiName));
QRegExp regHex("<hex>.*</hex>");
regHex.setMinimal(true);
strProfile.replace(regHex, QString("<hex>%1</hex>").arg(hexWifiName));
std::wstring wlpstrstd = strProfile.toStdWString();
strProfile.toWCharArray(profile);
}
else//未配置加密wifi
{
QString strProfile = STR_PROFILE;
QRegExp regName("<name>.*</name>");
regName.setMinimal(true);
strProfile.replace(regName, QString("<name>%1</name>").arg(wifiName));
QRegExp regHex("<hex>.*</hex>");
regHex.setMinimal(true);
strProfile.replace(regHex, QString("<hex>%1</hex>").arg(hexWifiName));
QRegExp regPassword("<keyMaterial>.*</keyMaterial>");
regPassword.setMinimal(true);
strProfile.replace(regPassword, QString("<keyMaterial>%1</keyMaterial>").arg(passWord));
std::wstring wlpstrstd = strProfile.toStdWString();
strProfile.toWCharArray(profile);
}
DWORD dwReasonCode = 0;
DWORD dwResult = WlanSetProfile(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
0,
profile,
NULL,
TRUE,
NULL,
&dwReasonCode);
if(dwResult == ERROR_SUCCESS)
{
wchar_t wifi[MAX_PATH] = {};
wifiName.toWCharArray(wifi);
WLAN_CONNECTION_PARAMETERS wlanConnPara;
wlanConnPara.wlanConnectionMode =wlan_connection_mode_profile ;
wlanConnPara.strProfile =wifi;
wlanConnPara.pDot11Ssid = NULL;
wlanConnPara.dot11BssType = dot11_BSS_type_infrastructure;
wlanConnPara.pDesiredBssidList = NULL;
wlanConnPara.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK;
DWORD dwResult = 0;
dwResult = WlanConnect(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
&wlanConnPara ,NULL);
if(dwResult!=ERROR_SUCCESS)
{
return false;
}
}
else
{
return false;
}
}
return true;
}
5、连接失败处理(已配置过的WiFi)
可能原因:
1、密码改了
2、信号弱
3、其他~
这里全都当作密码改了处理的,直接从系统删掉该配置信息
void CWifiInfoManage::removeWifiInfo(QString wifiName)
{
std::wstring wlpstrstd = wifiName.toStdWString();
LPCWSTR str = wlpstrstd.c_str();
DWORD dwResult = WlanDeleteProfile(hClientHandle,
&pInterfaceList->InterfaceInfo[0].InterfaceGuid,
str,
NULL);
}
三、总结
功能可能比较鸡肋~
但在一些特使场景还是会有需求的。
————————————————————————————————————————————
这是QT WiFi连接的第一篇,共两篇。
下一篇是安卓的,这个用处大一点~
等有时间整理了再发出来