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

DllImport进阶:参数配置与高级主题探究

深入讨论DllImport属性的作用和配置方法

在基础篇中,我们已经简单介绍了DllImport的一些属性。现在我们将深入探讨这些属性的实际应用。

1. EntryPoint

EntryPoint属性用于指定要调用的非托管函数的名称。如果托管代码中的函数名与非托管代码中的函数名不同,可以使用这个属性。例如:

[DllImport("user32.dll", EntryPoint = "MessageBoxW")]
public static extern int ShowMessage(IntPtr hWnd, String text, String caption, uint type);

在这个例子中,我们将非托管函数MessageBoxW映射到托管函数ShowMessage。

2. CallingConvention

CallingConvention属性指定调用约定,它定义了函数如何接收参数和返回值。常见的调用约定包括:

  • CallingConvention.Cdecl:调用者清理堆栈,多用于C/C++库。
  • CallingConvention.StdCall:被调用者清理堆栈,Windows API常用。
  • CallingConvention.ThisCall:用于C++类方法。
  • CallingConvention.FastCall:用于快速调用,较少使用。

示例:

[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool Beep(uint dwFreq, uint dwDuration);

3. CharSet

CharSet属性用于指定字符串的字符集,影响字符串的处理和传递方式。主要选项有:

  • CharSet.Ansi:将字符串作为ANSI编码传递。
  • CharSet.Unicode:将字符串作为Unicode编码传递。
  • CharSet.Auto:根据平台自动选择ANSI或Unicode。

示例:

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

4. SetLastError

SetLastError属性指定是否在调用非托管函数后调用GetLastError。设置为true时,可以使用Marshal.GetLastWin32Error获取错误代码。

示例:

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);public void CloseResource(IntPtr handle)
{if (!CloseHandle(handle)){int error = Marshal.GetLastWin32Error();// 处理错误}
}

5. ExactSpelling

ExactSpelling属性指定是否精确匹配入口点名称。默认情况下,CharSet影响名称查找,设置为true时,关闭字符集查找。

示例:

[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);

6. PreserveSig

PreserveSig属性指定是否保留方法签名的HRESULT返回类型。默认值为true。当设置为false时,HRESULT会转换为异常。

示例:

[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoCreateGuid(out Guid guid);

7. BestFitMapping 和 ThrowOnUnmappableChar

BestFitMapping属性控制是否启用ANSI到Unicode的最佳映射。ThrowOnUnmappableChar指定是否在遇到无法映射的字符时抛出异常。

示例:

[DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern bool SetEnvironmentVariable(string lpName, string lpValue);

实践示例

下面是一个综合使用多个DllImport属性的示例:

using System;
using System.Runtime.InteropServices;class Program
{[DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]public static extern int ShowMessageBox(IntPtr hWnd, String text, String caption, uint type);static void Main(){int result = ShowMessageBox(IntPtr.Zero, "Hello, World!", "Hello Dialog", 0);if (result == 0){int error = Marshal.GetLastWin32Error();Console.WriteLine($"Error: {error}");}}
}

在这个例子中,我们使用了EntryPoint、CharSet、SetLastError和CallingConvention属性来精确配置MessageBox函数的调用。

深入理解和正确配置DllImport属性可以帮助我们更高效地调用非托管代码,确保数据类型和调用约定的匹配,处理潜在的错误和异常,提升代码的稳定性和安全性。

探讨数据类型匹配的重要性

在C#中通过DllImport调用非托管代码时,数据类型的匹配是确保代码正确执行的关键因素之一。正确的数据类型匹配能够避免数据损坏、内存泄漏和程序崩溃等问题。

1. 数据类型匹配的重要性

  • 避免数据损坏:非托管代码和托管代码的数据类型必须一致,否则传递的数据可能会损坏。例如,将一个32位的整数传递给一个预期为64位整数的非托管函数会导致数据截断或损坏。
  • 防止程序崩溃:不匹配的数据类型可能会导致非托管代码访问非法内存地址,进而导致程序崩溃。
  • 确保数据完整性:正确的数据类型匹配可以确保数据在托管代码和非托管代码之间正确传递,保持数据的完整性。
  • 提高代码安全性:数据类型的不匹配可能会引入安全漏洞,导致潜在的缓冲区溢出等安全问题。

2. 基本数据类型的匹配

基本数据类型在托管代码和非托管代码之间的匹配非常重要。以下是常见数据类型的匹配示例:

  • 整数类型
    • C#中的int通常对应C/C++中的int或LONG类型:
[DllImport("Example.dll")] 
public static extern int Add(int a, int b);
  • 无符号整数类型
    • C#中的uint通常对应C/C++中的unsigned int或DWORD类型:
[DllImport("Example.dll")]
public static extern uint GetTickCount();
  • 长整数类型
    • C#中的long对应C中的long long或__int64类型:
[DllImport("Example.dll")] 
public static extern long Multiply(long a, long b);
  • 指针类型
    • C#中的IntPtr或UIntPtr对应C中的指针类型,如void*或HANDLE:
[DllImport("Example.dll")] 
public static extern IntPtr OpenHandle(uint access);
  • 布尔类型
    • C#中的bool对应C中的BOOL类型,需要注意的是,C/C++中的BOOL通常定义为int,而C#中的bool是1字节。
[DllImport("Example.dll")] [return: MarshalAs(UnmanagedType.Bool)] 
public static extern bool CloseHandle(IntPtr handle);

3. 复杂数据类型的匹配

对于结构体、数组和字符串等复杂数据类型的匹配,需要特别注意。

  • 结构体
    • 结构体需要在托管代码和非托管代码中保持一致,并使用StructLayout属性进行布局控制:
[StructLayout(LayoutKind.Sequential)] 
public struct Point { public int X; public int Y; } [DllImport("Example.dll")] 
public static extern void GetPoint(out Point p);
  • 数组
    • 数组的匹配需要使用MarshalAs属性指定数组的类型和大小:
[DllImport("Example.dll")]
public static extern void FillArray([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] int[] array);
  • 字符串
    • 字符串的匹配需要注意字符集的选择(如CharSet.Ansi或CharSet.Unicode):
[DllImport("Example.dll", CharSet = CharSet.Unicode)] 
public static extern void PrintMessage(string message);

4. 数据类型匹配的常见问题及解决方法

  • 字符集不匹配:在传递字符串时,如果字符集不匹配,可能会导致字符串被截断或乱码。解决方法是在DllImport特性中明确指定字符集:
[DllImport("Example.dll", CharSet = CharSet.Unicode)] 
public static extern void PrintMessage(string message);
  • 指针类型不匹配:非托管代码中的指针类型应对应C#中的IntPtr或UIntPtr:
[DllImport("Example.dll")] 
public static extern IntPtr AllocateMemory(uint size);
  • 结构体布局不匹配:如果结构体在内存中的布局不同,可能会导致数据损坏。解决方法是使用StructLayout属性确保一致的内存布局:
[StructLayout(LayoutKind.Sequential)] 
public struct Point { public int X; public int Y; }
  • 数组边界问题:传递数组时,应确保数组的大小匹配,避免越界访问:
[DllImport("Example.dll")] 
public static extern void ProcessArray([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] int[] array);

讨论内存管理的重要性

在调用非托管代码时,内存管理是一个不可忽视的重要环节。非托管代码不受.NET垃圾回收器的管理,因此需要开发人员手动分配和释放内存。这不仅涉及到如何正确使用内存,还包括如何避免内存泄漏和其他潜在问题。

1. 内存管理的重要性

  • 防止内存泄漏:手动分配的内存如果不正确释放,会导致内存泄漏,逐渐消耗系统资源。
  • 确保数据安全:未正确管理的内存可能会被覆盖或误用,导致数据损坏和程序崩溃。
  • 提高程序性能:高效的内存管理能够减少内存使用,提升程序性能。

2. 内存分配和释放

在非托管代码中,内存通常使用malloc、calloc等函数分配,并使用free函数释放。在托管代码中,我们可以使用Marshal类提供的方法来分配和释放非托管内存。

  • 分配内存Marshal.AllocHGlobal:分配指定字节数的非托管内存。Marshal.AllocCoTaskMem:分配任务内存,适用于COM互操作。
IntPtr ptr = Marshal.AllocHGlobal(100); // 分配100字节的内存
// 使用ptr进行操作
Marshal.FreeHGlobal(ptr); // 释放内存
  • 释放内存使用Marshal.FreeHGlobal或Marshal.FreeCoTaskMem释放之前分配的内存。
IntPtr ptr = Marshal.AllocCoTaskMem(100);
// 使用ptr进行操作
Marshal.FreeCoTaskMem(ptr); // 释放内存

3. 内存拷贝

在托管代码和非托管代码之间传递数据时,可能需要进行内存拷贝。Marshal类提供了一些方法用于内存拷贝:

  • Marshal.Copy:用于从托管数组复制到非托管内存,或从非托管内存复制到托管数组。
  • Marshal.StructureToPtr:将托管结构复制到非托管内存。
  • Marshal.PtrToStructure:将非托管内存的数据复制到托管结构。
int[] managedArray = new int[10];
IntPtr unmanagedArray = Marshal.AllocHGlobal(managedArray.Length * sizeof(int));Marshal.Copy(managedArray, 0, unmanagedArray, managedArray.Length);
// 使用unmanagedArray进行操作
Marshal.Copy(unmanagedArray, managedArray, 0, managedArray.Length);Marshal.FreeHGlobal(unmanagedArray);

4. 处理非托管资源

调用非托管代码时,可能会使用非托管资源(如文件句柄、窗口句柄等),这些资源也需要正确管理以避免资源泄漏。

  • 关闭句柄使用CloseHandle或类似的API来关闭非托管资源。
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);public void CloseResource(IntPtr handle)
{if (!CloseHandle(handle)){int error = Marshal.GetLastWin32Error();// 处理错误}
}

5. 管理生命周期

对于需要频繁分配和释放内存的操作,可以考虑封装内存管理逻辑,确保内存能够正确释放。

public class UnmanagedBuffer : IDisposable
{private IntPtr buffer;private bool disposed = false;public UnmanagedBuffer(int size){buffer = Marshal.AllocHGlobal(size);}~UnmanagedBuffer(){Dispose(false);}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (!disposed){if (buffer != IntPtr.Zero){Marshal.FreeHGlobal(buffer);buffer = IntPtr.Zero;}disposed = true;}}public IntPtr Buffer => buffer;
}

6. 内存管理最佳实践

  • 始终释放内存:确保所有分配的内存都在适当的时候释放,防止内存泄漏。
  • 使用智能指针或封装类:封装内存管理逻辑,减少手动管理的复杂性。
  • 定期检查内存使用:使用工具和代码分析,确保没有未释放的内存。

实践示例

以下是一个综合示例,展示了内存分配、内存拷贝和资源管理的完整流程:

C++部分代码:PointManager.h和PointManager.cpp两个文件

#pragma once#ifdef EXAMPLE_EXPORTS
#define EXAMPLE_API __declspec(dllexport)
#else
#define EXAMPLE_API __declspec(dllimport)
#endifstruct Point
{int X;int Y;
};extern "C" EXAMPLE_API Point* CreatePoint(int x, int y);
extern "C" EXAMPLE_API void GetPoint(Point * point, Point * pOut);
extern "C" EXAMPLE_API void DeletePoint(Point * point);
#include "pch.h"
#include "PointManager.h"// 创建一个新的 Point 对象并返回其指针
extern "C" __declspec(dllexport) Point* CreatePoint(int x, int y)
{Point* p = new Point();p->X = x;p->Y = y;return p;
}// 获取 Point 对象的值
extern "C" __declspec(dllexport) void GetPoint(Point * point, Point * pOut)
{if (point == nullptr || pOut == nullptr){SetLastError(ERROR_INVALID_PARAMETER);return;}pOut->X = point->X;pOut->Y = point->Y;
}// 删除 Point 对象
extern "C" __declspec(dllexport) void DeletePoint(Point * point)
{if (point != nullptr){delete point;}
}

C#部分代码:

using System;
using System.Runtime.InteropServices;class Program
{[StructLayout(LayoutKind.Sequential)]public struct Point{public int X;public int Y;}[DllImport("Example.dll", SetLastError = true)]public static extern IntPtr CreatePoint(int x, int y);[DllImport("Example.dll", SetLastError = true)]public static extern void GetPoint(IntPtr point, out Point p);[DllImport("Example.dll", SetLastError = true)]public static extern void DeletePoint(IntPtr point);static void Main(){IntPtr pointPtr = CreatePoint(10, 20);if (pointPtr == IntPtr.Zero){int error = Marshal.GetLastWin32Error();Console.WriteLine($"Error: {error}");return;}Point p;GetPoint(pointPtr, out p);Console.WriteLine($"Point: {p.X}, {p.Y}");DeletePoint(pointPtr);}
}

这个示例展示了如何在非托管代码中创建和管理内存资源,并在托管代码中正确分配和释放内存。

参考文档

使用非托管 DLL 函数 - .NET Framework | Microsoft Learn

标识 DLL 中的函数 - .NET Framework | Microsoft Learn


DllImportAttribute.EntryPoint 字段 (System.Runtime.InteropServices) | Microsoft Learn

原文链接:https://www.toutiao.com/article/7383720159233573427/?app=news_article&timestamp=1719443240&use_new_style=1&req_id=2024062707072029288E0C168B30524880&group_id=7383720159233573427&share_token=27F51CAD-7939-4769-90A3-0F26B5F997C4&tt_from=weixin&utm_source=weixin&utm_medium=toutiao_ios&utm_campaign=client_share&wxshare_count=1&source=m_redirect&wid=1719451374672

相关文章:

DllImport进阶:参数配置与高级主题探究

深入讨论DllImport属性的作用和配置方法 在基础篇中,我们已经简单介绍了DllImport的一些属性。现在我们将深入探讨这些属性的实际应用。 1. EntryPoint EntryPoint属性用于指定要调用的非托管函数的名称。如果托管代码中的函数名与非托管代码中的函数名不同&#…...

HTTP与HTTPS协议区别及应用场景

HTTP(超文本传输​​协议)和 HTTPS(安全超文本传输​​协议)都是用于通过网络传输数据的协议。虽然它们有一些相似之处,但在安全性和数据保护方面也存在显著差异。 在这篇博文中,我们将探讨 HTTP 和 HTTPS…...

Vue2-Vue Router前端路由实现思路

1.路由是什么? Router路由器:数据包转发设备,路由器通过转发数据包(数据分组)来实现网络互连 Route路由:数据分组从源到目的地时,决定端到端路径的网络范围的进程 | - 网络层 Distribute分发…...

2024 年 亚太赛 APMCM (C题)中文赛道国际大学生数学建模挑战赛 | 量子计算的物流配送 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时,你是否曾经感到茫然无措?作为2022年美国大学生数学建模比赛的O奖得主,我为大家提供了一套优秀的解题思路,让你轻松应对各种难题! 完整内容可以在文章末尾领取! 该段文字…...

客观分析-自己和本科学生之间的差距

进入专科学校和与985、211等重点本科院校学生之间的差距可能由多种因素造成,这些因素可能包括但不限于: 1. **入学标准**: 985和211工程院校通常有更高的入学标准和更严格的选拔过程。 你得使你自己适应更高的入学标准和更严格的选拔过程&am…...

清华镜像源

python在安装各种库的时候为了下载速度快,经常使用镜像源,下面是使用清华镜像源案例。其中的 xxx 表示要安装的库,如 requests。 pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple 安装requests案例:pip install r…...

大语言模型测评工具-ChatHub和ChatAll

背景 现在国内外拥有上百个大语言模型,在AI业务中,我们需要在其中选择一个合适业务模型,就需要对这些模型进行测试。手工去测试这么多模型效率一定不高,今天就介绍两个提高测评模型效率的工具 ChatHub和ChatAll。 介绍 ChatHub…...

使用redis分布式锁,不要把锁放在本地事务内部

在使用分布式锁的时候,习惯性的尽量缩小同步代码块的范围。 但是如果数据库隔离级别是可重复读,这种情况下不要把分布式锁加在Transactional注解的事务方法内部。 因为可能会出现这种情况: 线程1开启事务A后获取分布式锁,执行业务代码后在事务内释放了分布式锁。…...

Python学生信息管理系统(完整代码)

引言:(假装不是一个大学生课设)在现代教育管理中,学生管理系统显得尤为重要。这种系统能够帮助教育机构有效地管理学生资料、成绩、出勤以及其他教育相关活动,从而提高管理效率并减少人为错误。通过使用Python&#xf…...

【大功率汽车大灯升压方案】LED恒流驱动芯片FP7208升压车灯调光应用,PWM内部转模拟,调光深度1%,无频闪顾虑,低亮无抖动

文章目录 前言 一、LED车灯的内部组成结构 二、驱动板详解 三、FP7208芯片介绍 芯片参数 总结 前言 近年来,汽车市场飞速发展,车灯作为汽车重要的组成部分,也得到了广泛的关注。车灯对于汽车不仅是外观件更是汽车主动安全的重要组成部…...

uniapp应用如何实现传感器数据采集和分析

UniApp是一种跨平台的应用开发框架,它支持在同一份代码中同时开发iOS、Android、H5等多个平台的应用。在UniApp中实现传感器数据采集和分析的过程可以分为以下几个步骤: 引入相关插件或库 UniApp通过插件或库的形式扩展功能。对于传感器数据采集和分析&…...

读书笔记-Java并发编程的艺术-第3章(Java内存模型)-第6节(final域的内存语义)

文章目录 3.6 final域的内存语义3.6.1 final 域的重排序规则3.6.2 写final 域的重排序规则3.6.3 读final 域的重排序规则3.6.4 final 域为引用类型3.6.5 为什么 final 引用不能从构造函数内“逸出”3.6.6 final 语义在处理器中的实现3.6.7 JSR-133 为什么要增强final 的语义 3.…...

Spring AI 1.0.0 新变化,从 0.8.1 如何升级

Spring AI 1.0.0-M1 版本已经发布,距离 1.0.0 正式版又更近了一步。同时这也意味着,Spring AI 1.0.0 的 API 已经基本确定,不会发生大的改动。这里介绍一下,相对于上一个发布版本 0.8.1,Spring AI 1.0.0 的一些重要的变…...

【机器学习】FFmpeg+Whisper:二阶段法视频理解(video-to-text)大模型实战

目录 一、引言 二、FFmpeg工具介绍 2.1 什么是FFmpeg 2.2 FFmpeg核心原理 2.3 FFmpeg使用示例 三、FFmpegWhisper二阶段法视频理解实战 3.1 FFmpeg安装 3.2 Whisper模型下载 3.3 FFmpeg抽取视频的音频 3.3.1 方案一:命令行方式使用ffmpeg 3.3.2 方案二&a…...

Java中继承接口和实现接口的区别、接口和抽象类的区别、并理解关键字interface、implements

初学者容易把继承接口和实现接口搞混,专门整理一下,顺便简单介绍一下interface、implements关键字。 继承接口和实现接口的区别、接口的特点 继承接口是说的只有接口才可以继承接口,是接口与接口间的。实现接口是说的接口与类之间&#xff…...

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本 1.演示动画A.视频B.gif动画 2.跟踪鼠标区域的录屏脚本 Excel中有一组数据,希望画出曲线,并且能把均值线也绘制在图上,以下动画演示了整个过程,并且提供了区域录屏脚本,原理如下: 为节约空间,避免…...

易保全推动区块链应用与AI融合创新发展

数字化时代,区块链和人工智能技术作为当下两大“黑科技”,两者的深度结合,正在为企业数字化转型带来前所未有的机遇。 易保全作为国内权威的电子数据存证保全机构,积极探索两者的融合之道,将区块链的去中心化、不可篡…...

C++(Python)肥皂泡沫普拉托边界膜曲面模型算法

🎯要点 🎯肥皂泡二维流体模拟 | 🎯泡沫普拉托边界膜曲面模型算法演化厚度变化 | 🎯螺旋曲面三周期最小结构生成 📜皂膜用例:Python计算物理粒子及拉格朗日和哈密顿动力学 | Python和MATLAB粘性力接触力动…...

VBA打开其他Excel文件

前言 本节会介绍通过VBA实现打开其他excel文件,包括模糊匹配文件名称、循环同时打开多个文件,并获取工作表及工作簿进行数据操作后,对打开的文件进行保存并关闭操作。 一、打开固定文件名称的文件 场景说明: 1.新建一个宏文件VBA…...

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时,而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定,则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定,以便没有错误…...

tls各个版本的安全性介绍

TLS(Transport Layer Security)协议的各个版本在安全性方面经历了逐步的演进和改进,以应对不断变化的网络安全威胁。以下是各主要版本的安全性概览: TLS 1.0: 发布于1999年,是SSL 3.0的后续版本。在其发布时…...

PHP家政服务预约单开版微信小程序系统源码

🏠 —— 便捷生活,从指尖开始💪 🌈【开篇:家政新风尚,一键触达】 在忙碌的生活节奏中,你是否渴望拥有一个温馨、整洁的家,却又苦于找不到合适的家政服务?现在&#xff…...

数据增强:目标检测算法中的性能提升利器

引言 目标检测是计算机视觉领域的核心任务之一,旨在从图像或视频中识别和定位感兴趣的对象。然而,由于训练数据的局限性,目标检测模型往往面临过拟合和泛化能力不足的问题。数据增强作为一种有效的解决方案,通过增加数据多样性来…...

KVB交易平台 :市场迎来新热潮!铜价会持续上涨吗?

近期,全球铜价出现明显上涨趋势。韩国光阳LME仓库的铜库存显著下降,市场对即时需求的增加作出了积极反应。供应端的紧张和需求端的复苏共同推动了铜价的上涨。 KVB外汇 分析师们对未来铜价保持谨慎乐观态度,认为长期内铜价有望保持稳定甚至进…...

React@16.x(44)路由v5.x(9)源码(1)- path-to-regexp

目录 1,作用2,实现获取 match 对象2.1,match 对象的内容2.2,注意点2.3,实现 1,作用 之前在介绍 2.3 match 对象 时,提到了 react-router 使用第3方库 path-to-regexp 来匹配路径正则。 我们也…...

C#面:String str=new String(“a“)和String str = “a“有什么区别

String str new String(“a”)和String str “a”的区别在于对象的创建方式和内存分配方式。 字符串 str new String(“a”): 使用new关键字显式地创建了一个新的String对象。 每次执行这行代码时,都会…...

CS算法(二)—— 斜视SAR点目标仿真

SAR成像专栏目录 我们按照Cumming教授所著的《合成孔径雷达成像——算法与实现》7.6节的点目标参数进行仿真,斜视角设置为8,中心斜距改为1000km。先放最终的仿真结果: 1. 参数配置 在中心点和中心的的上下左右方向设置5个点目标 : function para=config_sar_para_cumming(…...

2024亚洲国际餐饮展览会(北京餐饮展|火锅展|预制菜展会)

2024北京餐饮展会,2024北京食材展会,2024北京火锅展会,2024北京火锅食材展会,2024北京预制菜展会,2024北京预制食材展会, 2024亚洲国际餐饮展览会(北京餐饮展|火锅展|预制菜展会) …...

【RabbitMQ问题踩坑】RabbitMQ设置手动ack后,消息队列有多条消息,只能消费一条,就不继续消费了,这是为什么 ?

现象:我发送5条消息到MQ队列中,同时,我在yml中设置的是需要在代码中手动确认,但是我把代码中的手动ack给关闭了,会出现什么情况? yml中配置,配置需要在代码中手动去确认消费者消费消息成功&…...

深度解码:需求跟踪的艺术与实战应用

文章目录 引言一、需求跟踪的定义二、需求跟踪矩阵2.1 需求跟踪矩阵包含的内容2.2 跟踪矩阵层级2.3 需求属性2.4 参考表格 三、需求跟踪的收益3.1 确保商业价值最大化3.2 满足客户期望3.3 范围管理3.4 决策支持3.5 提高效率和效果3.6 文档化和沟通3.7 变更管理3.8 测量和改进 四…...