当前位置: 首页 > news >正文

开源WebRTC库放大器模式在采集桌面图像时遇到的DPI缩放与内存泄漏问题排查

目录

1、在非100%的显示比例下放大器采集到的桌面图像不全问题

1.1、通过manifest文件禁止系统对软件进行缩放

1.2、调用SetThreadDpiAwarenessContext函数,禁止系统对目标线程中的窗口进行缩放

1.3、使用winver命令查看Windows的年月版本

2、使用放大器模式遇到的内存泄漏问题

2.1、使用Windbg动态调试发现软件因为申请内存失败抛出bad_alloc异常导致程序闪退

2.2、进一步分析发现时内存泄漏导致进程内存不足,引发申请内存失败抛出bad_alloc异常

2.3、排查桌面共享模块内存泄漏的原因

3、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N4P3https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N4P3https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)icon-default.png?t=N4P3https://blog.csdn.net/chenlycly/category_12279968.html       WebRTC开源库中实现桌面图像采集的方式有多种,为了支持过滤部分窗口的功能,我们采用了magnification放大器方式,但在使用放大器这种采集方式时遇到了一些问题,在这里大概地总结一下,给大家提供一个借鉴或参考。

1、在非100%的显示比例下放大器采集到的桌面图像不全问题

       我们软件为了支持过滤窗口,采用了开源WebRTC库中支持的放大镜采集模式,但使用放大器模式后测试发现,当系统的显示比例调成非100%的显示比例(比如150%、200%等)后,放大器组件采集出来的桌面图像不全,只采集到桌面的一部分。应该是系统DPI显示缩放引起的,默认情况下,系统会根据当前的显示比例自动对程序进行缩放。

1.1、通过manifest文件禁止系统对软件进行缩放

        一般再高分辨率的电脑上,均需要将系统的显示比例设置成100%以上的比例。设置入口是,在桌面空白处点击右键,在弹出的右键菜单中点击显示设置,然后在打开的窗口中找到缩放与布局栏,就可以更改系统的显示比例了,如下所示:

默认情况下,系统会根据当前的显示比例自动对程序进行缩放,除非我们想禁用一下系统的缩放,让程序始终保持100%的显示效果,看看放大器组件采集出来的桌面图像是否完整。直接到我们程序桌面快捷方式的属性中设置禁止系统对我们程序进行缩放,设置入口如下所示:

即不管系统设置了什么显示比例,我们的程序始终保持100%的显示效果,重新运行程序,发现采集出来的桌面图像就完整了。

       上述设置是手动修改的,有没有办法通过代码去设置呢?告诉系统不要对我的程序进行缩放呢?以前我们研究过系统API函数SetProcessDPIAware,这个函数可以禁止系统对我们程序进行缩放,但将程序放到Win10系统中运行就没有效果了,系统还是对程序进行了缩放。后来查看SetProcessDPIAware函数在MSDN上说明,提示不要再使用这接口了,应该使用嵌入manifest文件的方式,相关说明如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"><asmv3:application><asmv3:windowsSettings><dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware><dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness></asmv3:windowsSettings></asmv3:application>
</assembly>

我们新建txt文件,然后把上述内容拷贝到该文件中,然后再将文件重命名为.mannifest文件即可(注意manifest文件的名称要和进程名称一致)。然后直接将manifest文件像添加文件一样添加到工程中,编译后启动程序就生效了,Win10系统中就能实现禁止系统缩放了。

1.2、调用SetThreadDpiAwarenessContext函数,禁止系统对目标线程中的窗口进行缩放

       如果要让放大器组件采集到完整的桌面图像,就需要禁用系统对程序的缩放。但要禁用系统的缩放,让程序始终显示100%大小效果,会导致程序在一些高分率的电脑上(比如2K或4K的微软Surface平板电脑),显示太小,根本就没法看没法操作了。腾讯系的很多软件都禁用了系统缩放,软件自己实现了跟随系统显示比例的缩放,所以他们没有这样的困扰。但程序自己去实现缩放是有难度的,我们目前做不到,所以还是需要依赖系统缩放的。

       后来找到了一个针对线程设置DPI缩放属性的API函数SetThreadDpiAwarenessContext可以禁止系统对某个线程中创建的窗口禁止缩放。我们可以将放大器组件的操作放到一个线程中,然后调用这个函数禁止系统对该线程中放大器窗口进行缩放,这样放大器组件就能采集到完整的桌面图像了。

       这个API接口是放置在系统库user32.dll中的:

Win10以前的版本是不支持的,即便是Win10系统也要1607(2016年7月发布)之后的版本才支持。所以,要从user32.dll库中动态加载,如果找不到SetThreadDpiAwarenessContext接口,则直接返回;如果找到接口再去调用,相关代码如下:

bool SetThreadDpiAware() 
{//设置本线程为DPI感知模式,解决缩放时屏幕采集不全的问题HMODULE user32_module = GetModuleHandle(TEXT("user32.dll"));if (nullptr == user32_module) {return false;}decltype(&SetThreadDpiAwarenessContext) set_thread_dpi_awareness_context_func =(decltype(&SetThreadDpiAwarenessContext))GetProcAddress(user32_module, "SetThreadDpiAwarenessContext");if (nullptr == set_thread_dpi_awareness_context_func) {return false;}DPI_AWARENESS_CONTEXT original_dpi_awareness_context = set_thread_dpi_awareness_context_func(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);return true;
}

1.3、使用winver命令查看Windows的年月版本

        上面说到的SetThreadDpiAwarenessContext函数只有Win10系统才支持,并且只有“Windows 10, version 1607”以后的版本才支持,此处的版本号1607是2016年7月发布的意思。这个version 1607是年月版本,在命令行中使用systeminfo命令查看的版本是系统内部版本:

       如果要查看Windows发布的年月版本,则需要使用winver命令,执行该命令后会打开如下的版本窗口:

如图所示,当前机器的年月版本为1909,即2019年9月发布的版本。

2、使用放大器模式遇到的内存泄漏问题

       测试同事反馈,在多次发起桌面共享后,软件会出现闪退问题,异常捕获模块没有捕获到,没有生成dump文件。

2.1、使用Windbg动态调试发现软件因为申请内存失败抛出bad_alloc异常导致程序闪退

       对于这种没有捕获到异常的场景,就需要使用Windbg进行动态调试了。于是让同事重新将软件启动起来,然后将Windbg附加上去,让Wingdbg和软件一起跑,然后按照操作步骤将闪退问题复现出来。问题复现时,Windbg第一时间感知到并中断下来,使用kn命令查看此时的函数的函数调用堆栈,如下所示:

通过堆栈看出当前是在用new动态申请内存时抛出了bad_alloc异常,所以引发了异常崩溃。

2.2、进一步分析发现时内存泄漏导致进程内存不足,引发申请内存失败抛出bad_alloc异常

       应该是内存不足导致内存申请失败,进而抛出bad_alloc异常。此时的进程还在的,使用Process Explorer工具查看进程的虚拟内存占用,看到进程的虚拟内存已经占用了1.7GB。注意,Window任务管理器看不到进程占用的虚拟内存,只能看到与物理内存相关的内容,需要借助Process Explorer工具去查看。Process Explorer中显示的是用户态虚拟内存。

       我们的程序是32位的,系统给进程分配了4GB的虚拟内存,其中用户态虚拟内存占2GB,内核态虚拟内存占2GB。当前程序进程的用户态虚拟内存占用达到1.7GB:

按将离上限2GB还有300MB空闲,为啥用new申请内存时会失败呢?可能申请的是一段较长的buffer,而空闲的300MB虚拟内存是分散在不同地方的零零散散的小块内存(这就是我们经常讲的内存碎片),找不到一段很大的连续内存去分配了,所以出现内存分配失败了。为啥程序的虚拟内存占用会达到1.7GB之多呢?估计是程序中有内存泄漏了

       鉴于当前的操作场景,可能是桌面共享功能模块有内存泄漏,于是让测试发起桌面共享之前记录一下总的虚拟内存大小,然后发起桌面共享,然后再关闭桌面共享,看看内存有没有明显增长。经测试发现,发起共享时会申请内存,但停止共享后没有将申请的内存释放掉,所以这个操作有内存泄漏。后来使用Windbg分析内存泄漏,分析出发生泄漏内存的函数调用堆栈就指向桌面共享的模块代码中。具体如何使用Windbg分析内存泄漏,可以参见我之前写的文章:
使用Windbg定位Windows C++程序中的内存泄漏icon-default.png?t=N4P3https://blog.csdn.net/chenlycly/article/details/121295720

2.3、排查桌面共享模块内存泄漏的原因

2.3.1、怀疑是放大器组件回调上来的buffer没有释放导致内存泄漏的

       桌面共享模块为了实现窗口过滤,选用了上面讲到的magnification放大器模式,于是详细去排查操作放大器的相关代码,看看为啥会有内存泄漏。代码中主要使用了MagInitialize、MagUninitialize、MagSetWindowSource、MagSetWindowFilterList、MagSetImageScalingCallback这几个系统API函数,到MSDN上详细查看了这几个函数的详细说明,但说明都比较少,没有找到线索,也没有说需要额外释放哪些资源。我们在结束桌面共享时也调用MagUninitialize去释放资源,但还是存在内存泄漏。

       排查至此,陷入了僵局,没法进行下去了。后来想,难道是放大器组件调用设置进去的回调函数回调出来的buffer需要外部去释放?这个回调函数是调用MagSetImageScalingCallback函数设置的,函数声明如下:

ypedef BOOL (CALLBACK* MagImageScalingCallback)(HWND hwnd, void * srcdata, MAGIMAGEHEADER srcheader, void * destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty );

回调出来的buffer地址就存放在void * srcdata指针变量中,通过加打印发现回调出来的buffer首地址一个不变的地址,估计在放大器组件内部在开始时申请的一段buffer,存放抓取的桌面共享图像数据的,多次抓取的图像数据都是保存在该buffer中,然后每次将该buffer的地址回调出去。

       怀疑这个放大器组件内部申请的buffer内存没有释放,导致了内存泄漏。于是将回调函数回调出来的buffer地址保存到成员变量void* m_srcdata中,在结果共享时上层去主动将这个bufer内存给释放掉。最开始尝试用delete去释放,结果执行到delete时产生了异常。

2.3.2、参考API函数GetAdaptersAddresses的Remarks部分的说明,决定使用HeapFree去释放

       动态申请内存的方式有多种,比如使用new(要用delete去释放),比如使用malloc(要用free去释放),再比如调用系统API函数HeapCreate或者HeapAlloc(要用HeapFree去释放),还有可以调用API函数VirtualAlloc(要用VirtualFree去释放),还有其他的API函数。

       到底这个buffer使用哪种方式呢?以前在写读取多个网卡信息时,需要调用系统API函数GetAdaptersAddresses,在调用该接口时传入用来存放网卡信息的buffer需要外部申请好,看MSDN上GetAdaptersAddresses函数的Remarks部分说明:

建议使用HeapAlloc去申请内存,然后使用完后使用HeapFree将内存释放掉。调用系统API函数GetAdaptersAddresses的示例代码如下:

#include <winsock2.h>
#include <iphlpapi.h>
#include <stdio.h>
#include <stdlib.h>// Link with Iphlpapi.lib
#pragma comment(lib, "IPHLPAPI.lib")void PrintAdapterInfo()
{// Declare and initialize variablesDWORD dwSize = 0;DWORD dwRetVal = 0;unsigned int i = 0;// Set the flags to pass to GetAdaptersAddressesULONG flags = GAA_FLAG_INCLUDE_PREFIX;// default to unspecified address family (both)ULONG family = AF_UNSPEC;// AF_INET - ipv4, AF_INET6 - ipv6LPVOID lpMsgBuf = NULL;PIP_ADAPTER_ADDRESSES pAddresses = NULL;ULONG outBufLen = 0;ULONG Iterations = 0;PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;PIP_ADAPTER_ANYCAST_ADDRESS pAnycast = NULL;PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = NULL;IP_ADAPTER_DNS_SERVER_ADDRESS *pDnServer = NULL;IP_ADAPTER_PREFIX *pPrefix = NULL;// Allocate a 15 KB buffer to start with.outBufLen = 15000;do {pAddresses = (IP_ADAPTER_ADDRESSES *)HeapAlloc(GetProcessHeap(), 0, outBufLen);if (pAddresses == NULL) {printf("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");return;}dwRetVal = GetAdaptersAddresses( family, flags, NULL, pAddresses, &outBufLen );if (dwRetVal == ERROR_BUFFER_OVERFLOW) {HeapFree( GetProcessHeap(), 0, pAddresses );pAddresses = NULL;}else {break;}Iterations++;} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < 3));if (dwRetVal == NO_ERROR) {// If successful, output some information from the data we receivedpCurrAddresses = pAddresses;while (pCurrAddresses) {printf("\tLength of the IP_ADAPTER_ADDRESS struct: %ld\n",pCurrAddresses->Length);printf("\tIfIndex (IPv4 interface): %u\n", pCurrAddresses->IfIndex);printf("\tAdapter name: %s\n", pCurrAddresses->AdapterName);pUnicast = pCurrAddresses->FirstUnicastAddress;if (pUnicast != NULL) {for (i = 0; pUnicast != NULL; i++)pUnicast = pUnicast->Next;printf("\tNumber of Unicast Addresses: %d\n", i);}elseprintf("\tNo Unicast Addresses\n");pAnycast = pCurrAddresses->FirstAnycastAddress;if (pAnycast) {for (i = 0; pAnycast != NULL; i++)pAnycast = pAnycast->Next;printf("\tNumber of Anycast Addresses: %d\n", i);}elseprintf("\tNo Anycast Addresses\n");pMulticast = pCurrAddresses->FirstMulticastAddress;if (pMulticast) {for (i = 0; pMulticast != NULL; i++)pMulticast = pMulticast->Next;printf("\tNumber of Multicast Addresses: %d\n", i);}elseprintf("\tNo Multicast Addresses\n");pDnServer = pCurrAddresses->FirstDnsServerAddress;if (pDnServer) {for (i = 0; pDnServer != NULL; i++)pDnServer = pDnServer->Next;printf("\tNumber of DNS Server Addresses: %d\n", i);}elseprintf("\tNo DNS Server Addresses\n");printf("\tDNS Suffix: %wS\n", pCurrAddresses->DnsSuffix);printf("\tDescription: %wS\n", pCurrAddresses->Description);printf("\tFriendly name: %wS\n", pCurrAddresses->FriendlyName);if (pCurrAddresses->PhysicalAddressLength != 0) {printf("\tPhysical address: ");for (i = 0; i < (int)pCurrAddresses->PhysicalAddressLength;i++) {if (i == (pCurrAddresses->PhysicalAddressLength - 1))printf("%.2X\n",(int)pCurrAddresses->PhysicalAddress[i]);elseprintf("%.2X-",(int)pCurrAddresses->PhysicalAddress[i]);}}printf("\tFlags: %ld\n", pCurrAddresses->Flags);printf("\tMtu: %lu\n", pCurrAddresses->Mtu);printf("\tIfType: %ld\n", pCurrAddresses->IfType);printf("\tOperStatus: %ld\n", pCurrAddresses->OperStatus);printf("\tIpv6IfIndex (IPv6 interface): %u\n",pCurrAddresses->Ipv6IfIndex);printf("\tZoneIndices (hex): ");for (i = 0; i < 16; i++)printf("%lx ", pCurrAddresses->ZoneIndices[i]);printf("\n");printf("\tTransmit link speed: %I64u\n", pCurrAddresses->TransmitLinkSpeed);printf("\tReceive link speed: %I64u\n", pCurrAddresses->ReceiveLinkSpeed);pPrefix = pCurrAddresses->FirstPrefix;if (pPrefix) {for (i = 0; pPrefix != NULL; i++)pPrefix = pPrefix->Next;printf("\tNumber of IP Adapter Prefix entries: %d\n", i);}elseprintf("\tNumber of IP Adapter Prefix entries: 0\n");printf("\n");pCurrAddresses = pCurrAddresses->Next;}}else {printf("Call to GetAdaptersAddresses failed with error: %d\n",dwRetVal);if (dwRetVal == ERROR_NO_DATA)printf("\tNo addresses were found for the requested parameters\n");else {if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,NULL, dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),// Default language(LPTSTR)& lpMsgBuf, 0, NULL)) {printf("\tError: %s", lpMsgBuf);LocalFree(lpMsgBuf);if (pAddresses)HeapFree(GetProcessHeap(), 0, pAddresses);}}}if (pAddresses) {HeapFree(GetProcessHeap(), 0, pAddresses);}return;
}

       根据此处的提示,估计Windows API内部比较喜欢实用HeapAlloc去动态申请堆内存,所以此处决定试试HeapFree去释放放大器组件回调上来的buffer内存。经测试,使用HeapFree是有效的,使用新版本测试,每次发桌面共享停止后不再有明显的内存泄漏了,那基本确定就是没有释放回调出来的buffer内存引起内存泄漏的。

