C# 与 C/C++ 的交互
什么是平台调用 (P/Invoke)
- P/Invoke 是可用于从托管代码访问非托管库中的结构、回调和函数的一种技术。
托管代码与非托管的区别
托管代码和非托管代码的主要区别是内存管理方式和对计算机资源的访问方式。托管代码通常运行在托管环境中,如 mono 或 java 虚拟机等,这些环境提供了垃圾回收器(GC)等工具来管理内存。在托管环境中,程序员通常不需要手动分配和释放内存,因为这些任务由运行时系统自动完成。托管代码通常具有更高的安全性和可移植性,因为它们运行在虚拟机中,而不是直接在操作系统上运行。相比之下,非托管代码通常是直接在操作系统上运行的,这意味着程序员需要手动分配和释放内存来避免内存泄漏和其他内存相关的问题。非托管代码可以更直接地访问计算机硬件和操作系统资源,因此它们通常具有更高的性能和更好的控制性。
-
P/Invoke API 包含在以下两个命名空间中:
System和System.Runtime.InteropServices。 使用这两个命名空间可提供用于如何与 native 通信的工具。 -
P/Invoke 从功能上来说,只支持函数调用,在被导出的函数前面一定要添加extern "C"来指明导出函数的时候使用C语言方式编译和连接,这样保证函数定义的名字和导出的名字相同,否则如果默认按C++方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。
互调的基本原理
首先,我们需要了解 数据类型 的概念
在大多数编译型语言中定义变量的时候都需要先指定其数据类型,所谓数据类型,是语言约定的一个便于记忆的名称(int,long,float),究其本质就是在指明了这个数据在内存中到底是占用了几个字节,程序在运行的时候,首先找到这个数据的地址,然后再按照该类型的长度,读取响应的内存,然后再处理。
基于这个原理,编程语言直接就可以进行互调了,对于不同语言之间的互调,只要将改数据的指针(内存地址)传递给另一个语言,在另一个语言中根据通信协议将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下条件:
- 对于像 java,C# 这样有运行时虚拟机变成语言来讲,由于虚拟内存会让堆内存来回转移,因此在进行互调的时候,需要保证被调用的数据所在内存一定要固定,不能被转移。
- 有一些编程语言支持指针,有一些语言不支持指针(如Java),这个问题并不重要,所谓指针,其实就是一个内存地址,对于32位OS的指针是一个32位整数,而对于64位机OS的指针是一个64位整数。因为大多数语言中都有整型数,所以可以利用整型来接收指针。
Native 库的加载
举例被加载库的名称为: nativelib.so/nativelib.dll
- 在 windows 平台运行时,将按以下顺序搜寻 dll:
- nativelib
- nativelib.dll
[DllImport("nativelib")]
[DllImport("nativelib.dll")]
static extern int ExportedFunction();
- 在 linux 或 macOS 上运行时,运行时会尝试在
lib前添加,并追加规范共享库扩展。在这些 OS 上按以下顺序尝试名称变体:- nativelib.so/nativelib.dylib
- libnativelib.so/libnativelib.dylib
- nativelib
- libnativelib
基本数据类型传递
| C# 关键字 | .Net类型 | C/C++ |
|---|---|---|
| byte | System.Byte | uint8_t |
| sbyte | System.SByte | int8_t |
| short | System.Int16 | int16_t |
| ushort | System.UInt16 | uint16_t |
| int | System.Int32 | int32_t |
| uint | System.UInt32 | uint32_t |
| long | System.Int64 | int64_t |
| ulong | System.UInt64 | uint64_t |
| char | System.Char | char |
| nint | System.IntPtr | intptr_t |
| bool | System.Boolean | Win32 BOOL 类型 |
| decimal | System.Decimal | COM DECIMAL 结构 |
调用流程
基础数据类型调用
#define EXPORTAPI __declspec(dllexport)extern "C" EXPORTAPI int add(int a, int b)
{return a + b;
}
extern "C" EXPORTAPI void writeString(char* content)
{cout << content << endl;
}
- 第一行代码中定义了一个
EXPORTAPI的宏,对应的内容是__declspec(dllexport)意思是将后面修饰的内容定义为 Dll 中要导出的内容 ,当然也剋以不使用这个宏,直接将__declspec(dllexport)写在要导出的函数前。 - 第二行中的
extern "C",表示该函数编译和连接时使用 C 语言的方式以保证函数名称不变,由于 C++ 支持函数重载,因此编译器会将函数的参数类型也加到编译后的代码中。例如函数 void fun(int,int),编译后的可能是 _fun_int_int(不同的编译器可能不同,但都采取了类似的机制),extern是 C/C++ 语言中表明函数和全局变量的作用范围的关键字,这个关键字告诉编译器,其声明的函数和变量可以在本模块或其他模块中使用。
[DllImport("TestCPPDll.dll",EntryPoint = "add")]extern static int add(int a, int b);[DllImport("TestCPPDll.dll")]extern static void writeString(string content);public static void Add(){int result = add(1, 2);Console.WriteLine($"CallLibraryAdd result :{result}");}public static void WriteString(string str){writeString(str);}
DllImport 中第一个参数表示 Dll 文件的位置,第二个参数 EntryPoint 用来指明对应的 C/C++ 中的函数名称是什么, extern 关键字表明该方法是一个外部调用,这个方法声明后,就可以像调用一个普通的静态方法一样去使用了。
指针的传递
extern "C" EXPORTAPI void addInt(int* i)
{*i += 1;cout << *i << endl;
}//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出
extern "C" EXPORTAPI void sendIntArray(int* firstElement, int arraylength)
{cout << "C# send int array to CPP" << endl;int* currentPointer = firstElement;for (int i = 0; i < arraylength; i++){cout << currentPointer[i];}cout << endl;
}
在第一个方法中参数为一个 int 类型的指针,并将其所指向的内容 +1
第二个方法传入一个整型数组的指针以及数组长度,遍历数组的每一个元素并且输出
#region 指针的传递[DllImport("TestCPPDll.dll")]extern unsafe static void addInt(int* i);[DllImport("TestCPPDll.dll")]extern unsafe static void sendIntArray(int* firstElement, int arraylength);[DllImport("TestCPPDll.dll", EntryPoint = "getArrayFromCPP")]extern unsafe static IntPtr getArrayFromCPPUseInpPtr(IntPtr count);[DllImport("TestCPPDll.dll", EntryPoint = "getArrayFromCPP")]extern unsafe static IntPtr getArrayFromCPPUseRef(ref int count);// 调用 C++ 中的 AddInt 方法public static void AddInt(){int i = 10;unsafe{addInt(&i);}}//调用 C++ 中的 sendIntArray 方法将 C# 中的数组数据传递到 C++ 中,并在 C++ 中输出public static void AddIntArry(){int[] csArry = new int[10];for (int iArr = 0; iArr < 10; iArr++){csArry[iArr] = iArr;}unsafe{fixed (int* pCSArray = &csArry[0]){sendIntArray(pCSArray, 10);}}}//调用C++中的GetArrayFromCPP方法获取一个C++中建立的数组,使用 InpPrt 类型传参,IntPtr 与指针可以互相强转public static void GetArrayFromCPPUseInpPtr(){Console.WriteLine("CPP send int array to C# user IntPtr");int count = 0;GCHandle gch = GCHandle.Alloc(count, GCHandleType.Pinned);IntPtr countInptr = gch.AddrOfPinnedObject();IntPtr pArrayPointer = getArrayFromCPPUseInpPtr(countInptr);count = Marshal.PtrToStructure<int>(countInptr);int[] array = new int[count];for (int i = 0; i < count; i++){IntPtr intPtr = IntPtr.Add(pArrayPointer, i * Marshal.SizeOf<int>());array[i] = Marshal.PtrToStructure<int>(intPtr);Console.Write(array[i]);}Console.WriteLine();}//调用C++中的GetArrayFromCPP方法获取一个C++中建立的数组, 使用 ref 会出现防御性拷贝,造成 GCpublic static void GetArrayFromCPPUseRef(){Console.WriteLine("CPP send int array to C# user ref");int count = 0;IntPtr pArrayPointer = getArrayFromCPPUseRef(ref count);int[] array = new int[count];for (int i = 0; i < count; i++){IntPtr intPtr = IntPtr.Add(pArrayPointer, i * Marshal.SizeOf<int>());array[i] = Marshal.PtrToStructure<int>(intPtr);Console.Write(array[i]);}Console.WriteLine();}#endregion
函数指针的传递
C# 中并没有函数指针的概念,但是可以使用 委托 来代替函数指针
在 C/C++ 运行的某一时刻调用 C# 的对应函数, 这个时候就需要将一个 C# 中已经指向某一个函数的函数指针(委托)传递给 C++
//定义一个函数指针
typedef void (*OnCSCallBack)(int value);OnCSCallBack onCSCallBack;//用于设置函数指针的方法
extern "C" EXPORTAPI void setCallback(OnCSCallBack callback)
{onCSCallBack = callback;cout << "set CPP callback success" << endl;cout << endl;
}//对 C# 中传递过来的委托进行调用
extern "C" EXPORTAPI void testCPPInvoke() {int value = 999;onCSCallBack(value);
}
public delegate void OnCSDelegate(int value);[DllImport("TestCPPDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]extern static void setCallback(OnCSDelegate action);[DllImport("TestCPPDll.dll")]extern static void testCPPInvoke();//测试过程中发现 .Net 中 deletegate 可以传递。action<int> 不行。//而在 Unity 中 将 Action 方法添加[MonoPInvokeCallback(typeof(Action<int>))] 标签后可以. MonoPInvokeCallback 标签在 AOT 命名空间下public static void SetCPPCallBack(){OnCSDelegate onCSCallBack = OnCSCallback;setCallback(onCSCallBack);}public static void OnCSCallback(int value){Console.WriteLine($"CPP call CSharp founction value:{value}");}
结构体的传递
传递结构体的想法和传递一个int类型数据类似,struct中的数据是在内存中顺序排列的
- C#中结构体字段类型与C++结构体中的字段类型相兼容
- C#结构中的字段顺序与C++结构体中的字段顺序相同,要保证该功能,需要将C#结构体标记为[StructLayout( LayoutKind.Sequential)]
typedef struct Vector3_
{float x;float y;float z;
} Vector3;// 输出从 C# 侧传入的结构体内容
extern "C" EXPORTAPI void sendStructFormCSToCPP(Vector3 vector3)
{cout << "get vector3 int cpp,x:";cout << vector3.x;cout << ",y:";cout << vector3.y;cout << ",z:";cout << vector3.z << endl;cout << endl;
}//包含固定长度数组的结构体
typedef struct ContainArray_
{Vector3 vectorArray[5];float valueArray[2];
}ContainArray;//不固定数组长度的结构体
typedef struct ContainArrayHasCount_
{int arrayCount;float* floatArray;int vectorArrayCount;Vector3* vectorArray;
}ContainArrayHasCount;//输出从 C# 侧传入数组固定长度的结构体信息
extern "C" EXPORTAPI void sendContainArrayStructFormCSToCPP(ContainArray containArray)
{int vectorArrayCount = sizeof(containArray.vectorArray) / sizeof(*containArray.vectorArray);for (size_t i = 0; i < vectorArrayCount; i++){cout << "vectorArray i: " << i << ",x:" << containArray.vectorArray[i].x << ",y:" << containArray.vectorArray[i].y << ",z:" << containArray.vectorArray[i].z << endl;}int valueArrayCount = sizeof(containArray.valueArray) / sizeof(*containArray.valueArray);for (size_t i = 0; i < valueArrayCount; i++){cout << "valueArray i: " + i << ", value: " << containArray.valueArray[i] << endl;}cout << endl;
}//输出运行时决定数组长度的结构体信息
extern "C" EXPORTAPI void sendContainArrayHasCountStructFormCSToCPP(ContainArrayHasCount containArrayHasCount)
{for (size_t i = 0; i < containArrayHasCount.arrayCount; i++){cout << "containArrayHasCount i: " << i << ",ArrayValue: " << containArrayHasCount.floatArray[i] << endl;}for (size_t i = 0; i < containArrayHasCount.vectorArrayCount; i++){cout << "vectorArray i: " << i << ",x:" << containArrayHasCount.vectorArray[i].x << ",y:" << containArrayHasCount.vectorArray[i].y << ",z:" << containArrayHasCount.vectorArray[i].z << endl;}
}//返回 C++ 侧创建的结构体指针
extern "C" EXPORTAPI ContainArrayHasCount * getArrayStructFormCPP()
{ContainArrayHasCount* containArrayHasCount = new ContainArrayHasCount();containArrayHasCount->arrayCount = 3;containArrayHasCount->floatArray = new float[3];for (size_t i = 0; i < containArrayHasCount->arrayCount; i++){containArrayHasCount->floatArray[i] = i;}containArrayHasCount->vectorArrayCount = 4;containArrayHasCount->vectorArray = new Vector3[4];for (size_t i = 0; i < containArrayHasCount->vectorArrayCount; i++){containArrayHasCount->vectorArray[i].x = i;containArrayHasCount->vectorArray[i].y = i;containArrayHasCount->vectorArray[i].z = i;}return containArrayHasCount;
}
[StructLayout(LayoutKind.Sequential)]public struct Vector3{public Vector3(float x, float y, float z){X = x;Y = y;Z = z;}public float X, Y, Z;}[StructLayout(LayoutKind.Sequential)]public struct ContainArray{[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]public Vector3[] vectorArray;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]public float[] valueArray;}[StructLayout(LayoutKind.Sequential)]public struct ContainArrayHasCount{public int floatArrayCount;public IntPtr floatArray;public int vectorArrayCount;public IntPtr vectorArray;}[DllImport("TestCPPDll")]extern static void sendStructFormCSToCPP(Vector3 vector3);[DllImport("TestCPPDll")]extern static void sendContainArrayStructFormCSToCPP(ContainArray containArrayStruct);[DllImport("TestCPPDll")]extern static void sendContainArrayHasCountStructFormCSToCPP(ContainArrayHasCount containArrayHasCount);[DllImport("TestCPPDll")]extern static IntPtr getArrayStructFormCPP();// 结构体作为参数传递给 C++ 方法public static void SendStructFormCSToCPP(){Vector3 vector3 = new Vector3();vector3.X = 1;vector3.Y = 2;vector3.Z = 3;sendStructFormCSToCPP(vector3);}// 传入包含指定长度数组的结构体public static void SendContainArrayStructFormCSToCPP(){ContainArray containArrayStruct = new ContainArray();containArrayStruct.vectorArray = new Vector3[5];for (int i = 0; i < 5; i++){containArrayStruct.vectorArray[i].X = i;containArrayStruct.vectorArray[i].Y = i;containArrayStruct.vectorArray[i].Z = i;}containArrayStruct.valueArray = new float[2];containArrayStruct.valueArray[0] = 1.234f;containArrayStruct.valueArray[1] = 5.678f;sendContainArrayStructFormCSToCPP(containArrayStruct);}// 传入运行时决定数组大小的结构体public static void SendContainArrayHasCountStructFormCSToCPP(){ContainArrayHasCount containArrayHasCount = new ContainArrayHasCount();float[] floatArray = new float[] { 1, 22, 333, 4444, 55555 };Vector3[] vectorArray = new Vector3[] { new Vector3(123, 123, 123), new Vector3(456, 456, 456), new Vector3(789, 789, 789) };containArrayHasCount.floatArrayCount = floatArray.Length;containArrayHasCount.vectorArrayCount = vectorArray.Length;GCHandle floatArrayHandle = GCHandle.Alloc(floatArray,GCHandleType.Pinned);containArrayHasCount.floatArray = floatArrayHandle.AddrOfPinnedObject();GCHandle vectorArrayHandle = GCHandle.Alloc(vectorArray,GCHandleType.Pinned);containArrayHasCount.vectorArray = vectorArrayHandle.AddrOfPinnedObject();sendContainArrayHasCountStructFormCSToCPP(containArrayHasCount);floatArrayHandle.Free();vectorArrayHandle.Free();}// 接收从 C++ 返回的结构体public static void GetArrayStructFormCPP(){unsafe{IntPtr structIntPtr = getArrayStructFormCPP();ContainArrayHasCount containArrayHasCount = Marshal.PtrToStructure<ContainArrayHasCount>(structIntPtr);for (int i = 0; i < containArrayHasCount.floatArrayCount; i++){IntPtr floatIntPtr = IntPtr.Add(containArrayHasCount.floatArray,i*Marshal.SizeOf<float>());float value = Marshal.PtrToStructure<float>(floatIntPtr);Console.WriteLine($"floatArray {i}:{value}");}for (int i = 0; i < containArrayHasCount.vectorArrayCount; i++){IntPtr vectorIntPtr = IntPtr.Add(containArrayHasCount.vectorArray,i*Marshal.SizeOf<Vector3>());Vector3 vector = Marshal.PtrToStructure<Vector3>(vectorIntPtr);Console.WriteLine($"vectorArray {i}:{vector.X},{vector.Y},{vector.Z}");}Console.WriteLine($"floatArrayCount: {containArrayHasCount.floatArrayCount}");Console.WriteLine($"vectorArrayCount: {containArrayHasCount.vectorArrayCount}");}}
相关文章:
C# 与 C/C++ 的交互
什么是平台调用 (P/Invoke) P/Invoke 是可用于从托管代码访问非托管库中的结构、回调和函数的一种技术。 托管代码与非托管的区别 托管代码和非托管代码的主要区别是内存管理方式和对计算机资源的访问方式。托管代码通常运行在托管环境中,如 mono 或 java 虚拟机等…...
新版Android Studio搜索不到Lombok以及无法安装Lombok插件的问题
前言 在最近新版本的Android Studio中,使用插件时,在插件市场无法找到Lombox Plugin,具体表现如下图所示: 1、操作步骤: (1)打开Android Studio->Settings->Plugins,搜索Lom…...
BST二叉搜索树
文章目录 概述实现创建节点查找节点增加节点查找后驱值根据关键词删除找到树中所有小于key的节点的value 概述 二叉搜索树,它具有以下的特性,树节点具有一个key属性,不同节点之间key是不能重复的,对于任意一个节点,它…...
【Leetcode】211. 添加与搜索单词 - 数据结构设计
一、题目 1、题目描述 请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。 实现词典类 WordDictionary : WordDictionary() 初始化词典对象void addWord(word) 将 word 添加到数据结构中,之后可以对它…...
Discuz户外旅游|旅行游记模板/Discuz!旅行社、旅游行业门户网站模板
价值328的discuz户外旅游|旅行游记模板,本模板需要配套【仁天际-PC模板管理】插件使用。 模板说明 1、模板页面宽度1200px,简洁大气,较适合户外旅行、骑行、游记、摩旅、旅游、活动等类型的论坛、频道网站; 2、所优化的页面有&…...
【重拾C语言】十一、外部数据组织——文件
目录 前言 十一、外部数据组织——文件 11.1 重新考虑户籍管理问题——文件 11.2 文件概述 11.2.1 文件分类 11.2.2 文件指针、标记及文件操作 11.3 打开、关闭文件 11.4 I/O操作 11.4.1 字符读写 11.4.2 字符串读写 11.4.3 格式化读写 11.4.4 数据块读写 11.4.5 …...
dpdk/spdk/网络协议栈/存储/网关开发/网络安全/虚拟化/ 0vS/TRex/dpvs技术专家成长体系教程
课程围绕安全,网络,存储,云原生4个维度去讲解核心技术点。 6个专栏组成:dpdk网络专栏、存储技术专栏、安全与网关开发专栏、虚拟化与云原生专栏、测试工具专栏、性能测试专栏 一、dpdk网络 dpdk基础知识 多队列网卡࿰…...
树莓派玩转openwrt软路由:5.OpenWrt防火墙配置及SSH连接
1、SSH配置 打开System -> Administration,打开SSH Access将Interface配置成unspecified。 如果选中其他的接口表示仅在给定接口上侦听,如果未指定,则在所有接口上侦听。在未指定下,所有的接口均可通过SSH访问认证。 2、防火…...
Gin:获取本机IP,获取访问IP
获取本机IP func GetLocalIP() []string {var ipStr []stringnetInterfaces, err : net.Interfaces()if err ! nil {fmt.Println("net.Interfaces error:", err.Error())return ipStr}for i : 0; i < len(netInterfaces); i {if (netInterfaces[i].Flags & ne…...
缓存降级代码结构设计
缓存降级设计思想 接前文缺陷点 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。公用的远端缓存的操作方法应该私有化,避免集成方代码误操作&…...
一文深入理解高并发服务器性能优化
我们现在已经搞定了 C10K并发连接问题 ,升级一下,如何支持千万级的并发连接?你可能说,这不可能。你说错了,现在的系统可以支持千万级的并发连接,只不过所使用的那些激进的技术,并不为人所熟悉。…...
pytorch中的归一化函数
在 PyTorch 的 nn 模块中,有一些常见的归一化函数,用于在深度学习模型中进行数据的标准化和归一化。以下是一些常见的归一化函数: nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d: 这些函数用于批量归一化 (Batch Normalization…...
【管理运筹学】第 10 章 | 排队论(1,排队论的基本概念)
文章目录 引言一、基本概念1.1 排队过程1.2 排队系统的组成和特征1.3 排队模型的分类1.4 系统指标1.5 系统状态 引言 开一点排队论的内容吧,方便做题。 排队论(Queuing Theory)也称随机服务系统理论,是为解决一系列排队问题&…...
【Express】服务端渲染(模板引擎 EJS)
EJS(Embedded JavaScript)是一款流行的模板引擎,可以用于在Express中创建动态的HTML页面。它允许在HTML模板中嵌入JavaScript代码,并且能够生成基于数据的动态内容。 下面是一个详细的讲解和示例,演示如何在Express中…...
Linux CentOS8安装gitlab_ce步骤
1 下载安装包 wget --content-disposition https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/8/gitlab-ce-15.0.2-ce.0.el8.x86_64.rpm/download.rpm2 安装gitlab yum install policycoreutils-python-utilsrpm -Uvh gitlab-ce-15.0.2-ce.0.el8.x86_64.rpm3 更新配…...
RabbitMq启用TLS
Windows环境 查看配置文件的位置 选择使用的节点 查看当前节点配置文件的配置 配置TLS 将证书放到同配置相同目录中 编辑配置文件添加TLS相关配置 [{ssl, [{versions, [tlsv1.2]}]},{rabbit, [{ssl_listeners, [5671]},{ssl_options, [{cacertfile,"C:/Users/17126…...
CakePHP 3.x/4.x反序列化RCE链
最近网上公开了cakephp一些反序列化链的细节,但是没有公开poc,并且网上关于cakephp的反序列化链比较少,于是自己跟一下 ,构造pop链。 CakePHP简介 CakePHP是一个运用了诸如ActiveRecord、Association Data Mapping、Front Contr…...
练习之C++[3]
文章目录 1.模板类2.模板声明3.string类 1.模板类 模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构所以可用来创建动态增长和减小的数据结构模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换…...
[MT8766][Android12] 修改WIFI热点默认名称、密码、IP地址以及默认开启热点
文章目录 开发平台基本信息问题描述解决方法 开发平台基本信息 芯片: MTK8766 版本: Android 12 kernel: msm-4.19 问题描述 最近做了一款没有屏幕显示的智能盒子,要想操控这款设备就只能通过adb投屏,如果默认不允许有线连接,那么要怎么实…...
【嵌入式】堆栈与单片机内存
堆栈 在片内RAM中,常常要指定一个专门的区域来存放某些特别的数据 它遵循顺序存取和后进先出(LIFO/FILO)的原则,这个RAM区叫堆栈。 其实堆栈就是单片机中的一些存储单元,这些存储单元被指定保存一些特殊信息,比如地址࿰…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
