C++函数在库中的地址

本文讲述C++如何直接调用动态库dll或者so中的函数。

首先我们准备一个被调用库,这个库里面有两个函数,分别是C++98 与 C++11 下的,名称是run2和run1。

被调用库

相关介绍请看之前的文章《函数指针与库之间的通信讲解》。

//dll_ex_im.h
#ifndef __DLL_EX_IM_H__
#define __DLL_EX_IM_H__
#include <functional>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
#ifdef _WINDOWS
#ifdef DLLProvider
#define DLL_EXPORT_IMPORT		 __declspec(dllexport)  
#else  
#define DLL_EXPORT_IMPORT		 __declspec(dllimport)  
#endif  
#else
#define DLL_EXPORT_IMPORT __attribute__((visibility("default")))
#endif

//typedef class DLL_EXPORT_IMPORT std::function< void(std::string)> output_to_caller;
DLL_EXPORT_IMPORT void run1(int a, std::function< void(std::string)> output);
DLL_EXPORT_IMPORT void run2(int a, void(*output)(std::string));

#endif //__DLL_EX_IM_H__
//dll_ex_im.cpp
#include "dll_ex_im.h"

void run1(int a, std::function< void(std::string)> output)
{
	std::cout << "run1" << std::endl;
	std::cout << "got parametrer: " << a << std::endl;
	std::cout << "return signal: " << std::endl;
	while (true) {
		output("run1: " + std::to_string(a++));
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
}

void run2(int a, void(*output)(std::string)) {
	std::cout << "run2" << std::endl;
	std::cout << "got parametrer: " << a << std:bian

编译一下,出来的动态库在Windows下是Reflection-DLL_TEST.dll,在Linux下是libReflection-DLL_TEST.so。

Windows下直接调用

我们用BinaryViewer这款二进制查看器看看函数run1和run2在Reflection-DLL_TEST.dll长什么样子。

查找函数run1的位置:

找到三个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前两个是做啥的)。
第三个是run1函数的地址:
在这里插入图片描述
我把这个run1地址写下来:

?run1@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z

原来的函数run1长这样:

void run1(int a, std::function< void(std::string)> output);

查找函数run2的位置:

找到三个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前两个是做啥的)。
第三个是run2函数的地址:
在这里插入图片描述
我把这个run2地址写下来:

?run2@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z

原来的函数run2长这样:

void run2(int a, void(*output)(std::string));

地址解释

以上run1和run2函数在dll二进制文件中的地址是一个经过VS C++编译器名称修饰后的函数名,这种修饰是编译器用来区分具有相同名称但不同参数或返回类型的函数的方法。
由于我不是做编译器的,下面我用聊天机器人查了一下,仅给出以上run1函数地址的解释:
?run1@@:这是函数名run1的开头部分,其中?和@@是Microsoft编译器特有的名称修饰符号。
YAXH:这表示函数的返回类型和参数类型。在这个例子中,Y 表示返回类型为 void,AX 通常表示没有通过值传递的参数(但在这个特定情况下,由于后面有V,它实际上表示有一个通过引用或指针传递的参数),H 是参数列表的开始。不过,这里的AX和H的具体含义可能因编译器的具体实现而略有不同,重要的是理解整体结构。
V:这表示接下来的部分是一个通过引用或指针传递的参数。
?$function@…@std@@:这是对 std::function 模板的修饰表示,其中省略号(…)代表模板参数的具体类型,即 void(std::string)。
KaTeX parse error: Can't use function '$' in math mode at position 7: A6AXV?$̲basic_string@..…A6 是与调用约定相关的(可能是 __cdecl 的某种变体,但具体取决于编译器和平台),AXV 表示函数接受一个参数(V 表示通过引用或指针),?$basic_string@…@std@@@Z 是对 std::string 类型的修饰表示。

Windows调用程序

#include <iostream>
//#include <list>
#include <functional>
#ifdef _WINDOWS 
#include <shlwapi.h>
#include <Psapi.h> 
#include <codecvt> 
#else
#include <dlfcn.h>
#include <codecvt>
#endif

void callback(std::string info) {
	std::cout << info << std::endl;
}


