Windows设备管理

1、前言

熟悉Windows系统的都应该使用过设备管理器。设备管理器将操作系统中所有已安装的设备分类展现出来。同时提供了安装、卸载、启用和禁用的功能。
那么,我们应该如何通过C++编程的方式实现这种功能呢?答案很简单,那就是使用SetupDi函数族。

2、设备管理

2.1 设备枚举和查询

在查询设备信息时,首先需要使用SetupDiGetClassDevs函数获取设备信息集的句柄,其次通过SetupDiEnumDeviceInfo函数遍历信息集,获取每一个元素的设备信息,最后可以通过SetupDiGetDeviceInstanceIdSetupDiGetDeviceRegistryPropertySetupDiOpenDevRegKey查询对应的设备信息。

WINSETUPAPI HDEVINFO SetupDiGetClassDevs([in, optional] const GUID *ClassGuid,[in, optional] PCWSTR     Enumerator,[in, optional] HWND       hwndParent,[in]           DWORD      Flags
);// 获取所有的网卡设备,包含离线设备
HDEVINFO handle = SetupDiGetClassDevs(&GUID_DEVCLASS_NET, NULL, NULL, NULL);
  • 当需要获取某一类的设备时,ClassGuid参数传递对应设备类的GUID信息,否则请将参数置空。系统定义的类GUIDdevguid.h 中定义。
  • Flags参数通常可以使用DIGCF_ALLCLASSESDIGCF_PRESENTNULL。当使用DIGCF_ALLCLASSES,获取当前系统所有设备安装类或所有设备接口类的已安装设备的列表。使用后两者,则只返回指定设备类的设备列表,区别在于DIGCF_PRESENT只返回当前系统已连接的设备,而后者则返回所有。
WINSETUPAPI BOOL SetupDiEnumDeviceInfo([in]  HDEVINFO         DeviceInfoSet,[in]  DWORD            MemberIndex,[out] PSP_DEVINFO_DATA DeviceInfoData
);DWORD dev_index = 0;
do {SP_DEVINFO_DATA dev_data;dev_data.cbSize = sizeof(SP_DEVINFO_DATA);if (!SetupDiEnumDeviceInfo(handle, dev_index, &dev_data)) {break;}dev_index++;
} while (true);
  • DeviceInfoSet传入SetupDiGetClassDevs获取的设备句柄。
  • MemberIndex传入设备序列,从0开始每次加一,直至返回FALSE为止。
  • DeviceInfoData即获取的设备信息。
