开源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++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)
https://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++程序中的内存泄漏https://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函数,禁止系统对目标线程中的窗口进行缩放 1.3、使用winver命令查看Windows的年月版本 2、使用放大器模式遇…...
敲黑板!java反射机制和原理
获取Class对象:首先,你需要获取表示要操作的类的Class对象。可以使用以下三种方式之一来获取Class对象: Class.forName()方法:使用类的全限定名获取Class对象,例如:Class<? Class<?> clazz MyC…...

【大数据工具】HBase 集群搭建与基本使用
HBase 集群搭建 HBase 安装包下载地址:https://archive.apache.org/dist/hbase/ 安装 HBase 的前提: 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
最近电脑提搞了,可以无顾虑创建虚拟机了,试一下在Linux安装IRIS,适用CentOS7.6上安装Intersystem公司的IRIS数据库,资料基本是空白,分享一下。 首先安装解压软件unzip和libicu,最小化安装的缺,…...

华为OD机试真题 JavaScript 实现【最多几个直角三角形】【2023Q1 100分】
一、题目描述 有 N 条线段,长度分别为 a[1]-a[n]。 现要求你计算这 N 条线段最多可以组合成几个直角三角形,每条线段只能使用一次,每个三角形包含三条线段。 二、输入描述 第一行输入一个正整数 T (1< T< 100) ,表示有…...
vue3中的reactive、ref、toRef和toRefs
目录 reactivereactive的实现原理使用reactive的注意事项 refref的实现原理使用ref的注意事项 toRef和toRefsref和reactive的使用比较 reactive reactive用于创建响应式对象,它返回一个对象的响应式代理。即:它返回的对象以及其中嵌套的对象都会通过 Pr…...
数字图像处理与Python实现-图像增强经典算法汇总
图像增强经典算法汇总 文章目录 图像增强经典算法汇总1、像素变换2、图像逆变换3、幂律变换4、对数变换5、图像均衡化6、对比度受限自适应直方图均衡(CLAHE)7、对比度拉伸8、Sigmoid校正9、局部对比度归一化10、总结本文将对图像增强经典算法做一个简单的汇总。图像增强的经典…...
tag提示词总结
顺序的权重 越靠前的tag权重越大,越靠后的tag权重越小经验来讲,将图像质量相关的tag放在前面,例如masterpiece,best quality等;接着添加主体画风等;最后添加一些不太重要的细节 权重增减 (tag):…...

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

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

chatgpt赋能python:Python怎么删除列表中的最大值和最小值
Python怎么删除列表中的最大值和最小值 在Python中,一个列表(List)是一种非常常见的数据结构,它允许我们以有序的方式存储和访问数据。但是,有时候我们需要从列表中删除最大或最小的值,以满足我们的特定需…...

简述Vue的生命周期以及每个阶段做的事情
03_简述Vue的生命周期以及每个阶段做的事情 思路 给出概念 列举出生命周期各个阶段 阐述整体流程 结合实际 扩展:vue3变化 回答范例 每个vue组件实例被创建后都会经过一系列步骤。比如它需要数据观测、模板编译、挂载实例到dom、以及数据变化的时候更新dom、…...
LeetCode-C#-0004.寻找两个正序数组的中位数
0.声明 该题目来源于LeetCode 如有侵权,立马删除。 解法不唯一,如有新解法可一同讨论。 1.题目 0004寻找两个正序数组的中位数 给定两个大小分别为m和n的正序(从小到大)数组nums1和nums2。 请你找出并返回着两个正序数组的中位…...

Vue.js 中的 $emit 和 $on 方法有什么区别?
Vue.js 中的 $emit 和 $on 方法有什么区别? 在 Vue.js 中,$emit 和 $on 方法是两个常用的方法,用于实现组件间的通信。它们可以让我们在一个组件中触发一个自定义事件,并在另一个组件中监听这个事件,从而实现组件间的…...
LAZADA平台的商品评论Python封装API接口接入文档和参数说明
LAZADA是一个位于东南亚的电商平台,成立于2012年。该平台覆盖的国家包括新加坡、马来西亚、印尼、菲律宾、泰国和越南等地。它提供了一个多样化的产品选择,包括时尚、美容、数码、母婴等商品,并且拥有许多知名品牌的官方旗舰店。同时…...

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

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

shell脚本学习记录1(运算符)
Shell 传递参数 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推…… 以下实例我们向脚本传递三个…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...