void Run1(const std::string& dllpath, const std::string& initFuncName)
{
	std::string funName = initFuncName;
#ifdef _WINDOWS
	typedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);
	funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";
	auto module = LoadLibrary(dllpath.c_str());
	//寻找初始化函数,并执行
	pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());
	if (initfunc)
	{
		//常规方式
		//std::function< void(std::string)> cb = callback;
		//initfunc(4, cb);
		//lambda方式
		initfunc(4, [](std::string info) {
			std::cout << info << std::endl; 
			}
		);
	}
	else {
		std::cout << "未找到函数地址" << std::endl;
	}
#else
	typedef  void(__attribute__((__stdcall__))* pfnInitPlugin) (int, std::function< void(std::string));
	funName = "_Z22" + funName + "v";
	auto dp = dlopen(p.c_str(), RTLD_LAZY | RTLD_GLOBAL);
	if (dp)
	{
		pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());
		if (initfunc)
		{
			initfunc(4, [](std::string info) {
				std::cout << info << std::endl;
				}
			);
		}
		//dlclose(dp);
	}
	else {
		std::cout << "未找到函数地址" << std::endl;
	}
#endif
}

void Run2(const std::string& dllpath, const std::string& initFuncName)
{
	std::string funName = initFuncName;
#ifdef _WINDOWS
	typedef void(_stdcall* pfnInitPlugin) (int, void(*output)(std::string));
	//run1
	funName = "?" + funName + "@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z";
	auto module = LoadLibrary(dllpath.c_str());
	//寻找初始化函数,并执行
	pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());
	if (initfunc)
	{
		//void(*cb)(std::string);
		//cb = callback;
		//initfunc(4, cb);
		initfunc(4, [](std::string info) {
			std::cout << info << std::endl;
			}
		);
	}
	else {
		std::cout << "未找到函数地址" << std::endl;
	}
#else
	typedef  void(__attribute__((__stdcall__))* pfnInitPlugin) (int, void(*output)(std::string));
	funName = "_Z22" + funName + "v";
	auto dp = dlopen(p.c_str(), RTLD_LAZY | RTLD_GLOBAL);
	if (dp)
	{
		pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());
		if (initfunc)
		{
			initfunc(4, [](std::string info) {
				std::cout << info << std::endl;
				}
			);
		}
		//dlclose(dp);
	}
	else {
		std::cout << "未找到函数地址" << std::endl;
	}
#endif
}

int main(int argc, char* argv[]) {
	std::string argv1 = argv[1];
	if (argc == 2 && argv1 == "-h")
	{
		std::cout << "用法:\n【exe-name】【dll-path】【func-name】" << std::endl;
		return 0;
	}
	if (argc != 3)
	{
		std::cerr << "传入的参数数量不对,应该是俩,检查检查!!" << std::endl;
		return -1;
	}
	std::string dllPath = argv[1];
	if (dllPath.find(".dll") == dllPath.npos)
	{
		std::cerr << "传入的文件没有dll,检查检查!!" << std::endl;
		return -1;
	}
	std::string argv2 = argv[2];
	if (argv2 == "run1")
	{
		Run1(argv[1], "run1");
	}
	else if (argv2 == "run2") {
		Run2(argv[1], "run2");
	}
	else {
		std::cerr << "传入的函数名既不是 run1 也不是 run2 ,检查检查!!" << std::endl;
		return -1;
	}

	return 0;
}

在Windows下,核心代码是下面这几句:

	typedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);
	//run1
	funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";

	auto module = LoadLibrary(dllpath.c_str());
	//寻找初始化函数,并执行
	pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());
	if (initfunc)
	{
		//常规方式
		//std::function< void(std::string)> cb = callback;
		//initfunc(4, cb);
		//lambda方式
		initfunc(4, [](std::string info) {
			std::cout << info << std::endl; 
			}
		);
	}
	else {
		std::cout << "未找到函数地址" << std::endl;
	}

程序读入动态库,通过函数在动态库中的地址进行直接调用。
下面是调用结果:
在这里插入图片描述

Linux下直接调用

我们用BinaryViewer这款二进制查看器看看函数run1和run2在libReflection-DLL_TEST.so长什么样子。

查找函数run1的位置:

找到4个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前3个是做啥的)。
第4个是run1函数的地址:
在这里插入图片描述
我把这个run1地址写下来:

_Z4run1iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE

原来的函数run1长这样:

void run1(int a, std::function< void(std::string)> output);

查找函数run2的位置:

找到4个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前3个是做啥的)。
第4个是run2函数的地址:
在这里插入图片描述
我把这个run2地址写下来:

_Z4run2iPFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

原来的函数run2长这样:

void run2(int a, void(*output)(std::string));

地址解释

以上run1和run2函数在so二进制文件中的地址是一个由 GCC编译器生成的 mangled(修饰)名称。这种名称用于在编译后的代码中唯一标识函数、变量等符号,同时包含类型信息。Mangled 名称对于人类来说通常是不直观的,但它们对于编译器和链接器来说是必要的,以确保在复杂的程序中正确地解析和链接符号。
由于我不是做编译器的,下面我用聊天机器人查了一下,仅给出以上run1函数地址的解释:
_Z 前缀是 GCC 编译器用于 mangled 名称的标识。
4run1i 部分是函数名称的编码,其中 run 是函数名,1 表示该函数接受一个参数,i 表示该参数的类型(在这个上下文中,它实际上是指接下来的类型信息,而不是直接的类型)。
St8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE 是参数类型的 mangled 表示。这个类型是一个 std::function,它包装了一个可调用对象,该对象接受一个 std::string 类型的参数(没有返回值,因为 Fv 表示一个函数类型,没有返回类型)。
St8function 表示 std::function。
IFv 表示一个函数(F)没有返回值(v,即 void),并且接下来是参数类型。
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE 是 std::__cxx11::basic_string<char, std::char_traits, std::allocator> 的 mangled 表示,即 std::string。

Linux调用程序

#include <iostream>
//#include <list>
#include <functional>
#ifdef _WINDOWS 
#include <shlwapi.h>
#include <Psapi.h> 
#include <codecvt> 
#else
#include <dlfcn.h>
#include <codecvt>
#endif

void callback(std::string info) {
	std::cout << info << std::endl;
}


void Run1(const std::string& dllpath, const std::string& initFuncName)
{
	std::string funName = initFuncName;
#ifdef _WINDOWS
	typedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);
	funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";
	auto module = LoadLibrary(dllpath.c_str());
	//寻找初始化函数,并执行
	pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());
	if (initfunc)
	{
		//常规方式
		//std::function< void(std::string)> cb = callback;
		//initfunc(4, cb);
		//lambda方式
		initfunc(4, [](std::string info) {
			std::cout << info << std::endl; 
			}
		);
	}
	else
	{
		std::cout<<"not find function name"<<std::endl;
	}
#else
	typedef  void(* pfnInitPlugin) (int, std::function< void(std::string)>);
	funName = "_Z4" + funName + "iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE";
	auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);
	if (dp)
	{
		pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());
		if (initfunc)
		{
			initfunc(4, [](std::string info) {
				std::cout << info << std::endl;
				}
			);
		}
		dlclose(dp);
	}
	else
	{
		std::cout<<"not find function name"<<std::endl;
	}
#endif
}

void Run2(const std::string& dllpath, const std::string& initFuncName)
{
	std::string funName = initFuncName;
#ifdef _WINDOWS
	typedef void(_stdcall* pfnInitPlugin) (int, void(*output)(std::string));
	funName = "?" + funName + "@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z";
	auto module = LoadLibrary(dllpath.c_str());
	//寻找初始化函数,并执行
	pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());
	if (initfunc)
	{
		//常规方式
		//void(*cb)(std::string);
		//cb = callback;
		//initfunc(4, cb);
		//lambda方式
		initfunc(4, [](std::string info) {
			std::cout << info << std::endl;
			}
		);
	}
	else
	{
		std::cout<<"not find function name"<<std::endl;
	}
#else
	typedef  void(* pfnInitPlugin) (int, void(*output)(std::string));
	funName = "_Z4" + funName + "iPFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE";
	auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);
	if (dp)
	{
		pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());
		if (initfunc)
		{
			initfunc(4, [](std::string info) {
				std::cout << info << std::endl;
				}
			);
		}
		dlclose(dp);
	}
	else
	{
		std::cout<<"not find function name"<<std::endl;
	}
#endif
}