WINSETUPAPI BOOL SetupDiGetDeviceInstanceId([in]            HDEVINFO         DeviceInfoSet,[in]            PSP_DEVINFO_DATA DeviceInfoData,[out, optional] PWSTR            DeviceInstanceId,[in]            DWORD            DeviceInstanceIdSize,[out, optional] PDWORD           RequiredSize
);DWORD len = 1024;
wchar_t buffer[1024] = {};
if (SetupDiGetDeviceInstanceId(handle, &dev_data, buffer, len, &len) == TRUE) {if (0 == _wcsnicmp(buffer, L"PCI", 3) || 0 == _wcsnicmp(buffer, L"USB", 3)) {;  // Usb网卡或者内置网卡}
}
  • 最终获取的设备实例ID如下所示:
    在这里插入图片描述
WINSETUPAPI BOOL SetupDiGetDeviceRegistryProperty([in]            HDEVINFO         DeviceInfoSet,[in]            PSP_DEVINFO_DATA DeviceInfoData,[in]            DWORD            Property,[out, optional] PDWORD           PropertyRegDataType,[out, optional] PBYTE            PropertyBuffer,[in]            DWORD            PropertyBufferSize,[out, optional] PDWORD           RequiredSize
);DWORD regDataType;
DWORD len = 1024;
wchar_t buffer[1024] = {};
SetupDiGetDeviceRegistryProperty(handle, &dev_data, SPDRP_FRIENDLYNAME, &regDataType, (PBYTE)buffer, len, &len)
  • 从设备信息中取得想要的设备信息。比较常用的有SPDRP_FRIENDLYNAME等。

在这里插入图片描述

WINSETUPAPI HKEY SetupDiOpenDevRegKey([in] HDEVINFO         DeviceInfoSet,[in] PSP_DEVINFO_DATA DeviceInfoData,[in] DWORD            Scope,[in] DWORD            HwProfile,[in] DWORD            KeyType,[in] REGSAM           samDesired
);HKEY hDeviceKey = SetupDiOpenDevRegKey(handle, &dev_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ);
  • 打开设备对应的注册表信息,如下所示(通常情况下不需要使用本函数):

    在这里插入图片描述

2.2 设备启用/禁用
WINSETUPAPI BOOL SetupDiSetClassInstallParams([in]           HDEVINFO                DeviceInfoSet,[in, optional] PSP_DEVINFO_DATA        DeviceInfoData,[in, optional] PSP_CLASSINSTALL_HEADER ClassInstallParams,[in]           DWORD                   ClassInstallParamsSize
);WINSETUPAPI BOOL SetupDiCallClassInstaller([in]           DI_FUNCTION      InstallFunction,[in]           HDEVINFO         DeviceInfoSet,[in, optional] PSP_DEVINFO_DATA DeviceInfoData
);WINSETUPAPI BOOL SetupDiGetDeviceInstallParams([in]           HDEVINFO                DeviceInfoSet,[in, optional] PSP_DEVINFO_DATA        DeviceInfoData,[out]          PSP_DEVINSTALL_PARAMS_W DeviceInstallParams
);
  • SetupDiSetClassInstallParams函数可以实现各种各样的功能,而具体执行的功能由ClassInstallParams结构体的InstallFunction决定。

  • 而设备禁用/启用对应的功能编号是DIF_PROPERTYCHANGE,其对应的结构体是SP_PROPCHANGE_PARAMS

typedef struct _SP_PROPCHANGE_PARAMS {SP_CLASSINSTALL_HEADER ClassInstallHeader;DWORD                  StateChange;DWORD                  Scope;DWORD                  HwProfile;
} SP_PROPCHANGE_PARAMS, *PSP_PROPCHANGE_PARAMS;
  • StateChange支持DICS_ENABLE(启用)、DICS_DISABLE(禁用)、DICS_PROPCHANGE(设备的属性已更改)、DICS_START(启动设备)、DICS_STOP(设备正在停止)。
  • Scope可以使用DICS_FLAG_GLOBALDICS_FLAG_CONFIGSPECIFIC2个值,分别代表变更所有硬件配置文件和指定的硬件配置文件。
  • HwProfile指定变更的硬件配置文件,0代表当前硬件配置文件。
  • 个人见解,这里所说的硬件配置文件注册表中HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX
  • 简单示例如下:
do {SP_PROPCHANGE_PARAMS params;params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;// when disable, set params.StateChange = DICS_DISABLEparams.StateChange = DICS_ENABLE;params.Scope = DICS_FLAG_CONFIGSPECIFIC;params.HwProfile = 0;if (!SetupDiSetClassInstallParams(handle, &dev_data, &params.ClassInstallHeader, sizeof(params)) ||!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, handle, p_data)) {// SetupDiCallClassInstaller maybe failed by 0xE000020b, ignoreif (GetLastError() != ERROR_NO_SUCH_DEVINST) {break;}}SP_DEVINSTALL_PARAMS devInstallParams;devInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);// get new install params, check os whether need rebootif (SetupDiGetDeviceInstallParams(handle, &dev_data, &devInstallParams)) {if (devInstallParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) {;  // os need reboot or restart}}
} while (false);
2.3 设备卸载

设备卸载对应的功能编号是DIF_REMOVE。其对应的结构体则是SP_REMOVEDEVICE_PARAMS

typedef struct _SP_REMOVEDEVICE_PARAMS {SP_CLASSINSTALL_HEADER ClassInstallHeader;DWORD Scope;DWORD HwProfile;
} SP_REMOVEDEVICE_PARAMS, *PSP_REMOVEDEVICE_PARAMS;
  • Scope可以使用DI_REMOVEDEVICE_GLOBALDI_REMOVEDEVICE_CONFIGSPECIFIC2个值,分别代表变更所有硬件配置文件和指定的硬件配置文件。
  • HwProfile指定变更的硬件配置文件,0代表当前硬件配置文件。
  • DI_REMOVEDEVICE_CONFIGSPECIFIC标志仅适用于根枚举设备。 当 Windows 从配置设备的最后一个硬件配置文件中删除设备时,Windows 将执行全局删除。(具体用法请自行实践)
do {SP_REMOVEDEVICE_PARAMS params;params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);params.ClassInstallHeader.InstallFunction = DIF_REMOVE;params.Scope = DI_REMOVEDEVICE_GLOBAL;params.HwProfile = 0;if (!SetupDiSetClassInstallParams(handle, &dev_data, &params.ClassInstallHeader, sizeof(params)) ||!SetupDiCallClassInstaller(DIF_REMOVE, handle, p_data)) {break;}SP_DEVINSTALL_PARAMS devInstallParams;devInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);// get new install params, check os whether need rebootif (SetupDiGetDeviceInstallParams(handle, &dev_data, &devInstallParams)) {if (devInstallParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) {;  // os need reboot or restart}}
} while (false);