2.4、为啥MagSetImageScalingCallback接口设置的回调函数回调上来的buffer需要外部释放呢?

       在MSDN的MagSetImageScalingCallback函数说明页面:

已经显示MagSetImageScalingCallback函数被微软废弃了,应用程序不要再使用了。但对于桌面图像采集,必须要用到这个接口去设置回调的,通过设置的回调将采集到的桌面图像回调上来的。至于回调函数回调上来的buffer需要外部释放的问题,不知道是否与这点有关系。

2.5、解决了放大器模式下的内存泄漏,但WebRTC相关模块还是有小的内存泄漏

        但详细测试下来,发起桌面共享并停止后还是有轻微的内存泄漏,每次大概泄漏5MB左右,用以前的老版本测试也存在同样的问题,每次泄漏5MB左右。这应该是开源的WebRTC库中有内存泄漏,应该和使用放大器模式没关系,因为老版本的桌面共享使用的不是桌面共享模式。这个5MB的泄漏,可以容忍,不会直接引发问题,但这是个隐患。比如客户长时间电脑不关机,软件长时间运行,如果多次发起桌面共享,内存泄漏会持续累计,比如发起100次桌面共享就会泄漏100*5=500MB的内存,这个影响就比较大了。

       但开源WebRTC内部的代码比较复杂,排查起来比较困难,等后面有时间的时候再去详细研究,目前这个放大器模式引发的明显内存泄漏算是告一段落了。

3、最后

       之前在遇到上述问题时,在网上一直也没搜到有用的信息,这里将使用magnification放大器方式遇到的这两个典型的问题做个详细的总结,以便给大家提供一个借鉴或参考。
    

相关文章:

开源WebRTC库放大器模式在采集桌面图像时遇到的DPI缩放与内存泄漏问题排查

目录 1、在非100%的显示比例下放大器采集到的桌面图像不全问题 1.1、通过manifest文件禁止系统对软件进行缩放 1.2、调用SetThreadDpiAwarenessContext函数&#xff0c;禁止系统对目标线程中的窗口进行缩放 1.3、使用winver命令查看Windows的年月版本 2、使用放大器模式遇…...

敲黑板!java反射机制和原理

获取Class对象&#xff1a;首先&#xff0c;你需要获取表示要操作的类的Class对象。可以使用以下三种方式之一来获取Class对象&#xff1a; Class.forName()方法&#xff1a;使用类的全限定名获取Class对象&#xff0c;例如&#xff1a;Class<? Class<?> clazz MyC…...