int main(int argc, char* argv[]) {

	//C++ 98
	//Lambda回调函数
	//Run1("./Reflection-DLL_TEST.dll","run1");
	std::string argv1 = argv[1];
	if (argc == 2 && argv1 == "-h")
	{
		std::cout << "用法:\n【exe-name】【dll-path】【func-name】" << std::endl;
		return 0;
	}
	if (argc != 3)
	{
		std::cerr << "传入的参数数量不对,应该是俩,检查检查!!" << std::endl;
		return -1;
	}
	std::string dllPath = argv[1];
	if (dllPath.find(".so") == dllPath.npos)
	{
		std::cerr << "传入的文件没有so,检查检查!!" << std::endl;
		return -1;
	}
	std::string argv2 = argv[2];
	if (argv2 == "run1")
	{
		Run1(argv[1], "run1");
	}
	else if (argv2 == "run2") {
		Run2(argv[1], "run2");
	}
	else {
		std::cerr << "传入的函数名既不是 run1 也不是 run2 ,检查检查!!" << std::endl;
		return -1;
	}
	//system("pause");
	return 0;
}

在Linux下,核心代码是下面这几句:

	typedef  void(* pfnInitPlugin) (int, std::function< void(std::string)>);
	funName = "_Z4" + funName + "iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE";
	auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);
	if (dp)
	{
		pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());
		if (initfunc)
		{
			initfunc(4, [](std::string info) {
				std::cout << info << std::endl;
				}
			);
		}
		dlclose(dp);
	}
	else
	{
		std::cout<<"not find function name"<<std::endl;
	}

程序读入动态库,通过函数在动态库中的地址进行直接调用。
下面是调用结果:
在这里插入图片描述

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

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

相关文章

Stylized Smooth Clouds 卡通风格化云朵包

下载:​​Unity资源商店链接资源下载链接 效果图:

Vert.x HttpClient调用后端服务时使用Idle Timeout和KeepAlive Timeout的行为分析

其实网上有大量讨论HTTP长连接的文章&#xff0c;而且Idle Timeout和KeepAlive Timeout都是HTTP协议上的事情&#xff0c;跟Vert.x本身没有太大关系&#xff0c;只不过最近在项目上遇到了一些问题&#xff0c;用到了Vert.x的HttpClient&#xff0c;就干脆总结一下&#xff0c;留…

Codes 开源研发项目管理平台——敏捷测试管理创新解决方案

前言 Codes 是国内首款重新定义 SaaS 模式的开源项目管理平台&#xff0c;支持云端认证、本地部署、全部功能开放&#xff0c;并且对30人以下团队免费。它通过整合迭代、看板、度量和自动化等功能&#xff0c;简化测试协同工作&#xff0c;使敏捷测试更易于实施。并提供低成本的…

计算机人工智能前沿进展-大语言模型方向-2024-09-13

计算机人工智能前沿进展-大语言模型方向-2024-09-13 1. OneEdit: A Neural-Symbolic Collaboratively Knowledge Editing System Authors: Ningyu Zhang, Zekun Xi, Yujie Luo, Peng Wang, Bozhong Tian, Yunzhi Yao, Jintian Zhang, Shumin Deng, Mengshu Sun, Lei Liang, Z…

【AI学习笔记】初学机器学习西瓜书概要记录(二)常用的机器学习方法篇

初学机器学习西瓜书的概要记录&#xff08;一&#xff09;机器学习基础知识篇(已完结) 初学机器学习西瓜书的概要记录&#xff08;二&#xff09;常用的机器学习方法篇(持续更新) 初学机器学习西瓜书的概要记录&#xff08;三&#xff09;进阶知识篇(待更) 文字公式撰写不易&am…

设计模式 享元模式(Flyweight Pattern)

享元模式 简绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它的目的是通过共享技术来有效地支持大量细粒度的对象。享元模式可以极大地减少内存的使用&#xff0c;从而提高程序的性能。它特别适用于需要创建大量相似对象的场景&#…

基于web的工作管理系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

嵌入式-QT学习-小练习

1. 实现多窗口 2. 给按键增加图标 3. 动图展示 结果演示&#xff1a; Mul_Con main.cpp #include "widget.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }一、第一个窗口展示 …