3、热插拔管理

对于计算机上已有的设备,可以通过上述步骤实现管控,那么新接入的设备应该如何管理呢?一个显而易见的方法就是监控所有设备的插拔事件。
而Windows系统也确实提供了一个对应的功能,即RegisterDeviceNotification函数。

HDEVNOTIFY RegisterDeviceNotification([in] HANDLE hRecipient,[in] LPVOID NotificationFilter,[in] DWORD  Flagsc
);
  • hRecipient:指定接受设备事件的窗口或者服务句柄。
  • NotificationFilter:指向设备类型的数据块指针。此块始终以 DEV_BROADCAST_HDR 结构开头。
  • Flagsc
含义
DEVICE_NOTIFY_WINDOW_HANDLEhRecipient 参数是窗口句柄。
DEVICE_NOTIFY_SERVICE_HANDLEhRecipient 参数是服务状态句柄。
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES通知接收方所有设备接口类的设备接口事件。 (实际测试中无效,待调查)
3.1 硬件事件注册

由于需要注册的是设备事件,对应的结构如下:

typedef struct _DEV_BROADCAST_DEVICEINTERFACE_W {DWORD       dbcc_size;DWORD       dbcc_devicetype;DWORD       dbcc_reserved;GUID        dbcc_classguid;wchar_t     dbcc_name[1];
} DEV_BROADCAST_DEVICEINTERFACE_W, *PDEV_BROADCAST_DEVICEINTERFACE_W;
  • dbcc_devicetype必须设置为DBT_DEVTYP_DEVICEINTERFACE
  • dbcc_devicetype则设置为需要监控的设备接口GUID。
定义含义头文件
GUID_DEVINTERFACE_USB_DEVICEUSB设备usbiodef.h
GUID_DEVINTERFACE_NET网络设备ndisguid.h
GUID_DEVINTERFACE_CDROM光驱设备winioctl.h
GUID_DEVINTERFACE_VOLUME卷设备winioctl.h
HWND hWnd; // 窗口句柄
SERVICE_STATUS_HANDLE hSvrHandle = NULL; // 服务句柄
HDEVNOTIFY hDev[4] = { NULL, NULL, NULL, NULL };BOOL UnregisterDeviceNotify() {for (int i = 0; i < 4; i++) {if (hDev[i] != NULL) {UnregisterDeviceNotification(hDev[i]);}}
}BOOL RegisterDeviceNotify() {GUID dev_guids[4] = {GUID_DEVINTERFACE_CDROM, GUID_DEVINTERFACE_VOLUME,GUID_DEVINTERFACE_NET, GUID_DEVINTERFACE_USB_DEVICE};for (int i = 0; i < 4; i++) {DEV_BROADCAST_DEVICEINTERFACE di = {0};di.dbcc_size = sizeof(di);di.dbcc_reserved = 0;di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;di.dbcc_classguid = dev_guids[i];// hDev[i] = RegisterDeviceNotification(hSvrHandle, &di,// DEVICE_NOTIFY_SERVICE_HANDLE);hDev[i] =RegisterDeviceNotification(hWnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);if (hDev[i] == NULL) {UnregisterDeviceNotify();}}
}
3.2 硬件事件回调
  • 对于窗口进程而言,硬件事件会通知到**WNDPROC** 回调函数中。其中uMsg参数固定为WM_DEVICECHANGEwParam参数对应具体的事件id,lParam为特定于事件的数据。
LRESULT CALLBACK WindowProc(HWND hwnd,      // handle to windowUINT uMsg,      // WM_DEVICECHANGEWPARAM wParam,  // device-change eventLPARAM lParam)  // event-specific data
{switch (message) {case WM_DEVICECHANGE: {switch (wParam) {case DBT_DEVICEARRIVAL:case DBT_DEVICEREMOVECOMPLETE: {PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {PDEV_BROADCAST_DEVICEINTERFACE pDev =(PDEV_BROADCAST_DEVICEINTERFACE)pHdr;// do some thing}break;}default:break;}} break;default:}return 0;
}
  • 对于服务进程来说,硬件事件会通知到**LPHANDLER_FUNCTION_EX** 回调函数中。此回调函数需要通过registerServiceCtrlHandlerEx注册。其中dwControl参数固定为SERVICE_CONTROL_DEVICEEVENTwParam参数对应具体的事件id,lParam为特定于事件的数据。
DWORD ServiceCtrlHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData,LPVOID lpContext) {switch (dwControl) {case SERVICE_CONTROL_DEVICEEVENT:switch (dwEventType) {case DBT_DEVICEARRIVAL:case DBT_DEVICEREMOVECOMPLETE: {PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lpEventData;if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {PDEV_BROADCAST_DEVICEINTERFACE pDev =(PDEV_BROADCAST_DEVICEINTERFACE)pHdr;// do some thing}}default:break;}default:break;};return 0;
}
3.3 数据解析

我们从硬件事件中获取的数据DEV_BROADCAST_DEVICEINTERFACE中,需要关注的只有2个数据。

  • dbcc_devicetype :硬件设备接口的GUID。即上文中注册时使用的GUID。
  • dbcc_name:硬件设备的设备路径。如下所示:

“\\?\USBSTOR#CdRom&Ven_SecZure&Prod_SZU113&Rev_1.12#8&17c82afa&0&SZU2230621000019&0#{53f56308-b6bf-11d0-94f2-00a0c91efb8b}”

其中,\\?\代表设备路径,#号作为路径分隔符,
{53f56308-b6bf-11d0-94f2-00a0c91efb8b}是设备接口的GUID信息,可以从中取得实例路径:USBSTOR\CdRom&Ven_SecZure&Prod_SZU113&Rev_1.12\8&17c82afa&0&SZU2230621000019&0

那么,取出设备地址可以干什么呢?答案就是注册表。
在windows操作系统中,所有的设备都会注册到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum路径下,将两个路径拼接就可以获取设备的注册表信息。
在这里插入图片描述
可以看出SetupDiGetDeviceRegistryProperty函数获取的信息和注册表中看到的信息基本上是一致的。

而其中的Driver字段对应的则是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class路径下SetupDiOpenDevRegKey打开的注册表路径。

bool GetInstanceId(const char *dev_path, char *instance_id, int *length) {if (strncmp(dev_path.c_str(), "\\\\?\\", 4)) return;size_t len = strlen(dev_path);for (int pos = len - 1; pos >= 0; pos--) {if (dev_path[pos] == '#') {if (dev_path[pos + 1] == '{' &&dev_path[pos + 1 + sizeof("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")] =='}') {for (int i = 4, j = 0; i < pos && j < (*length); i++) {if (dev_path[i] == '#') {instance_id[j++] = '\\';} else {instance_id[j++] = dev_path[i];}}instance_id[j] = '\0';return true;} else {return false;}}}return false;
}

4、类型识别

4.1 端口
  • 通过GUID_DEVCLASS_PORTS枚举所有的端口设备
  • 通过SetupDiGetDeviceRegistryProperty获取设备友好名称
  • 包含LPT:并口
  • 包含COM:串口
4.2 光驱
  • 通过GUID_DEVCLASS_CDROM枚举所有的端口设备
  • 通过SetupDiGetDeviceInstanceId获取设备实例路径
  • 包含CDROM:光驱
4.3 软驱
  • 通过GUID_DEVCLASS_FLOPPYDISK枚举所有的端口设备
  • 通过SetupDiGetDeviceInstanceId获取设备实例路径
  • FDC开始:软驱
4.4 蓝牙
  • 通过GUID_DEVCLASS_BLUETOOTH枚举所有的端口设备
  • 通过SetupDiGetDeviceInstanceId获取设备实例路径
  • USB开始:蓝牙
4.5 网口
  • 通过GUID_DEVCLASS_NET枚举所有的端口设备
  • 通过SetupDiGetDeviceInstanceId获取设备实例路径
  • PCI开始:有线网卡/无线网卡
  • USB开始:无线网卡/无线上网卡
  • BTH开始:蓝牙局部网
  • {5D624F94-8850-40C3-A3FA-A4FD2080BAF3}开始:热点
  • 其它:虚拟网卡

那么应该如何区分有线网卡,无线网卡和无线上网卡呢?那就是通过查询网卡注册表中的MediaSubType字段,值为2的情形即为无线网卡。

// 首先打开设备注册表
WCHAR szInstanceId[MAX_PATH];
DWORD dwSize = sizeof(szInstanceId);
// 查询网卡的连接id
if (RegQueryValueEx(hDeviceKey, TEXT("NetCfgInstanceId"), NULL, NULL,(LPBYTE)szInstanceId, &dwSize) == ERROR_SUCCESS) {std::wstring hKeyPath =L"SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-"L"11CE-BFC1-08002BE10318}\\";hKeyPath += szInstanceId;hKeyPath += L"\\Connection";if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, hKeyPath.c_str(), 0, KEY_READ, &hKey) ==ERROR_SUCCESS) {DWORD dwMediaSubType = 0;DWORD dwSize = sizeof(dwMediaSubType);if (RegQueryValueEx(hKey, L"MediaSubType", NULL, NULL,(LPBYTE)&dwMediaSubType, &dwSize) == ERROR_SUCCESS &&dwMediaSubType == 0x02) {// 无线网卡 } else {// 有线或者无线上网卡}RegCloseKey(hKey);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lumerical 选项------superimpose structure

Lumerical 选项------superimpose structure 简介正文 简介 这里给大家介绍一下 Modal analysis 计算中的 superimpose structure 选项的作用。 正文 当我们勾选上 superimpose structure 选项时&#xff0c; 当我们取消勾选时 通过对比我们得到&#xff0c;勾选 superimp…

Windows11环境下配置深度学习环境(Pytorch)

目录 1. 下载安装Miniconda2. 新建Python3.9虚拟环境3. 下载英伟达驱动4. 安装CUDA版Pytorch5. CPU版本pytorch安装 1. 下载安装Miniconda 下载安装包&#xff1a;镜像文件地址 将Miniconda相关路径添加至系统变量的路径中。 打开Anaconda Powershell Prompt&#xff0c;输入…

计算机组成原理-指令系统CISC和RISC

文章目录 总览CISC和RISC 总览 CISC和RISC 存储程序就是用一个电路再加上存储部件构成 可访存指令不同 RISC更自由&#xff0c;因为很多函数没有固定&#xff0c;是自己写的 由于CISC各个指令执行时间不一样&#xff0c;要实现指令流水线比较困难 由于CISC可访存指令没有限制…

游戏、算法竞赛与退役(流水账版)

写在前面 不出意外的话&#xff0c;这东西本该咕到翻年之后再发的&#xff0c;但好像催稿催的有点厉害&#xff0c;于是就找个机会把他写了&#xff08;笑&#xff09; 最初是只想写个算法竞赛退役记的&#xff0c;后面发觉写起来就有点收不住&#xff0c;算法竞赛牵扯到太多…

CSS margin-trim

margin-trim 主角登场主角的局限性兼容性 margin-trim &#x1f9ea;这是一个实验性的属性, 目前仅有 Safari 支持 看这个属性的名字就知道, 外边距修剪. 平常都会遇到一些排版上的问题, 比如垂直排列的元素之间增加下外边距 <div><li>123</li><li>…

JAVA序列化(创建可复用的 Java 对象)

JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象&#xff0c;但一般情况下&#xff0c;只有当 JVM 处于运行时&#xff0c;这些对象才可能存在&#xff0c;即&#xff0c;这些对象的生命周期不…

算法竞赛备赛进阶之树形DP训练

目录 1.树的最长路径 2.树的中心 3.数字转换 4.二叉苹果树 5.战略游戏 6.皇宫守卫 树形DP是一种动态规划方法&#xff0c;主要用于解决树形结构的问题。在树形DP中&#xff0c;通常会使用动态规划的思想来求解最优化问题。其核心在于通过不断地分解问题和优化子问题来解决…

2023年国家基地“楚慧杯”网络空间安全实践能力竞赛 Web方向 题解wp

前言&#xff1a;三小时的比赛&#xff0c;和强网同时结束还要当场交wp&#xff0c;汗流浃背&#xff0c;烧起来了啊啊啊啊~ eaaeval 目录扫出备份文件 源码如下 <?php class Flag{public $a;public $b;public function __construct(){$this->a admin;$this->b …

c++字符串和日期基础

一&#xff0c;字母三角形 #include<string> #include<iostream> using namespace std; int main() {int n 0;cin >> n;for (int i 1; i < n; i)//i代表行数{string spacestring(n - i, );//前半部分空格string ch string(2 * i - 1, A i - 1);cout…

工具在手,创作无忧:一键下载安装Auto CAD工具,让艺术创作更加轻松愉悦!

不要再浪费时间在网上寻找Auto CAD的安装包了&#xff01;因为你所需的一切都可以在这里找到&#xff01;作为全球领先的设计和绘图软件&#xff0c;Auto CAD为艺术家、设计师和工程师们提供了无限的创作潜力。不论是建筑设计、工业设计还是室内装饰&#xff0c;Auto CAD都能助…

《Linux C编程实战》笔记:文件属性操作函数

获取文件属性 stat函数 在shell下直接使用ls就可以获得文件属性&#xff0c;但是在程序里应该怎么获得呢&#xff1f; #include<sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *file_name,struct stat *buf); int fstat(i…

【eNSP实验项目】eNSP实验配置项目教程,ensp安装步骤

eNSP安装教程 附安装包 eNSP介绍安装教程1.安装 VirtualBox2.安装 WinPcap3.安装 Wireshark4.eNSP安装 eNSP介绍 eNSP是华为提供的一款功能强大的网络仿真平台&#xff0c;适用于学习、实践和测试企业网络场景&#xff0c;可以帮助用户深入理解网络知识和技术。 eNSP安装,需要…

Tektronix泰克TCP303示波器电流探头

主要特点和优点&#xff1a; ● 交流/直流测量功能 ● DC~100MHz电流探头放大器&#xff08;TCPA300&#xff09;&#xff0c;当使用&#xff1a; - DC~100MHz, 30A DC&#xff08;TCP312&#xff09; - DC~50MHz, 50A DC&#xff08;TCP305&#xff09; - DC~5MHz, 150A DC&a…

关于多重背包的笔记

多重背包可以看作01背包的拓展&#xff0c; 01背包是选或者不选。多重背包是选0个一直到选s个。 for (int i 1; i < n; i) {for (int j m; j > w[i]; --j){f[j] max(f[j], f[j - 1*w[i]] 1*v[i], f[j - 2*w[i]] 2*v[i],...f[j - s*w[i]] s*v[i]);} } 由上述伪代码…

Mybatis-plus是使用,告别繁琐的CRUD编写,自动生成直接使用

目录 一、简介 1. 是什么 2. 特性 3. 框架结构 4. 常用注解 二、搭建使用 1. 依赖 2. 生成器 3. 生成 4. 引用 5. 路径访问 三、测试 四、雪花ID 每篇一获 Mybatis-plus&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;…

VRRP协议

一.基本概念 1.概念 VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的IP地址为默认网关&#xff0c;实现网关的备份。协议版本&#xff1a;VRRPv2&#xff08;常用&#xff09;和VRRPv3&#xff1a;VRRPv2仅适用于…

【基于卷积神经网络的疲劳检测与预警系统的设计与实现】

基于卷积神经网络的疲劳检测与预警系统的设计与实现 引言数据集介绍技术与工具1. OpenCV2. TensorFlow3. 卷积神经网络&#xff08;CNN&#xff09; 系统功能模块1. 视频采集模块2. 图像预处理模块3. 人脸识别模块4. 疲劳程度判别模块5. 报警模块 系统设计创新点1. 实时监测与预…

【LeetCode刷题笔记(6-1)】【Python】【三数之和】【哈希表】【中等】

文章目录 引言三数之和题目描述示例示例1示例2示例3 提示 解决方案1&#xff1a;【三层遍历查找】解决方案2&#xff1a;【哈希表】【两层遍历】结束语 三数之和 引言 编写通过所有测试案例的代码并不简单&#xff0c;通常需要深思熟虑和理性分析。虽然这些代码能够通过所有的…

【STM32】STM32学习笔记-EXTI外部中断(11)

00. 目录 文章目录 00. 目录01. 中断系统02. 中断执行流程03. STM32中断04. NVIC基本结构05. NVIC优先级分组06. EXTI简介07. EXTI基本结构08. AFIO复用IO口09. EXTI框图10. 计数器模块11. 旋转编码器简介12. 附录 01. 中断系统 中断&#xff1a;在主程序运行过程中&#xff0…

韩顺平学java第二阶段之BS框架002

这边讲了php都可以&#xff0c;反正就是打通双方的间隔就行了∑(っД;)っ卧槽&#xff0c;不见了