记一次使用WIN api中的wifi有关操作并借助CLR生成链接库供C#的WPF程序调用

最后带有完整代码

WlanConnect的参数

DWORD WlanConnect(
  [in] HANDLE                            hClientHandle,
  [in] const GUID                        *pInterfaceGuid,
  [in] const PWLAN_CONNECTION_PARAMETERS pConnectionParameters,
       PVOID                             pReserved
);

其参数如下,主要的参数就是pConnectionParameters,它的其中一个成员wlanConnectionMode有许多模式可供选择,网上找的别人写的范例都是将其设为wlan_connection_mode_profile ,但每次运行都抛出参数错误提示,也就是返回ERROR_INVALID_PARAMETER错误

WlanConnect的返回值说明中,ERROR_INVALID_PARAMETER错误有12种之多,其中一条写道:

  • The wlanConnectionMode member of the structure pointed to by pConnectionParameters is set to wlan_connection_mode_profile, and the strProfile member of the same structure is NULL or the length of the profile exceeds WLAN_MAX_NAME_LENGTH.

就是说如果设置为wlan_connection_mode_profile 模式,相应的配置文件strProfile不能为空,并且不能超过WLAN_MAX_NAME_LENGTH(该值在我的机器上定义为256),而我的配置文件早就超过了这个值。好几天后,尝试将连接模式设置为wlan_connection_mode_temporary_profile,才能成功执行WlanConnect函数。

感觉微软的文档有时候说明了怎么做不对,却没有说明要具体怎么做(可能我水平太低)

最后虽然能够完成连接网络的代码,但是windows状态栏点开wifi图标后却显示没有连接,这个真的不知道问题出在哪里,而用cmd的wlan命令没有这个问题

静态编译(将dll文件整合到exe程序中)

上述连接wifi代码编译后还附加几个dll,毕竟只是一个小文件,移植到别人的还要用压缩包,停麻烦的,遂整合成一个文件。

我使用VS编译代码,按网上说的将项目属性>>C++>>代码生成  中的运行库选项更改为/MT,但是并不能奏效,因为我还使用了除winapi外的库,仅仅更改此选线没用。

笔者使用vcpkg安装编译其他库,而vcpkg具有在编译阶段生成静态库的功能(详见vcpkg文档网页中搜索static),而项目属性中也有Use Static Library选项,结合/MT,确实将dll文件整合到一个exe中了,但是移植到我同学的电脑上时,运行又失败了,就是那个另外引入的库运行失败,不知道具体原因。

网上还有用winrar打包的,没使用过,以后可以试试看

