C++程序中使用Windows系统Native Wifi API的基本教程
Windows应用想要实现连接wifi,监听wifi信号,断开连接等功能,用NativeWifiAPI是个不错的选择。
打开MSDN,搜索NativeWifiApi,找到NativeWifi页。在这里。
信息量很大,如果像我着急实现上述功能,看海量的文档有些来不及。如果直接给我例子,在运行中调试,阅读代码,效率会更高。
但是,我并没有成功。首先,Sample在SDK中,参见这里。我下载几次都失败了,最后放弃这条路。后来同事给了我一份Sample,我不敢确定是否就是这个,但是代码写的也是很晦涩。我的初衷是简单的使用这些API的例子。
看来还是自己动手吧。看相关API,如果不懂,就找有经验人的例子。
几经周折,终于实现我的需求。让我慢慢道来。
1.获得可用AP列表
参见WlanGetAvailableNetworkList的官方文档,下面有例子。
DWORDWINAPIWlanGetAvailableNetworkList( _In_HANDLEhClientHandle, _In_constGUID*pInterfaceGuid, _In_DWORDdwFlags, _Reserved_PVOIDpReserved, _Out_PWLAN_AVAILABLE_NETWORK_LIST*ppAvailableNetworkList );
由可用列表便可以找到当前哪个AP正在连接,并显示信号强度。
2.监听当前连接
在获得可用AP列表的基础上,遍历当前AP,看谁正在连接,并取得它的信号。代码片段如下:
boolisConnect=false;
intnumberOfItems=pWLAN_AVAILABLE_NETWORK_LIST->dwNumberOfItems;
for(inti=0;i<=numberOfItems;i++)
{
WLAN_AVAILABLE_NETWORKwlanAN=pWLAN_AVAILABLE_NETWORK_LIST->Network[i];
if(wlanAN.dwFlags&WLAN_AVAILABLE_NETWORK_CONNECTED)
{
Wprintf(WLANsignalis%s:%d\n",wlanAN.strProfileName,wlanAN.wlanSignalQuality);
isConnect=true;
}
}
if(!isConnect){
wprintf("Wifiisdisconnected!\n");}
3.断开连接
如果wifi处于连接状态,将其断开。WlanDisconnect还是容易使用的。原型如下:
DWORDWINAPIWlanDisconnect( _In_HANDLEhClientHandle, _In_constGUID*pInterfaceGuid, _Reserved_PVOIDpReserved );
代码演示在后面。
4.连接一个有profile的AP(已保存过密码)
这是本文的重点。
虽然连接函数WlanConnect原型很简单:
DWORDWINAPIWlanConnect( _In_HANDLEhClientHandle, _In_constGUID*pInterfaceGuid, _In_constPWLAN_CONNECTION_PARAMETERSpConnectionParameters, _Reserved_PVOIDpReserved );但参数PWLAN_CONNECTION_PARAMETERS却是很复杂,只要有一个配错,连接就会失败。
还好我的需求还是蛮简单的,只要连接已有的profile的AP。那么我的工作就会有针对性的开展。挫折了好多天,每次都连接失败,原因是ERROR_INVALID_PARAMETER。
就在今天,我终于成功了。真是会者不难,难者不会啊。
看看连接参数的结构体:
typedefstruct_WLAN_CONNECTION_PARAMETERS{
WLAN_CONNECTION_MODEwlanConnectionMode;
LPCWSTRstrProfile;
PDOT11_SSIDpDot11Ssid;
PDOT11_BSSID_LISTpDesiredBssidList;
DOT11_BSS_TYPEdot11BssType;
DWORDdwFlags;
}WLAN_CONNECTION_PARAMETERS,*PWLAN_CONNECTION_PARAMETERS;
为了实现我的要求,可以这样赋值:
wlanConnectionMode这里设成wlan_connection_mode_profile;
strProfile写上你要连接ap的名称(通常是profile名称);
pDot11Ssid用不上,设置NULL;
pDesiredBssidList同样置成NULL;
dot11BssType我给设成dot11_BSS_type_infrastructure(基础设施?);
dwFlags设置为WLAN_CONNECTION_HIDDEN_NETWORK。
确实是工作了,strProfile如何获取呢?参见监听连接信号中对可用AP列表中第一个profile的获取。
完整代码如下:
//
#include"stdafx.h"
#include<windows.h>
#include<wlanapi.h>
#include<objbase.h>
#include<wtypes.h>
#include<string>
#include<stdio.h>
#include<stdlib.h>
//NeedtolinkwithWlanapi.libandOle32.lib
#pragmacomment(lib,"wlanapi.lib")
#pragmacomment(lib,"ole32.lib")
usingnamespacestd;
intlistenStatus()
{
HANDLEhClient=NULL;
DWORDdwMaxClient=2;
DWORDdwCurVersion=0;
DWORDdwResult=0;
DWORDdwRetVal=0;
intiRet=0;
WCHARGuidString[39]={0};
//ListenthestatusoftheAPyouconnected.
while(1){
Sleep(5000);
PWLAN_INTERFACE_INFO_LISTpIfList=NULL;//Ithinkwlaninterfacemeansnetworkcard
PWLAN_INTERFACE_INFOpIfInfo=NULL;
DWORDdwFlags=0;
dwResult=WlanOpenHandle(dwMaxClient,NULL,&dwCurVersion,&hClient);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanOpenHandlefailedwitherror:%u\n",dwResult);
return1;
}
dwResult=WlanEnumInterfaces(hClient,NULL,&pIfList);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanEnumInterfacesfailedwitherror:%u\n",dwResult);
return1;
}else{
wprintf(L"WLAN_INTERFACE_INFO_LISTforthissystem\n");
wprintf(L"NumEntries:%lu\n",pIfList->dwNumberOfItems);
wprintf(L"CurrentIndex:%lu\n\n",pIfList->dwIndex);
inti;
for(i=0;i<(int)pIfList->dwNumberOfItems;i++){
pIfInfo=(WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
wprintf(L"InterfaceIndex[%u]:\t%lu\n",i,i);
iRet=StringFromGUID2(pIfInfo->InterfaceGuid,(LPOLESTR)&GuidString,
sizeof(GuidString)/sizeof(*GuidString));
if(iRet==0)
wprintf(L"StringFromGUID2failed\n");
else{
wprintf(L"InterfaceGUID[%d]:%ws\n",i,GuidString);
}
wprintf(L"InterfaceDescription[%d]:%ws",i,
pIfInfo->strInterfaceDescription);
wprintf(L"\n");
wprintf(L"InterfaceState[%d]:\t",i);
switch(pIfInfo->isState){
casewlan_interface_state_not_ready:
wprintf(L"Notready\n");
break;
casewlan_interface_state_connected:
wprintf(L"Connected\n");
break;
casewlan_interface_state_ad_hoc_network_formed:
wprintf(L"Firstnodeinaadhocnetwork\n");
break;
casewlan_interface_state_disconnecting:
wprintf(L"Disconnecting\n");
break;
casewlan_interface_state_disconnected:
wprintf(L"Notconnected\n");
break;
casewlan_interface_state_associating:
wprintf(L"Attemptingtoassociatewithanetwork\n");
break;
casewlan_interface_state_discovering:
wprintf(L"Autoconfigurationisdiscoveringsettingsforthenetwork\n");
break;
casewlan_interface_state_authenticating:
wprintf(L"Inprocessofauthenticating\n");
break;
default:
wprintf(L"Unknownstate%ld\n",pIfInfo->isState);
break;
}
}
}
}
}
int_tmain(intargc,_TCHAR*argv[])
{
HANDLEhClient=NULL;
DWORDdwMaxClient=2;
DWORDdwCurVersion=0;
DWORDdwResult=0;
DWORDdwRetVal=0;
intiRet=0;
/*variablesusedforWlanEnumInterfaces*/
PWLAN_INTERFACE_INFO_LISTpIfList=NULL;
PWLAN_INTERFACE_INFOpIfInfo=NULL;
LPCWSTRpProfileName=NULL;
LPWSTRpProfileXml=NULL;
DWORDdwFlags=0;
pProfileName=argv[1];
wprintf(L"Informationforprofile:%ws\n\n",pProfileName);
dwResult=WlanOpenHandle(dwMaxClient,NULL,&dwCurVersion,&hClient);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanOpenHandlefailedwitherror:%u\n",dwResult);
return1;
}
dwResult=WlanEnumInterfaces(hClient,NULL,&pIfList);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanEnumInterfacesfailedwitherror:%u\n",dwResult);
return1;
}else{
dwResult=WlanDisconnect(hClient,&pIfList->InterfaceInfo[0].InterfaceGuid,NULL);//DISCONNECTFIRST
if(dwResult!=ERROR_SUCCESS)
{
printf("WlanDisconnectfailedwitherror:%u\n",dwResult);
return-1;
}
PWLAN_AVAILABLE_NETWORK_LISTpWLAN_AVAILABLE_NETWORK_LIST=NULL;
dwResult=WlanGetAvailableNetworkList(hClient,&pIfList->InterfaceInfo[0].InterfaceGuid,
WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_MANUAL_HIDDEN_PROFILES,
NULL,&pWLAN_AVAILABLE_NETWORK_LIST);
if(dwResult!=ERROR_SUCCESS)
{
printf("WlanGetAvailableNetworkListfailedwitherror:%u\n",dwResult);
WlanFreeMemory(pWLAN_AVAILABLE_NETWORK_LIST);
return-1;
}
WLAN_AVAILABLE_NETWORKwlanAN=pWLAN_AVAILABLE_NETWORK_LIST->Network[0];//PLEASECHECKTHISYOURSELF
if(pProfileName==NULL)
pProfileName=wlanAN.strProfileName;
WLAN_CONNECTION_PARAMETERSwlanConnPara;
wlanConnPara.wlanConnectionMode=wlan_connection_mode_profile;//YES,WECONNECTAPVIATHEPROFILE
wlanConnPara.strProfile=pProfileName;//settheprofilename
wlanConnPara.pDot11Ssid=NULL;//SETSSIDNULL
wlanConnPara.dot11BssType=dot11_BSS_type_infrastructure;//dot11_BSS_type_any,Idonotneeditthistime.
wlanConnPara.pDesiredBssidList=NULL;//thedesiredBSSIDlistisempty
wlanConnPara.dwFlags=WLAN_CONNECTION_HIDDEN_NETWORK;//itworksonmyWIN7\8
dwResult=WlanConnect(hClient,&pIfList->InterfaceInfo[0].InterfaceGuid,&wlanConnPara,NULL);
if(dwResult==ERROR_SUCCESS)
{
printf("WlanConnectsuccess!\n");
}
else
{
printf("WlanConnectfailederris%d\n",dwResult);
}
}
listenStatus();//LISTENTHESTATUS
if(pProfileXml!=NULL){
WlanFreeMemory(pProfileXml);
pProfileXml=NULL;
}
if(pIfList!=NULL){
WlanFreeMemory(pIfList);
pIfList=NULL;
}
returndwRetVal;
}
5.打开网络设置界面
遇到以前没有连接过的AP,需要输入密码,那么,直接打开配置界面让用户自己来搞吧。
ShellExecute(
NULL,
L"open",
L"shell:::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{38a98528-6cbf-4ca9-8dc0-b1e1d10f7b1b}",
NULL,
NULL,
SW_SHOWNORMAL);
6.RSSI
当屏幕上打印出“WlanConnectsuccess!”的时候,别提多高兴了。
就像爱迪生试验灯丝一下,在无数次失败后,终于找到了一种材料可以胜任灯丝的工作。这种喜悦真的令人振奋,往日的阴霾和不爽终于一扫而光。
其实我也尝试过WlanGetProfile和WlanSetProfile,虽然有时结果是能够连上指定AP,但是函数返回结果却总是ERROR_INVALID_PARAMETER。
网上的例子,很多都是抄来抄去的,写的不明不白,虽然有过帮助,但是也有些误导。
今天自己成功的连接到指定AP了(用命令行运行我的例子,输入参数profilename),我一定要把它发表出来,让其他人有个参考。
我认为这是一件诚意的作品,在此也谢谢给过我帮助的朋友。
最后说一下获得的信号。标准信号RSSI是负值,而这里获得的信号都是正值(0~100),在有些需要RSSI的地方,我们需要转换一下:
if(pBssEntry->wlanSignalQuality==0) iRSSI=-100; elseif(pBssEntry->wlanSignalQuality==100) iRSSI=-50; else iRSSI=-100+(pBssEntry->wlanSignalQuality/2); wprintf(L"SignalQuality[%u]:\t%u(RSSI:%idBm)\n",j, pBssEntry->wlanSignalQuality,iRSSI);
7.Wifion与wifioff
下面要说的是在软件层面控制无线网卡的开和关。
问题听起来简单,调查起来复杂,但解决起来却也简单。关键函数便是Nativewifiapi中的WlanSetInterface。其实这个API功能也是非
常强大的,我只用到其中控制wifiradiostate的功能。官网文档在此。
函数原型:
DWORDWINAPIWlanSetInterface( _In_HANDLEhClientHandle, _In_constGUID*pInterfaceGuid, _In_WLAN_INTF_OPCODEOpCode, _In_DWORDdwDataSize, _In_constPVOIDpData, _Reserved_PVOIDpReserved );
重点说一下三个参数:
(1)OpCode,指定要设置的参数。我们选择wlan_intf_opcode_radio_state
(2)DwDataSize,pData的size。传入时用sizeof得到。
(3)pData,radiostate对应的data是WLAN_PHY_RADIO_STATE。
看看这个state结构体:
typedefstruct_WLAN_PHY_RADIO_STATE{
DWORDdwPhyIndex;
DOT11_RADIO_STATEdot11SoftwareRadioState;
DOT11_RADIO_STATEdot11HardwareRadioState;
}WLAN_PHY_RADIO_STATE,*PWLAN_PHY_RADIO_STATE;
Index设为0.
State设置如下:
typedefenum_DOT11_RADIO_STATE{
dot11_radio_state_unknown,
dot11_radio_state_on,
dot11_radio_state_off
}DOT11_RADIO_STATE,*PDOT11_RADIO_STATE;
与前几个API(比如wlanconnect)相比,这个函数的使用简单多了。全部源码如下:
//ManageWirelessNetwork.cpp:Definestheentrypointfortheconsoleapplication.
//
#include"stdafx.h"
#include<stdio.h>
#include<windows.h>
#include<shellapi.h>
#include<wlanapi.h>
//Needtolinkwithshell32.lib
#pragmacomment(lib,"shell32.lib")
#pragmacomment(lib,"wlanapi.lib")
int_tmain(intargc,_TCHAR*argv[])
{
DWORDdwResult=0;
DWORDdwMaxClient=2;
DWORDdwCurVersion=0;
HANDLEhClient=NULL;
PWLAN_INTERFACE_INFO_LISTpIfList=NULL;
PWLAN_INTERFACE_INFOpIfInfo=NULL;
dwResult=WlanOpenHandle(dwMaxClient,NULL,&dwCurVersion,&hClient);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanOpenHandlefailedwitherror:%u\n",dwResult);
returnfalse;
}
dwResult=WlanEnumInterfaces(hClient,NULL,&pIfList);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanEnumInterfacesfailedwitherror:%u\n",dwResult);
returnfalse;
}
WLAN_PHY_RADIO_STATEstate;
state.dwPhyIndex=0;
state.dot11SoftwareRadioState=dot11_radio_state_on;
PVOIDpData=&state;
dwResult=WlanSetInterface(hClient,&pIfList->InterfaceInfo[0].InterfaceGuid,
wlan_intf_opcode_radio_state,sizeof(WLAN_PHY_RADIO_STATE),pData,NULL);
if(dwResult==ERROR_SUCCESS)
{
wprintf(L"setstatesuccess!\n");
}
else
{
wprintf(L"setstatefailed!erris%d\n",dwResult);
}
return0;
}
8.GOTO在释放资源时的作用
GOTO语句有着很臭的名声,我们的老师经常教导我们说,不要轻易使用它。
C++跳转语句有三个:goto、break和continue。它们只是工具,我觉得问题不能归咎于工具,问题在于人。
就像指针一样,goto这个无条件跳转语句力量还是很强大的,如果滥用,出现问题很难排查。
但有些时候goto确实是不二选择,例如我遇到的,在函数中有多个出口,而每个出口都遇到释放资源的时候,与其都把释放语句不厌其烦的写一遍,
不如一个goto语句来的干脆利落。
下面的例子取自上一篇NativeWifiAPI文章,由于我们的程序经常控制的wifi的on和off,必须注意释放资源。就拿WlanOpenHandle来说,
如果不注意对称WlanCloseHandler,程序几次运行后报错:ERROR_REMOTE_SESSION_LIMIT_EXCEEDED
官网解释为:Toomanyhandleshavebeenissuedbytheserver.
所以我们会在每个API调用后,确认返回值,如果错误,程序将不再继续向下运行,return之前,我们必须释放资源。当出口很多时,我们要写很多同样的代码,
很烦躁,难读,代码急速膨胀。但使用goto后,问题便轻松了许多,请看简单例子:
//ManageWirelessNetwork.cpp:Definestheentrypointfortheconsoleapplication.
//
#include"stdafx.h"
#include<stdio.h>
#include<windows.h>
#include<shellapi.h>
#include<wlanapi.h>
//Needtolinkwithshell32.lib
#pragmacomment(lib,"shell32.lib")
#pragmacomment(lib,"wlanapi.lib")
int_tmain(intargc,_TCHAR*argv[])
{
DWORDdwResult=0;
DWORDdwMaxClient=2;
DWORDdwCurVersion=0;
HANDLEhClient=NULL;
PWLAN_INTERFACE_INFO_LISTpIfList=NULL;
PWLAN_INTERFACE_INFOpIfInfo=NULL;
dwResult=WlanOpenHandle(dwMaxClient,NULL,&dwCurVersion,&hClient);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanOpenHandlefailedwitherror:%u\n",dwResult);
returnfalse;
}
dwResult=WlanEnumInterfaces(hClient,NULL,&pIfList);
if(dwResult!=ERROR_SUCCESS){
wprintf(L"WlanEnumInterfacesfailedwitherror:%u\n",dwResult);
gotoRELEASE_RESOURCE;
}
WLAN_PHY_RADIO_STATEstate;
state.dwPhyIndex=0;
state.dot11SoftwareRadioState=dot11_radio_state_on;//offheretoo.
PVOIDpData=&state;
dwResult=WlanSetInterface(hClient,&pIfList->InterfaceInfo[0].InterfaceGuid,
wlan_intf_opcode_radio_state,sizeof(WLAN_PHY_RADIO_STATE),pData,NULL);
if(dwResult==ERROR_SUCCESS)
{
wprintf(L"setstatesuccess!\n");
}
else
{
wprintf(L"setstatefailed!erris%d\n",dwResult);
}
RELEASE_RESOURCE:
if(hClient)
{
WlanCloseHandle(hClient,NULL);
hClient=NULL;
}
if(pIfList)
{
WlanFreeMemory(pIfList);
pIfList=NULL;
}
if(pIfInfo)
{
WlanFreeMemory(pIfInfo);
pIfInfo=NULL;
}
return0;
}
最后,goto还会用来跳出多重循环。但需要注意的是,只能从内层跳到外层,不可逆操作。
后记:
其实几个月前就要实现windows上的wifion和off,问了许多人,发了许多帖子,最后都不了了之。之后的日子里也发生了许多事。国内的
搜索无果,加上google的无法使用,都对调查增加了些许难度。我们把重点先放到了nativewifiapi的几个方法,见上一篇玩转文章。但
那并不是我想要的。
原以为windows也会想android一样,普通应用没有权限来控制wifi的开关呢,结果并不是这样。这也宣告了之前我的判断失误。
直到今天,通过Bing发现了几条线索。那是通过C#调用nativewifiapi的问题,里面提及了之前并没有重视的wlansetinterface。
Interface,在这里我觉得可以理解成无线网卡。类似的WlanEnumInterfaces中实现的功能是罗列出当前无线网卡。
无线网卡的设置,其中有一项是radio的状态。
果然,这一切都有了了断。