Windows下使用Wifi Native Api在应用程序内部控制wifi,官方文档链接https://docs.microsoft.com/zh-cn/windows/win32/nativewifi/native-wifi-api-sample,主要注意以下几点:
- 连接wifi使用wlan_connection_mode_profile时,如果被连接的wifi有配置文件,那么连接参数直接使用配置文件名称即可,如果没有配置文件,要先WlanSetProfile成功后才能创建连接,不同的加密方式有不同的配置文件样本,常用的WPA2样本WPA2-Personal Profile Sample
- 官方的配置文件样本有个坑,官方的xml demo里面命名空间是https,实际使用的是http才能WlanSetProfile成功,否则会报ERROR_BAD_PROFILE错误
- 连接wifi前需要断开当前已经连接的wifi
- WlanRegisterNotification的回调函数接收通知是系统全局的,也就是说在系统设置里面操作wifi也能通知回调,并且是异步回调
下面是根据官方文档和demo写的demo,结合Qt
WifiHelper.h
#pragma once
#include <Windows.h>
#include <wlanapi.h>
#include <objbase.h>
#include <wtypes.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "wlanapi.lib")
#include <qobject.h>
struct EntryInfo {
QString profile;
QString ssid;
bool connectable;
long signalQuality;//信号质量
int rssi;
bool securityEnabled;//启动网络安全
QString authAlgorithm;//认证算法
QString cipherAlgorithm;//加密算法
DWORD dwFlags;
QString status;//当前状态
//
DOT11_SSID dot11_ssid;
DOT11_BSS_TYPE dot11BssType;
DOT11_AUTH_ALGORITHM dot11DefaultAuthAlgorithm;
DOT11_CIPHER_ALGORITHM dot11DefaultCipherAlgorithm;
};
struct WifiInfo {
QString description;
GUID guid;
QString guidStr;
QString status;
QList<EntryInfo> entryList;
};
class WifiHelper : public QObject {
Q_OBJECT
public:
WifiHelper(QString profileTemplateStr, QObject* parent = nullptr);
~WifiHelper();
void loadWifiInfo();
void scanWifiList(int interfaceIndex);
void reloadWifiList(int interfaceIndex);
void connectWifi(int interfaceIndex, int entryInfoIndex, QString passwordIfNeed);
void disconnectWifi(int interfaceIndex);
signals:
void getNewInterfaceGuid(QString guidStr);
void interfaceListRefreshed(const QList<WifiInfo>& wifiList);
void wifiListRefreshed(const WifiInfo& wifiInfo);
void printErr(QString title, QString content);
private:
QList<WifiInfo> wifiList;
HANDLE hClient = NULL;
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
QString profileTemplateStr;
private:
void findActiveWireless(WifiInfo& wifiInfo);
void connectWifi(const GUID& interfaceGuid, const EntryInfo& entryInfo, QString password);
void disconnectWifi(const GUID& interfaceGuid);
bool setProfile(const GUID& interfaceGuid, const EntryInfo& entryInfo, QString password);
QString getProfileStr(const EntryInfo& entryInfo, QString password);
QString getProfileStr(const GUID& interfaceGuid, QString profileName);
private slots:
void reloadWifiListInMainThread(QString guidStr);
};
WifiHelper.cpp
#include "WifiHelper.h"
#include <qdebug.h>
void WlanNotificationCallback(PWLAN_NOTIFICATION_DATA Arg1, PVOID Arg2) {
if (Arg1 != NULL) {
switch (Arg1->NotificationSource) {
case WLAN_NOTIFICATION_SOURCE_ACM:
const auto wifi_list_refresh = [&] {
auto obj = static_cast<WifiHelper*>(Arg2);
if (obj != nullptr) {
WCHAR GuidString[39] = { 0 };
int iRet = StringFromGUID2(Arg1->InterfaceGuid, (LPOLESTR)&GuidString, sizeof(GuidString) / sizeof(*GuidString));
obj->getNewInterfaceGuid(QString::fromStdWString(GuidString));
}
};
switch (Arg1->NotificationCode) {
case wlan_notification_acm_connection_complete:
{
if (Arg1->dwDataSize < sizeof(WLAN_CONNECTION_NOTIFICATION_DATA)) {
break;
}
auto data = (PWLAN_CONNECTION_NOTIFICATION_DATA)Arg1->pData;
QString ssid = QByteArray((char*)data->dot11Ssid.ucSSID, data->dot11Ssid.uSSIDLength);
if (data->wlanReasonCode == WLAN_REASON_CODE_SUCCESS) {
qDebug() << QString("%1 connection successed!").arg(ssid);
wifi_list_refresh();
} else {
wchar_t reasonCodeStr[1024] = { 0 };
WlanReasonCodeToString(data->wlanReasonCode, 1024, reasonCodeStr, NULL);
auto obj = static_cast<WifiHelper*>(Arg2);
if (obj != nullptr) {
obj->printErr(QString("%1 connection failed!").arg(ssid), QString::fromWCharArray(reasonCodeStr));
}
}
}
break;
case wlan_notification_acm_scan_complete:
case wlan_notification_acm_disconnected:
wifi_list_refresh();
break;
default:
break;
}
break;
}
}
qDebug() << "notifycode = " << Arg1->NotificationCode;
}
WifiHelper::WifiHelper(QString profileTemplateStr, QObject* parent)
: QObject(parent)
, profileTemplateStr(profileTemplateStr)
, pIfList(NULL)
{
DWORD dwMaxClient = 2;
DWORD dwCurVersion = 0;
DWORD dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient);
Q_ASSERT(dwResult == ERROR_SUCCESS);
DWORD dwNotifSourcePre;
dwResult = WlanRegisterNotification(hClient, WLAN_NOTIFICATION_SOURCE_ACM, TRUE, (WLAN_NOTIFICATION_CALLBACK)WlanNotificationCallback, this, NULL, &dwNotifSourcePre);
connect(this, &WifiHelper::getNewInterfaceGuid, this, &WifiHelper::reloadWifiListInMainThread);
}
WifiHelper::~WifiHelper() {
if (pIfList != NULL) {
WlanFreeMemory(pIfList);
pIfList = NULL;
}
WlanCloseHandle(hClient, NULL);
}
void WifiHelper::loadWifiInfo() {
wifiList.clear();
if (pIfList != NULL) {
WlanFreeMemory(pIfList);
pIfList = NULL;
}
DWORD dwResult = WlanEnumInterfaces(hClient, NULL, &pIfList);
Q_ASSERT(dwResult == ERROR_SUCCESS);
for (int i = 0; i < (int)pIfList->dwNumberOfItems; i++) {
PWLAN_INTERFACE_INFO pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
WifiInfo wifiInfo;
wifiInfo.description = QString::fromStdWString(pIfInfo->strInterfaceDescription);
WCHAR GuidString[39] = { 0 };
int iRet = StringFromGUID2(pIfInfo->InterfaceGuid, (LPOLESTR)&GuidString, sizeof(GuidString) / sizeof(*GuidString));
wifiInfo.guid = pIfInfo->InterfaceGuid;
wifiInfo.guidStr = iRet == 0 ? "StringFromGUID2 failed" : QString::fromStdWString(GuidString);
switch (pIfInfo->isState) {
case wlan_interface_state_not_ready:
wifiInfo.status = "Not ready";
break;
case wlan_interface_state_connected:
wifiInfo.status = "Connected";
break;
case wlan_interface_state_ad_hoc_network_formed:
wifiInfo.status = "First node in a ad hoc network";
break;
case wlan_interface_state_disconnecting:
wifiInfo.status = "Disconnecting";
break;
case wlan_interface_state_disconnected:
wifiInfo.status = "Not connected";
break;
case wlan_interface_state_associating:
wifiInfo.status = "Attempting to associate with a network";
break;
case wlan_interface_state_discovering:
wifiInfo.status = "Auto configuration is discovering settings for the network";
break;
case wlan_interface_state_authenticating:
wifiInfo.status = "In process of authenticating";
break;
default:
wifiInfo.status = "Unknown state " + QString::number(pIfInfo->isState);
break;
}
wifiList << wifiInfo;
}
findActiveWireless(wifiList[0]);
wifiListRefreshed(wifiList.at(0));
interfaceListRefreshed(wifiList);
}
void WifiHelper::scanWifiList(int interfaceIndex) {
WLAN_RAW_DATA wlanRawData = { 0 };
DWORD dwResult = WlanScan(hClient, &wifiList[interfaceIndex].guid, NULL, &wlanRawData, NULL);
Q_ASSERT(dwResult == ERROR_SUCCESS);
}
void WifiHelper::reloadWifiList(int interfaceIndex) {
findActiveWireless(wifiList[interfaceIndex]);
wifiListRefreshed(wifiList.at(interfaceIndex));
}
void WifiHelper::connectWifi(int interfaceIndex, int entryInfoIndex, QString passwordIfNeed) {
connectWifi(wifiList.at(interfaceIndex).guid,
wifiList.at(interfaceIndex).entryList.at(entryInfoIndex),
passwordIfNeed
);
}
void WifiHelper::disconnectWifi(int interfaceIndex) {
disconnectWifi(wifiList.at(interfaceIndex).guid);
}
void WifiHelper::reloadWifiListInMainThread(QString guidStr) {
for (int i = 0; i < wifiList.size(); i++) {
if (wifiList.at(i).guidStr == guidStr) {
findActiveWireless(wifiList[i]);
wifiListRefreshed(wifiList.at(i));
break;
}
}
}
void WifiHelper::connectWifi(const GUID& interfaceGuid, const EntryInfo& entryInfo, QString password) {
if (entryInfo.dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED) {
return;
}
disconnectWifi(interfaceGuid);
wchar_t profile[WLAN_MAX_NAME_LENGTH] = {};
if (!(entryInfo.dwFlags & WLAN_AVAILABLE_NETWORK_HAS_PROFILE)) {
setProfile(interfaceGuid, entryInfo, password);
}
entryInfo.ssid.toWCharArray(profile);
auto fstr = getProfileStr(interfaceGuid, entryInfo.ssid);
WLAN_CONNECTION_PARAMETERS parameter;
parameter.strProfile = profile;
parameter.pDot11Ssid = NULL;
parameter.pDesiredBssidList = NULL;
parameter.wlanConnectionMode = wlan_connection_mode_profile;
parameter.dot11BssType = entryInfo.dot11BssType;
parameter.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK;
DWORD dwResult = WlanConnect(hClient, &interfaceGuid, ¶meter, NULL);
if (dwResult != ERROR_SUCCESS) {
if (dwResult == ERROR_INVALID_PARAMETER) {
printErr(u8"无法连接", u8"不支持的连接!修改profile模板!");
} else {
Q_ASSERT(dwResult == ERROR_SUCCESS);
}
}
}
void WifiHelper::disconnectWifi(const GUID & interfaceGuid) {
DWORD dwResult = WlanDisconnect(hClient, &interfaceGuid, NULL);
Q_ASSERT(dwResult == ERROR_SUCCESS);
}
bool WifiHelper::setProfile(const GUID& interfaceGuid, const EntryInfo& entryInfo, QString password) {
wchar_t profile[2048] = { 0 };
QString profileStr = getProfileStr(entryInfo, password);
profileStr.toWCharArray(profile);
DWORD dwReasonCode;
wchar_t reasonCodeStr[1024] = { 0 };
DWORD dwResult = WlanSetProfile(hClient, &interfaceGuid, 0, profile, NULL, TRUE, NULL, &dwReasonCode);
if (dwResult != ERROR_SUCCESS) {
WlanReasonCodeToString(dwReasonCode, 1024, reasonCodeStr, NULL);
}
return dwResult == ERROR_SUCCESS;
}
QString WifiHelper::getProfileStr(const EntryInfo& entryInfo, QString password) {
QString templateContent = profileTemplateStr;
templateContent.replace("{ssid}", entryInfo.ssid);
templateContent.replace("{password}", password);
switch (entryInfo.dot11DefaultAuthAlgorithm) {
case DOT11_AUTH_ALGO_80211_OPEN:
templateContent.replace("{authentication}", "open");
break;
case DOT11_AUTH_ALGO_80211_SHARED_KEY:
templateContent.replace("{authentication}", "shared");
break;
case DOT11_AUTH_ALGO_WPA:
templateContent.replace("{authentication}", "WPA");
break;
case DOT11_AUTH_ALGO_WPA_PSK:
templateContent.replace("{authentication}", "WPAPSK");
break;
case DOT11_AUTH_ALGO_WPA_NONE:
templateContent.replace("{authentication}", "none");
break;
case DOT11_AUTH_ALGO_RSNA:
templateContent.replace("{authentication}", "WPA2");
break;
case DOT11_AUTH_ALGO_RSNA_PSK:
templateContent.replace("{authentication}", "WPA2PSK");
break;
default:
break;
}
switch (entryInfo.dot11DefaultCipherAlgorithm) {
case DOT11_CIPHER_ALGO_NONE:
templateContent.replace("{encryption}", "none");
break;
case DOT11_CIPHER_ALGO_WEP40:
templateContent.replace("{encryption}", "WEP");
break;
case DOT11_CIPHER_ALGO_TKIP:
templateContent.replace("{encryption}", "TKIP");
break;
case DOT11_CIPHER_ALGO_CCMP:
templateContent.replace("{encryption}", "AES");
break;
case DOT11_CIPHER_ALGO_WEP104:
templateContent.replace("{encryption}", "WEP");
break;
case DOT11_CIPHER_ALGO_WEP:
templateContent.replace("{encryption}", "WEP");
break;
default:
break;
}
return templateContent;
}
QString WifiHelper::getProfileStr(const GUID & interfaceGuid, QString profileName) {
wchar_t profile[WLAN_MAX_NAME_LENGTH] = {};
profileName.toWCharArray(profile);
DWORD dwFlags, dwGrantedAccess;
LPWSTR pProfileXml = NULL;
DWORD dwResult = WlanGetProfile(hClient, &interfaceGuid, profile, NULL, &pProfileXml, &dwFlags, &dwGrantedAccess);
if (dwResult == ERROR_SUCCESS) {
return QString::fromWCharArray(pProfileXml);
}
return QString();
}
void WifiHelper::findActiveWireless(WifiInfo& wifiInfo) {
PWLAN_AVAILABLE_NETWORK_LIST pBssList = NULL;
PWLAN_AVAILABLE_NETWORK pBssEntry = NULL;
wifiInfo.entryList.clear();
DWORD dwResult = WlanGetAvailableNetworkList(hClient, &wifiInfo.guid, 0, NULL, &pBssList);
if (dwResult == ERROR_SUCCESS) {
QHash<QString, EntryInfo> entryList;
for (int j = 0; j < pBssList->dwNumberOfItems; j++) {
pBssEntry = (WLAN_AVAILABLE_NETWORK *)& pBssList->Network[j];
EntryInfo entryInfo;
entryInfo.profile = QString::fromWCharArray(pBssEntry->strProfileName);
entryInfo.dot11_ssid = pBssEntry->dot11Ssid;
entryInfo.ssid = QByteArray((char*)pBssEntry->dot11Ssid.ucSSID, pBssEntry->dot11Ssid.uSSIDLength);
entryInfo.connectable = pBssEntry->bNetworkConnectable;
entryInfo.signalQuality = pBssEntry->wlanSignalQuality;
int iRSSI = 0;
if (pBssEntry->wlanSignalQuality == 0)
iRSSI = -100;
else if (pBssEntry->wlanSignalQuality == 100)
iRSSI = -50;
else
iRSSI = -100 + (pBssEntry->wlanSignalQuality / 2);
entryInfo.rssi = iRSSI;
entryInfo.dot11BssType = pBssEntry->dot11BssType;
entryInfo.securityEnabled = pBssEntry->bSecurityEnabled;
entryInfo.dot11DefaultAuthAlgorithm = pBssEntry->dot11DefaultAuthAlgorithm;
switch (pBssEntry->dot11DefaultAuthAlgorithm) {
case DOT11_AUTH_ALGO_80211_OPEN:
entryInfo.authAlgorithm = "802.11 Open";
break;
case DOT11_AUTH_ALGO_80211_SHARED_KEY:
entryInfo.authAlgorithm = "802.11 Shared";
break;
case DOT11_AUTH_ALGO_WPA:
entryInfo.authAlgorithm = "WPA";
break;
case DOT11_AUTH_ALGO_WPA_PSK:
entryInfo.authAlgorithm = "WPA-PSK";
break;
case DOT11_AUTH_ALGO_WPA_NONE:
entryInfo.authAlgorithm = "WPA-None";
break;
case DOT11_AUTH_ALGO_RSNA:
entryInfo.authAlgorithm = "RSNA";
break;
case DOT11_AUTH_ALGO_RSNA_PSK:
entryInfo.authAlgorithm = "RSNA with PSK";
break;
default:
entryInfo.authAlgorithm = "Other";
break;
}
entryInfo.dot11DefaultCipherAlgorithm = pBssEntry->dot11DefaultCipherAlgorithm;
switch (pBssEntry->dot11DefaultCipherAlgorithm) {
case DOT11_CIPHER_ALGO_NONE:
entryInfo.cipherAlgorithm = "None";
break;
case DOT11_CIPHER_ALGO_WEP40:
entryInfo.cipherAlgorithm = "WEP-40";
break;
case DOT11_CIPHER_ALGO_TKIP:
entryInfo.cipherAlgorithm = "TKIP";
break;
case DOT11_CIPHER_ALGO_CCMP:
entryInfo.cipherAlgorithm = "CCMP";
break;
case DOT11_CIPHER_ALGO_WEP104:
entryInfo.cipherAlgorithm = "WEP-104";
break;
case DOT11_CIPHER_ALGO_WEP:
entryInfo.cipherAlgorithm = "WEP";
break;
default:
entryInfo.cipherAlgorithm = "Other";
break;
}
entryInfo.dwFlags = pBssEntry->dwFlags;
if (pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED) {
entryInfo.status = u8"已连接";
} else if (pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_HAS_PROFILE) {
entryInfo.status = u8"已保存";
} else {
entryInfo.status = u8"未连接";
if (entryList.contains(entryInfo.ssid)) {
continue;
}
}
entryList.insert(entryInfo.ssid, entryInfo);
}
for (EntryInfo info : entryList.values()) {
wifiInfo.entryList << info;
}
} else {
qDebug() << "WlanGetAvailableNetworkList failed with error: " << dwResult;
}
if (pBssList != NULL) {
WlanFreeMemory(pBssList);
pBssList = NULL;
}
}
上面是工具类,下面使用一个widget显示和交互
//WifiConnectTest.h
#pragma once
#include <QtWidgets/QWidget>
#include "ui_WifiConnectTest.h"
#include <qstandarditemmodel.h>
#include "WifiHelper.h"
class WifiConnectTest : public QWidget
{
Q_OBJECT
public:
WifiConnectTest(QWidget *parent = Q_NULLPTR);
~WifiConnectTest();
private:
Ui::WifiConnectTestClass ui;
WifiHelper* wifiHelper;
void addTableHeader(QStandardItemModel* model);
private slots:
void loadWifiEntryList(const WifiInfo& wifiInfo);
void loadWifiInterfaceList(const QList<WifiInfo>& wifiList);
void showErrMessageBox(QString title, QString content);
};
WifiConnectTest.cpp
#include "WifiConnectTest.h"
#include <qfile.h>
#include <qdebug.h>
#include <qmessagebox.h>
WifiConnectTest::WifiConnectTest(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
auto model = new QStandardItemModel(this);
ui.tableView->setModel(model);
ui.tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui.tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui.tableView->setSelectionMode(QAbstractItemView::SingleSelection);
addTableHeader(model);
connect(ui.scan_adapter, &QPushButton::clicked, [&]() {
wifiHelper->loadWifiInfo();
});
connect(ui.scan_wifi, &QPushButton::clicked, [&]() {
int currentAdapterIndex = ui.interfaceDescription->currentIndex();
if (currentAdapterIndex >= 0) {
wifiHelper->scanWifiList(currentAdapterIndex);
}
});
connect(ui.interfaceDescription, qOverload<int>(&QComboBox::currentIndexChanged), [&](int index) {
if (index >= 0) {
wifiHelper->reloadWifiList(index);
}
});
connect(ui.connect_tag, &QPushButton::clicked, [&]() {
int interfaceIndex = ui.interfaceDescription->currentIndex();
if (interfaceIndex >= 0) {
int row = ui.tableView->currentIndex().row();
if (row >= 0) {
wifiHelper->connectWifi(interfaceIndex, row,
ui.tableView->model()->index(row, ui.tableView->model()->columnCount() - 1).data(Qt::DisplayRole).toString());
}
}
});
connect(ui.disconnect_tag, &QPushButton::clicked, [&]() {
int interfaceIndex = ui.interfaceDescription->currentIndex();
if (interfaceIndex >= 0) {
wifiHelper->disconnectWifi(interfaceIndex);
}
});
QFile file(":/WifiConnectTest/Resources/profile_template.xml");
file.open(QIODevice::ReadOnly);
QString templateContent = file.readAll();
file.close();
wifiHelper = new WifiHelper(templateContent, this);
connect(wifiHelper, &WifiHelper::interfaceListRefreshed, this, &WifiConnectTest::loadWifiInterfaceList);
connect(wifiHelper, &WifiHelper::wifiListRefreshed, this, &WifiConnectTest::loadWifiEntryList);
connect(wifiHelper, &WifiHelper::printErr, this, &WifiConnectTest::showErrMessageBox);
}
WifiConnectTest::~WifiConnectTest() {
}
void WifiConnectTest::addTableHeader(QStandardItemModel * model) {
QStringList titles;
titles << "ssid" << u8"是否可连接" << u8"信号质量" << "rssi" << u8"启动网络安全" << u8"认证算法" << u8"加密算法" << u8"状态" << u8"密码";
model->setColumnCount(titles.size());
for (int i = 0; i < titles.size(); i++) {
model->setHeaderData(i, Qt::Horizontal, titles[i], Qt::DisplayRole);
}
}
void WifiConnectTest::loadWifiEntryList(const WifiInfo& wifiInfo) {
auto tabModel = static_cast<QStandardItemModel*>(ui.tableView->model());
tabModel->clear();
addTableHeader(tabModel);
ui.interfaceStatus->setText(wifiInfo.status);
for (int i = 0; i < wifiInfo.entryList.size(); i++) {
auto entryInfo = wifiInfo.entryList.at(i);
QStandardItem* item;
int col = 0;
item = new QStandardItem(entryInfo.ssid);
tabModel->setItem(i, col++, item);
item = new QStandardItem(entryInfo.connectable ? "yes" : "no");
tabModel->setItem(i, col++, item);
item = new QStandardItem(QString::number(entryInfo.signalQuality));
tabModel->setItem(i, col++, item);
item = new QStandardItem(QString("%1 dBm").arg(entryInfo.rssi));
tabModel->setItem(i, col++, item);
item = new QStandardItem(entryInfo.securityEnabled ? "yes" : "no");
tabModel->setItem(i, col++, item);
item = new QStandardItem(entryInfo.authAlgorithm);
tabModel->setItem(i, col++, item);
item = new QStandardItem(entryInfo.cipherAlgorithm);
tabModel->setItem(i, col++, item);
item = new QStandardItem(entryInfo.status);
tabModel->setItem(i, col++, item);
item = new QStandardItem;
tabModel->setItem(i, col++, item);
}
}
void WifiConnectTest::loadWifiInterfaceList(const QList<WifiInfo>& wifiList) {
ui.interfaceDescription->clear();
for (const auto& i : wifiList) {
ui.interfaceDescription->addItem(i.description);
}
}
void WifiConnectTest::showErrMessageBox(QString title, QString content) {
QMessageBox::warning(NULL, title, content);
}
界面: