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_DISABLE
  params.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, ignore
    if (GetLastError() != ERROR_NO_SUCH_DEVINST) {
      break;
    }
  }

  SP_DEVINSTALL_PARAMS devInstallParams;
  devInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
  // get new install params, check os whether need reboot
  if (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 reboot
  if (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 window
                            UINT uMsg,      // WM_DEVICECHANGE
                            WPARAM wParam,  // device-change event
                            LPARAM 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);
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GZ015 机器人系统集成应用技术样题4-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题4 选手须知&#xff1a; 本任务书共 25页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…

2020 ICPC·小米邀请赛 决赛 J. Rikka with Book(状压dp)

题目 登录—专业IT笔试面试备考平台_牛客网 n(n<20)本书&#xff0c;放在桌子上&#xff0c; 第i本书的可以看成是li(li<1e3)*1*1的物体&#xff0c;其中长为li&#xff0c;宽为1&#xff0c;高为1&#xff0c; 质量均匀分布&#xff0c;且为wi(wi<1e3) 求n本书摞…

前端桌面通知(Desktop Notifications)API

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

msvcrtd.dll下载安装方法,解决msvcrtd.dll找不到的问题

在这篇文章中&#xff0c;我们将详细讨论msvcrtd.dll文件的下载安装方法&#xff0c;并分析出现找不到msvcrtd.dll的情况及解决方法。如果你遇到了与msvcrtd.dll相关的问题&#xff0c;本文将为你提供全面且详细的解决方案。 一.什么是msvcrtd.dll文件 首先&#xff0c;让我们…

JS中的String常用的实例方法

splice():分隔符 把字符串以分隔符的形式拆分为数组 const str pink,red;const arr str.split(,);console.log(arr);//Array[0,"pink";1:"red"]const str1 2022-4-8;const arr1 str1.split(-);console.log(arr1);//Array[0,"2022";1:"…

权重衰减(Weight Decay)

在深度学习中&#xff0c;权重衰减&#xff08;Weight Decay&#xff09;是一种常用的正则化技术&#xff0c;旨在减少模型的过拟合现象。权重衰减通过向损失函数添加一个正则化项&#xff0c;以惩罚模型中较大的权重值。 一、权重衰减 在深度学习中&#xff0c;模型的训练过程…

leetcode移除元素

移除元素 题目分析题解代码:c版本c版本 题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺…

六大场景36种数据分析模型及方法图示,数据分析师必备!

【关注微信公众号&#xff1a;跟强哥学SQL&#xff0c;回复“笔试”免费领取大厂SQL笔试题。】 我一直认为&#xff0c;实际工作中&#xff0c;精通数据分析工具仅仅只是数据思维训练的一部分&#xff0c;掌握丰富的数据分析方法和模型实际上更为重要。 基于科学的数据分析方法…

『PyTorch』张量和函数之gather()函数

文章目录 PyTorch中的选择函数gather()函数 参考文献 PyTorch中的选择函数 gather()函数 import torch a torch.arange(1, 16).reshape(5, 3) """ result: a [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12],[13, 14, 15]] """# 定义两个index…

Tomcat-指定启动jdk、修改使用的jdk版本

修改tomcat配置文件setclasspath.sh 配置文件首行增加以下代码&#xff0c;指定启动的jdk&#xff1a; export JAVA_HOME/opt/softwares/jdk1.8.0_211/ export JRE_HOME/opt/softwares/jdk1.8.0_211/jre

【Kafka】Kafka的重复消费和消息丢失问题

文章目录 前言一、重复消费1.1 重复消费出现的场景1.1.1 Consumer消费过程中&#xff0c;进程挂掉/异常退出1.1.2 消费者消费时间过长 1.2 重复消费解决方案1.2.1 针对于消费端挂掉等原因造成的重复消费问题1.2.2 针对于Consumer消费时间过长带来的重复消费问题 二、消息丢失2.…

【Qt5】QVersionNumber

2023年12月10日&#xff0c;周日上午 QVersionNumber 是 Qt 框架中用于表示版本号的类。它提供了一种方便的方式来处理和比较版本号&#xff0c;特别是在应用程序或库需要与特定版本的依赖项进行交互时。 以下是一个简单的示例&#xff0c;演示了如何使用 QVersionNumber&…

Spring-temp

IOC/DI实现步骤 1.配置元数据 2.实例化IOC 3.获取Bean 基于XML配置方式 管理组件 1.基于构造函数&#xff1a;有参、无参 2.基于静态工厂方法&#xff1a;有参、无参 依赖注入 1.构造函数 2.setter方法 Bean组件高级特性 1.作用域 2.生命周期 FactoryBean 基于注解 IOC Bean作…

Python如何匹配库的版本

目录 1. 匹配库的版本 2. Python中pip&#xff0c;库&#xff0c;编译环境的问题回答总结 2.1 虚拟环境 2.2 pip&#xff0c;安装库&#xff0c;版本 1. 匹配库的版本 &#xff08;别的库的版本冲突同理&#xff09; 在搭建pyansys环境的时候&#xff0c;安装grpcio-tools…

加权准确率WA,未加权平均召回率UAR和未加权UF1

加权准确率WA&#xff0c;未加权平均召回率UAR和未加权UF1 1.加权准确率WA&#xff0c;未加权平均召回率UAR和未加权UF12.参考链接 1.加权准确率WA&#xff0c;未加权平均召回率UAR和未加权UF1 from sklearn.metrics import classification_report from sklearn.metrics impor…

Python中的程序逻辑经典案例详解

我的博客 文章首发于公众号&#xff1a;小肖学数据分析 Python作为一种强大的编程语言&#xff0c;以其简洁明了的语法和强大的标准库&#xff0c;成为了理想的工具来构建这些解决方案。 本文将通过Python解析几个经典的编程问题。 经典案例 水仙花数 问题描述&#xff1a…

三勾商城新功能-电子面单发货

商家快递发货时可以选择在线下单,在线获取和打印电子面单。免去手写面单信息以及避免填写运单号填错,系统会自动填写对应发货商品的运单信息 快递100电子面单1、进入快递100&#xff0c;点击登录 2、登录成功后&#xff0c;点击“电子面单与云打印” 3、进入电子面单与云打印后…

Druid-spring-boot-starter源码阅读-其余组件自动装配

前面我们看完了整个DruidDataSource初始化流程&#xff0c;但是其实Druid除了最核心的数据源之外&#xff0c;还有其他需要自动配置的&#xff0c;细心的人可能看到了&#xff0c;就是利用Import注解导入的四个类。 DruidFilterConfiguration public class DruidFilterConfigu…

解决:TypeError: write() argument must be str, not tuple

解决&#xff1a;TypeError: write() argument must be str, not tuple 文章目录 解决&#xff1a;TypeError: write() argument must be str, not tuple背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&#xff0c;报错&…

缓存的定义及重要知识点

文章目录 缓存的意义缓存的定义缓存原理缓存的基本思想缓存的优势缓存的代价 缓存的重要知识点 缓存的意义 在互联网高访问量的前提下&#xff0c;缓存的使用&#xff0c;是提升系统性能、改善用户体验的唯一解决之道。 缓存的定义 缓存最初的含义&#xff0c;是指用于加速 …