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的状态。
果然,这一切都有了了断。