【大数据工具】HBase 集群搭建与基本使用

HBase 集群搭建 HBase 安装包下载地址&#xff1a;https://archive.apache.org/dist/hbase/ 安装 HBase 的前提&#xff1a; ZooKeeper 集群 OKHadoop 集群 OK 1. HBase 集群安装 1. 将 HBase 软件包上传至 Hadoop0 解压并重命名 使用 FileZilla 将 hbase-1.3.1-bin.tar.g…...

【Java】数组详解

文章目录 一、数组的基本认识1.1 数组的概念1.2数组的创建与初始化1.3 数组的使用 二、数组的类型 — 引用类型2.1 JVM 内存分布2.2 什么是引用类型2.3 基本类型变量与引用类型变量的区别2.4 Java 中的 null 三、数组的应用3.1 保存数据3.2 函数参数3.3 函数返回值 一、数组的基…...

NumPy库的学习

本文主要记录的是笔者在B站自学Numpy库的学习笔记。 引入numpy库 import numpy as np矩阵的创建 创建一个二行三列的矩阵。 array np.array([[1,2,3],[2,3,4]])查看array的行数、形状、元素数量 print("number of dim:",array.ndim) print("shape:"…...

CentOS安装IRIS

最近电脑提搞了&#xff0c;可以无顾虑创建虚拟机了&#xff0c;试一下在Linux安装IRIS&#xff0c;适用CentOS7.6上安装Intersystem公司的IRIS数据库&#xff0c;资料基本是空白&#xff0c;分享一下。 首先安装解压软件unzip和libicu&#xff0c;最小化安装的缺&#xff0c;…...

华为OD机试真题 JavaScript 实现【最多几个直角三角形】【2023Q1 100分】

一、题目描述 有 N 条线段&#xff0c;长度分别为 a[1]-a[n]。 现要求你计算这 N 条线段最多可以组合成几个直角三角形&#xff0c;每条线段只能使用一次&#xff0c;每个三角形包含三条线段。 二、输入描述 第一行输入一个正整数 T (1< T< 100) &#xff0c;表示有…...

vue3中的reactive、ref、toRef和toRefs

目录 reactivereactive的实现原理使用reactive的注意事项 refref的实现原理使用ref的注意事项 toRef和toRefsref和reactive的使用比较 reactive reactive用于创建响应式对象&#xff0c;它返回一个对象的响应式代理。即&#xff1a;它返回的对象以及其中嵌套的对象都会通过 Pr…...

数字图像处理与Python实现-图像增强经典算法汇总

图像增强经典算法汇总 文章目录 图像增强经典算法汇总1、像素变换2、图像逆变换3、幂律变换4、对数变换5、图像均衡化6、对比度受限自适应直方图均衡(CLAHE)7、对比度拉伸8、Sigmoid校正9、局部对比度归一化10、总结本文将对图像增强经典算法做一个简单的汇总。图像增强的经典…...

tag提示词总结

顺序的权重 越靠前的tag权重越大&#xff0c;越靠后的tag权重越小经验来讲&#xff0c;将图像质量相关的tag放在前面&#xff0c;例如masterpiece&#xff0c;best quality等&#xff1b;接着添加主体画风等&#xff1b;最后添加一些不太重要的细节 权重增减 (tag)&#xff1a…...

微信小程序原生开发功能合集二十:导航栏、tabbar自定义及分包功能介绍

本章实现导航栏及tabbar的自定义处理的相关方法介绍及效果展示。   另外还提供小程序开发基础知识讲解课程,包括小程序开发基础知识、组件封装、常用接口组件使用及常用功能实现等内容,具体如下:    1. CSDN课程: https://edu.csdn.net/course/detail/37977    2. 5…...

高通 Camera HAL3:项目开发技术点总结

做高通 Camera HAL3开发的一些技术点的总结、整理。 做个记录&#xff0c;方便后续查阅。 1.目录、so、配置文件 productName是项目名 out Target路径&#xff1a;\out\target\product\productName\chi-cdk&#xff1a;\vendor\qcom\proprietary\chi-cdk\ldc node&#xff1…...

chatgpt赋能python:Python怎么删除列表中的最大值和最小值

Python怎么删除列表中的最大值和最小值 在Python中&#xff0c;一个列表&#xff08;List&#xff09;是一种非常常见的数据结构&#xff0c;它允许我们以有序的方式存储和访问数据。但是&#xff0c;有时候我们需要从列表中删除最大或最小的值&#xff0c;以满足我们的特定需…...

简述Vue的生命周期以及每个阶段做的事情

03_简述Vue的生命周期以及每个阶段做的事情 思路 给出概念 列举出生命周期各个阶段 阐述整体流程 结合实际 扩展&#xff1a;vue3变化 回答范例 每个vue组件实例被创建后都会经过一系列步骤。比如它需要数据观测、模板编译、挂载实例到dom、以及数据变化的时候更新dom、…...

LeetCode-C#-0004.寻找两个正序数组的中位数

0.声明 该题目来源于LeetCode 如有侵权&#xff0c;立马删除。 解法不唯一&#xff0c;如有新解法可一同讨论。 1.题目 0004寻找两个正序数组的中位数 给定两个大小分别为m和n的正序&#xff08;从小到大&#xff09;数组nums1和nums2。 请你找出并返回着两个正序数组的中位…...

Vue.js 中的 $emit 和 $on 方法有什么区别?

Vue.js 中的 $emit 和 $on 方法有什么区别&#xff1f; 在 Vue.js 中&#xff0c;$emit 和 $on 方法是两个常用的方法&#xff0c;用于实现组件间的通信。它们可以让我们在一个组件中触发一个自定义事件&#xff0c;并在另一个组件中监听这个事件&#xff0c;从而实现组件间的…...

LAZADA平台的商品评论Python封装API接口接入文档和参数说明

LAZADA是一个位于东南亚的电商平台&#xff0c;成立于2012年。该平台覆盖的国家包括新加坡、马来西亚、印尼、菲律宾、泰国和越南等地。它提供了一个多样化的产品选择&#xff0c;包括时尚、美容、数码、母婴等商品&#xff0c;并且拥有许多知名品牌的官方旗舰店。同时&#xf…...

云原生Docker镜像管理

docker是什么&#xff1f; docker是一个go语言开发的应用容器引擎。 docker的作用&#xff1f; ①运行容器里的应用&#xff1b; ②docker是用来管理容器和镜像的一种工具。 #容器 与 虚拟机 的区别&#xff1f; 容器虚拟机所有容器共享宿主机内核每个虚拟机都有独立的操…...

ChatGPT+小红书的8种高级玩法

掌握了这套万能命令&#xff0c;让你快速做出小红书爆款文案! 一、用ChatGPT做定位 我是一个大龄的普通人&#xff0c;没有什么特殊的技能&#xff0c;接下来&#xff0c;请你作为一位小红书的账号定位专家&#xff0c;通过与我对话的方式&#xff0c;为我找到我的小红书账号定…...

shell脚本学习记录1(运算符)

Shell 传递参数 我们可以在执行 Shell 脚本时&#xff0c;向脚本传递参数&#xff0c;脚本内获取参数的格式为&#xff1a;$n。n 代表一个数字&#xff0c;1 为执行脚本的第一个参数&#xff0c;2 为执行脚本的第二个参数&#xff0c;以此类推…… 以下实例我们向脚本传递三个…...

用LVGL玩转嵌入式UI:5个实战控件代码详解(按钮/滑块/图片/标签/开关)

LVGL嵌入式UI开发实战&#xff1a;五大核心控件深度解析与代码优化 在资源受限的嵌入式设备上实现流畅美观的用户界面&#xff0c;一直是开发者面临的挑战。LVGL&#xff08;Light and Versatile Graphics Library&#xff09;作为一款轻量级开源图形库&#xff0c;凭借其丰富的…...

SEO_网站SEO诊断与快速优化解决办法分享

<h2>SEO诊断&#xff1a;了解你的网站现状&#xff0c;为优化铺路</h2> <p>在当今数字化时代&#xff0c;拥有一个高效、优化良好的网站是任何企业或个人成功的关键。网站SEO诊断是这一过程中的重要步骤。通过网站SEO诊断&#xff0c;我们可以全面了解你的网…...

企业Exchange邮箱配置失败?可能是Autodiscover服务出了问题,教你用微软官方工具排查

企业Exchange邮箱自动配置故障深度排查指南 引言 当企业用户或IT管理员遇到Outlook无法自动配置Exchange邮箱的问题时&#xff0c;往往意味着Autodiscover服务出现了异常。作为Exchange生态系统的核心组件&#xff0c;Autodiscover服务负责在客户端与服务器之间建立初始连接通…...

GCC编译选项详解与工程实践指南

GCC编译选项深度解析与工程实践指南1. 编译选项基础概念1.1 编译过程与选项作用GCC编译过程分为预处理、编译、汇编和链接四个阶段。编译选项通过控制这些阶段的行为&#xff0c;实现不同的编译目标&#xff1a;# 完整编译流程示例 gcc -E main.c -o main.i # 预处理 gcc -S…...

如何实现Flomo到Obsidian的高效迁移与无缝衔接?一站式数据迁移工具全解析

如何实现Flomo到Obsidian的高效迁移与无缝衔接&#xff1f;一站式数据迁移工具全解析 【免费下载链接】flomo-to-obsidian Make Flomo Memos to Obsidian Notes 项目地址: https://gitcode.com/gh_mirrors/fl/flomo-to-obsidian 当你需要将积累已久的Flomo笔记迁移到Obs…...

小红书数据采集自动化工具实战:突破反爬限制的零基础搭建指南

小红书数据采集自动化工具实战&#xff1a;突破反爬限制的零基础搭建指南 【免费下载链接】XiaohongshuSpider 小红书爬取 项目地址: https://gitcode.com/gh_mirrors/xia/XiaohongshuSpider 高效数据采集是内容分析与市场研究的基础&#xff0c;但面对小红书等平台的反…...

突破远程桌面限制:RDP Wrapper实现多用户并发连接的创新解决方案

突破远程桌面限制&#xff1a;RDP Wrapper实现多用户并发连接的创新解决方案 【免费下载链接】rdpwrap RDP Wrapper Library 项目地址: https://gitcode.com/gh_mirrors/rd/rdpwrap 副标题&#xff1a;适用于Windows Vista至Windows 11全版本的远程桌面功能扩展工具 在…...

高性能指纹特征提取开源方案:FingerJetFX OSE架构解析与实现指南

高性能指纹特征提取开源方案&#xff1a;FingerJetFX OSE架构解析与实现指南 【免费下载链接】FingerJetFXOSE Fingerprint Feature Extractor; the initial contribution by DigitalPersona is MINEX Compliant (SDK 3F). 项目地址: https://gitcode.com/gh_mirrors/fi/Fing…...

ElasticSearch集群搭建步骤

文章目录一、前言二、使用 RPM 安装 Elasticsearch导入 Elasticsearch GPG 密钥从 RPM 存储库安装三、设置基本安全性生成证书使用TLS加密节点间通信四、为 Elasticsearch 加密 HTTP 客户端通信五、配置集群编辑 elasticsearch.yml&#xff08;通用配置&#xff09;关键性能参数…...

基于S7-200 PLC与组态王的大棚控制系统:产品原理图与IO分配详解

基于S7-200 PLC和组态王温室大棚控制 我们主要的后发送的产品有&#xff0c;带解释的梯形图接线图原理图图纸&#xff0c;io分配&#xff0c;组态画面 菜农张叔上周还给我打电话吐槽&#xff1a;“小王啊&#xff0c;上周那场降温加突然转晴&#xff0c;我三点爬起来盖半层棉被…...