(中间还尝试过使用.def文件生成dll,但是自己写的导出函数都报ERROR LINK,只好换个方向

C++通过CLR编译为动态库供WPF使用

建议在创建项目的时候选择CLR类库

并且不要改乱改适用的.NET版本,否则虽然dll文件生成了却无法调用。而且wpf项目使用的.NET版本应该和库文件使用的一样(也不一定,dll使用.NETCore3.1,wpf使用.Net5.0我也成功运行了),Release版本的dll应该对应Release版本的WPF,并且同时x64或x86

编写适用与CLR的C++代码时,有些winapi内置类型像句柄,在取址的时候,会自动转换成托管类型(属于C#的类型,但是winapi无法接收),而且我没找到方法去转换,但微软文档里写道:

An interior pointer can only be declared on the stack. An interior pointer cannot be declared as a member of a class.

一个interior指针(C#中的托管类型)只能在栈中被声明,该指针不能被声明为类成员

也就是说,不能在类中定义这些HANDLE之类的变量,但可以声明为全局变量。

生成后的dll文件在引用后可以在WPF工程项目的对象浏览器中查看导出的函数,但使用dumpbin或Dependencies工具查不到导出的函数

该方法相比每个函数都使用dllimport,对原C++文件和C#代码都没有太多的附加操作,简便了许多

不知道真正的C++写内核,C#写界面是什么样的,欢迎讨论和交流

CLR项目头文件
//CLR项目头文件    ClassLibrary2.h
#pragma once
#include<Windows.h>
#include<wlanapi.h>
#include <stdio.h>
#include<curl/curl.h>
#include<locale>
using namespace System;

HANDLE hClientHandle;
PWLAN_INTERFACE_INFO_LIST  pInterfaceList;

DWORD* dwDataSize;
VOID* pData;
WLAN_OPCODE_VALUE_TYPE* opcodeValueType;

WLAN_AVAILABLE_NETWORK_LIST* pAvailableNetworkList;

PWLAN_PROFILE_INFO_LIST pProfileList;
//校园网,无密码,连接后需要网络登录验证;有密码的导出自己本机wifi配置替换strProfileXml中的数据,详细看微软文档
LPCWSTR    strProfileXml=TEXT(R"(<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
	<name>NIIT-WIFI</name>
	<SSIDConfig>
		<SSID>
			<hex>WIFI名称的hex值,可以不填</hex>
			<name>WIFI名称</name>
		</SSID>
	</SSIDConfig>
	<connectionType>ESS</connectionType>
	<connectionMode>auto</connectionMode>
	<MSM>
		<security>
			<authEncryption>
				<authentication>open</authentication>
				<encryption>none</encryption>
				<useOneX>false</useOneX>
			</authEncryption>
		</security>
	</MSM>
	<MacRandomization xmlns="http://www.microsoft.com/networking/WLAN/profile/v3">
		<enableRandomization>false</enableRandomization>
		<randomizationSeed>3036961407</randomizationSeed>
	</MacRandomization>
</WLANProfile>

)");;
namespace ClassLibrary2 {
	public ref class myWlan
	{
	public:
		myWlan();
		~myWlan();
		BOOL openWlanService();
		BOOL enumWlanInterfaces();
		BOOL queryInterfacePara();
		BOOL setWlanInterface(int state);
		BOOL scanWlan();
		BOOL getAvailableNetList();
		BOOL setWlanProfile(LPCWSTR strProfileXml);
		BOOL getWlanProfile();
		BOOL connectWlan(LPCWSTR strProfileXml);
		BOOL loginWeb();

	};
}

CLR代码实现
 #include "pch.h"

#include "ClassLibrary2.h"





namespace ClassLibrary2
{
    myWlan::myWlan()
    {
        hClientHandle = (HANDLE)WlanAllocateMemory(sizeof(HANDLE));
        pInterfaceList = (WLAN_INTERFACE_INFO_LIST*)WlanAllocateMemory(sizeof(WLAN_INTERFACE_INFO_LIST*));
        opcodeValueType = (WLAN_OPCODE_VALUE_TYPE*)WlanAllocateMemory(sizeof(WLAN_OPCODE_VALUE_TYPE*));
        pAvailableNetworkList = (WLAN_AVAILABLE_NETWORK_LIST*)WlanAllocateMemory(sizeof(WLAN_AVAILABLE_NETWORK_LIST*));
        pProfileList = (PWLAN_PROFILE_INFO_LIST)WlanAllocateMemory(sizeof(PWLAN_PROFILE_INFO_LIST));
    }
    myWlan::~myWlan()
    {
        WlanFreeMemory(hClientHandle);
        WlanFreeMemory(pInterfaceList);
        WlanFreeMemory(opcodeValueType);
        WlanFreeMemory(pAvailableNetworkList);
        WlanFreeMemory(pProfileList);
    }
    BOOL myWlan::openWlanService()
    {
        DWORD  dwNegotiatedVersion;
        DWORD dwResult = WlanOpenHandle(WLAN_API_VERSION, NULL, &dwNegotiatedVersion, &hClientHandle);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_INVALID_PARAMETER:
                wprintf(L"%s\n", L"pdwNegotiatedVersion is NULL, phClientHandle is NULL, or pReserved is not NULL.");
                break;
            case ERROR_NOT_ENOUGH_MEMORY:
                wprintf(L"%s\n", L"	Failed to allocate memory to create the client context.");
                break;
            case ERROR_REMOTE_SESSION_LIMIT_EXCEEDED:
                wprintf(L"%s\n", L"Too many handles have been issued by the server.");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"WlanOpenHandle Fail:%d\n", dwResult);
                break;
            }
            return false;
        }
        return true;

    }
    BOOL myWlan::enumWlanInterfaces()
    {
        DWORD dwResult = WlanEnumInterfaces(hClientHandle, NULL, &pInterfaceList);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_INVALID_PARAMETER:
                wprintf(L"%s", L"A parameter is incorrect.\
                This error is returned if the hClientHandle or ppInterfaceList parameter is NULL. \
                This error is returned if the pReserved is not NULL. \
                This error is also returned if the hClientHandle parameter is not valid.");
                break;
            case ERROR_INVALID_HANDLE:
                wprintf(L"%s", L"	The handle hClientHandle was not found in the handle table.");
                break;
            case ERROR_NOT_ENOUGH_MEMORY:
                wprintf(L"%s", L"Not enough memory is available to process this request and allocate memory for the query results.");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"WlanEnumInterfaces failed,%d", dwResult);
                break;
            }
            return false;
        }
        return true;

    }

    BOOL myWlan::queryInterfacePara()
    {
        WLAN_INTERFACE_INFO interfaceInfo = pInterfaceList->InterfaceInfo[0];
        DWORD dwResult = WlanQueryInterface(hClientHandle, &interfaceInfo.InterfaceGuid, wlan_intf_opcode_bss_type,
            NULL, dwDataSize, &pData, opcodeValueType);  //为pData释放内存
        if (ERROR_SUCCESS != dwResult)
        {
            dwResult = GetLastError();
            wprintf(L"WlanEnumInterfaces failed,%d", dwResult);
            return false;
        }
        return true;
    }
    BOOL myWlan::setWlanInterface(int state)
    {
        WLAN_PHY_RADIO_STATE phyRadioState{};
        phyRadioState.dwPhyIndex = wlan_interface_type_native_802_11;
        if (1 == state)
        {
            phyRadioState.dot11SoftwareRadioState = dot11_radio_state_on;
            phyRadioState.dot11HardwareRadioState = dot11_radio_state_on;
        }
        else
        {
            phyRadioState.dot11SoftwareRadioState = dot11_radio_state_off;
            phyRadioState.dot11HardwareRadioState = dot11_radio_state_off;
        }
        PVOID pData = &phyRadioState;


        DWORD dwResult = WlanSetInterface(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid,
            wlan_intf_opcode_radio_state, sizeof(WLAN_PHY_RADIO_STATE), pData, NULL);
        if (ERROR_SUCCESS != dwResult)
        {
            dwResult = GetLastError();
            wprintf(L"%s", L"setWlanInterface failed");
            return false;
        }
        return true;
    }

    BOOL myWlan::scanWlan()
    {
        DWORD dwResult = WlanScan(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid, NULL, NULL, NULL);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_INVALID_PARAMETER:
                wprintf(L"%s", L"hClientHandle is NULL or invalid, pInterfaceGuid is NULL, or pReserved is not NULL.");
                break;
            case ERROR_INVALID_HANDLE:
                wprintf(L"%s", L"The handle hClientHandle was not found in the handle table.");
                break;
            case ERROR_NOT_ENOUGH_MEMORY:
                wprintf(L"%s", L"Failed to allocate memory for the query results.");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"scanWlan failed,%d", dwResult);
                break;
            }
            return false;
        }
        return true;
    }
    BOOL myWlan::getAvailableNetList()
    {
        DWORD dwResult = WlanGetAvailableNetworkList(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid,
            WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES, NULL, &pAvailableNetworkList);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_INVALID_PARAMETER:
                wprintf(L"%s\n", L"A parameter is incorrect.\
 This error is returned if the hClientHandle, pInterfaceGuid, \
or ppAvailableNetworkList parameter is NULL. This error is returned \
if the pReserved is not NULL. This error is also returned if the dwFlags parameter value\
 is set to value that is not valid or the hClientHandle parameter is not valid.");
                break;
            case ERROR_INVALID_HANDLE:
                wprintf(L"%s\n", L"	The handle hClientHandle was not found in the handle table.");
                break;
            case ERROR_NDIS_DOT11_POWER_STATE_INVALID:
                wprintf(L"%s\n", L"The radio associated with the interface is turned off.\
 There are no available networks when the radio is off.");
                break;
            case ERROR_NOT_ENOUGH_MEMORY:
                wprintf(L"%s\n", L"Not enough memory is available to process\
 this request and allocate memory for the query results.");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"WlanOpenHandle Fail:%d\n", dwResult);
                break;
            }
            return false;
        }
        return true;


    }
    BOOL myWlan::setWlanProfile(LPCWSTR strProfileXml)
    {
        DWORD dwReasonCode;
        DWORD dwResult = WlanSetProfile(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid, 0, strProfileXml, NULL,
            TRUE, NULL, &dwReasonCode);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_ACCESS_DENIED:
                wprintf(L"%s\n", L"The caller does not have sufficient permissions to set the profile.");
                break;
            case ERROR_ALREADY_EXISTS:
                wprintf(L"%s\n", L"	strProfileXml specifies a network that already exists.");
                break;
            case ERROR_BAD_PROFILE:
            {
                wprintf(L"%s\n", L"The profile specified by strProfileXml is not valid.");
                PWCHAR pStringBuffer = (PWCHAR)calloc(128, sizeof(WCHAR));
                WlanReasonCodeToString(dwReasonCode, 128, pStringBuffer, NULL);
                setlocale(LC_ALL, "chs");
                printf("%ls", pStringBuffer);
                printf("%ls", strProfileXml);
                break;
            }//在case中定义变量要在大括号中完成
            case ERROR_INVALID_PARAMETER:
                wprintf(L"%s\n", L"One of the following conditions occurred:");
                break;
            case ERROR_NO_MATCH:
                wprintf(L"%s\n", L"The interface does not support one or more of the capabilities specified in the profile. ");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"WlanOpenHandle Fail:%d\n", dwResult);
                break;
            }
            return false;
        }
        //printf("setprofile成功\n");
        return true;
    }
    BOOL myWlan::getWlanProfile()
    {
        //不用get……list找其他的配置了,只找一个wifi的配置
        LPWSTR strProfileXML;
        DWORD dwFlags = WLAN_EXECUTE_ACCESS;
        DWORD dwGrantedAccess = WLAN_READ_ACCESS;
        DWORD dwResult = WlanGetProfile(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid,
            L"WIFI名称", NULL, &strProfileXML, &dwFlags, &dwGrantedAccess);//设置pdwFlags可以查找密码
        if (ERROR_SUCCESS != dwResult)
        {
            wprintf(L"failed");
            return false;
        }

        setlocale(LC_ALL, "chs");
        //printf("%ls",strProfileXML);
        return true;

    }
    BOOL myWlan::connectWlan(LPCWSTR strProfileXml)
    {

        //printf("%ls\n", strProfileXml);
        WLAN_CONNECTION_PARAMETERS connectionParameters{};
        connectionParameters.wlanConnectionMode = wlan_connection_mode_temporary_profile;
        connectionParameters.strProfile = strProfileXml;
        connectionParameters.pDot11Ssid = NULL;
        connectionParameters.pDesiredBssidList = NULL;
        connectionParameters.dot11BssType = dot11_BSS_type_infrastructure;
        connectionParameters.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK;
        DWORD dwResult = WlanConnect(hClientHandle, &pInterfaceList->InterfaceInfo[0].InterfaceGuid,
            &connectionParameters, NULL);
        if (ERROR_SUCCESS != dwResult)
        {
            switch (dwResult)
            {
            case ERROR_INVALID_PARAMETER:
                wprintf(L"Para is NULL\n");
                break;
            case ERROR_INVALID_HANDLE:
                wprintf(L"Failed to INVALID HANDLE \n");
                break;
            case ERROR_ACCESS_DENIED:
                wprintf(L"The caller does not have sufficient permissions. \n");
                break;
            default:
                dwResult = GetLastError();
                wprintf(L"WlanConnect Fail: %d\n", dwResult);
                break;
            }
            wprintf(L"FAILED");
            return false;
        }
        wprintf(L"SUCCESS");
        return true;
    }

    BOOL myWlan::loginWeb()
    {
        CURL* curl;
        CURLcode res;
        char* jsonString = (char*)R"(登录的JSON请求)";

        /* In windows, this will init the winsock stuff */
        curl_global_init(CURL_GLOBAL_ALL);

        /* get a curl handle */
        curl = curl_easy_init();
        if (curl) {
            /* First set the URL that is about to receive our POST. This URL can
               just as well be a https:// URL if that is what should receive the
               data. */
            curl_easy_setopt(curl, CURLOPT_URL, "请求网址");
            /* Now specify the POST data */
            curl_slist* plist = curl_slist_append(NULL,
                "Content-Type:application/json;charset=UTF-8");
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, plist);

            // 设置要POST的JSON数据
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonString);

            /* Perform the request, res will get the return code */
            res = curl_easy_perform(curl);
            /* Check for errors */
            if (res != CURLE_OK)
            {
                fprintf(stderr, "curl_easy_perform() failed: %s\n",
                    curl_easy_strerror(res));
                wprintf(L"\n登录校园网失败,请重试\n");
                Sleep(1000);
                return FALSE;
            }

            /* always cleanup */
            curl_easy_cleanup(curl);
        }
        curl_global_cleanup();
        return TRUE;

    }
}

CLR代码基本和普通的C++代码差不多,删除类声明中的public ref class myWlan再按编译器提示改一下就行了,主程序中依次调用类中函数可以实现wlanapi的功能。至于WPF代码主要是界面,我就不列了,C#我也没接触多少。

posted @ 2022-10-15 23:26  werid  阅读(303)  评论(0)    收藏  举报