C++ -命名空间-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C -命名空间-详解 1.C语言缺点之一 -- 命名冲突2.命名空间2.1定义2.2使用访问命名空间中的变量展开命名空间域指定访问命名空间域 2.3其他功能 3.C 标准库中的命名空间指定展开…

【网络安全】一篇文章带你了解CTF那些事儿

&#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 一、什么是CTF&#xff1f; CTF&#xff0c;即 Capture The Flag&#xff0c;中文名为夺旗赛&#xff0c;是一种网络安全技术人员之间进行技术竞技的比赛形式。…

保护您的隐私:隐藏 IP 地址的重要性

在当今的数字时代&#xff0c;我们的在线隐私和安全变得比以往任何时候都更加重要。浏览互联网时保护自己的一种方法是隐藏您的 IP 地址。 但是为什么要隐藏您的 IP 地址以及如何有效地做到这一点&#xff1f; 隐藏您的 IP 地址有助于保护您的在线匿名性。您的 IP 地址就像您的…

高速数据转换器设计(一):简介

【注&#xff1a;本文基于《高速数据转换器设计》一书进行学习、总结编撰&#xff0c;适合新手小白进行学习】 目录 1.1 理想数据转换器 1.2 采样操作 1.2.1 冲激采样 1.2.2 采样-保持(S-H) 1.2.3 跟踪-保持 1.2.4 带通采样定理 1.3 信号重构 1.4 量化 1.4.1 量化器 …

SEO 和内容营销:吸引更多人阅读你下一篇文章的3个步骤

SEO和内容营销之间的界限模糊不清。它们显然不同&#xff0c;但很难确切指明其中的界限。 想一想&#xff1a;昼夜的差别是明显的&#xff0c;像白天和黑夜。 但对于昼夜交替的那一刻——究竟是什么时候呢&#xff1f; 你可能认为是在日落时分。但这忽略了市民黄昏&#xff…

【CSS in Depth 2 精译_033】5.4 Grid 网格布局的显示网格与隐式网格(中)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

影刀RPA实战:网页爬虫之携程酒店数据

1.实战目标 大家对于携程并不陌生&#xff0c;我们出行定机票&#xff0c;住酒店&#xff0c;去旅游胜地游玩&#xff0c;都离不开这样一个综合性的网站为我们提供信息&#xff0c;同时&#xff0c;如果你也是做旅游的公司&#xff0c;那携程就是一个业界竞争对手&#xff0c;…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0917)

七、引入 element-ui 组件库 我的Git仓库&#xff1a;https://gitee.com/msyycn/vue3-hei-ma.git 官方文档&#xff1a; https://element-plus.org/zh-CN/ 安装 $ pnpm add element-plus自动按需&#xff1a; 安装插件 pnpm add -D unplugin-vue-components unplugin-auto…

Python VS Golng 谁更胜一筹?

今天我们聊聊Python和Golang这俩到底谁更胜一筹。 这个话题我已经在各种技术论坛上看到无数次了&#xff0c;每次都能引起一波热烈的讨论。作为一个多年写代码的老程序员&#xff0c;今天就站在我的角度&#xff0c;和大家掰扯掰扯这两个语言各自的优缺点。 1. 性能与并发模型…

小程序渗透 | 利用ce修改器挖掘内存修改漏洞

CE修改器原理 ce修改器可以修改内存数值&#xff0c;因为有些程序会把一些值放在本地&#xff0c;然后改动的时候访问的本地的值&#xff0c;修改之后&#xff0c;客户端服务器再次发生交互的时候&#xff0c;把修改的值发出去&#xff0c;可能会影响到服务器的数据&#xff0…

【sgCreateCallAPIFunction】自定义小工具:敏捷开发→调用接口方法代码生成工具

<template><div :class"$options.name" class"sgDevTool"><sgHead /><div class"sg-container"><div class"sg-start"><div style"margin-bottom: 10px">调用接口方法定义列表</div…

五种数据库特性对比(Redis/Mysql/SQLite/ES/MongoDB)

做后端开发的程序员基本都要学会数据库的相关知识。 1、关系型数据 今天就着这段时间了解大模型的事需要牵扯到是我们接触最多的、也是入门后端必学的关系型数据库。在关系型数据库中&#xff0c;数据以表的形式进行组织和存储&#xff0c;每个表就像一个 Excel 表格&#xf…