C# 与 C/C++ 的交互

什么是平台调用 (P/Invoke)

  • P/Invoke 是可用于从托管代码访问非托管库中的结构、回调和函数的一种技术。

托管代码与非托管的区别

    托管代码和非托管代码的主要区别是内存管理方式和对计算机资源的访问方式。托管代码通常运行在托管环境中,如 mono 或 java 虚拟机等,这些环境提供了垃圾回收器(GC)等工具来管理内存。在托管环境中,程序员通常不需要手动分配和释放内存,因为这些任务由运行时系统自动完成。托管代码通常具有更高的安全性和可移植性,因为它们运行在虚拟机中,而不是直接在操作系统上运行。相比之下,非托管代码通常是直接在操作系统上运行的,这意味着程序员需要手动分配和释放内存来避免内存泄漏和其他内存相关的问题。非托管代码可以更直接地访问计算机硬件和操作系统资源,因此它们通常具有更高的性能和更好的控制性。
  • P/Invoke API 包含在以下两个命名空间中:SystemSystem.Runtime.InteropServices。 使用这两个命名空间可提供用于如何与 native 通信的工具。

  • P/Invoke 从功能上来说,只支持函数调用,在被导出的函数前面一定要添加extern "C"来指明导出函数的时候使用C语言方式编译和连接,这样保证函数定义的名字和导出的名字相同,否则如果默认按C++方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。

互调的基本原理

首先,我们需要了解 数据类型 的概念
在大多数编译型语言中定义变量的时候都需要先指定其数据类型,所谓数据类型,是语言约定的一个便于记忆的名称(int,long,float),究其本质就是在指明了这个数据在内存中到底是占用了几个字节,程序在运行的时候,首先找到这个数据的地址,然后再按照该类型的长度,读取响应的内存,然后再处理。
基于这个原理,编程语言直接就可以进行互调了,对于不同语言之间的互调,只要将改数据的指针(内存地址)传递给另一个语言,在另一个语言中根据通信协议将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下条件:

  1. 对于像 java,C# 这样有运行时虚拟机变成语言来讲,由于虚拟内存会让堆内存来回转移,因此在进行互调的时候,需要保证被调用的数据所在内存一定要固定,不能被转移。
  2. 有一些编程语言支持指针,有一些语言不支持指针(如Java),这个问题并不重要,所谓指针,其实就是一个内存地址,对于32位OS的指针是一个32位整数,而对于64位机OS的指针是一个64位整数。因为大多数语言中都有整型数,所以可以利用整型来接收指针。

Native 库的加载

举例被加载库的名称为: nativelib.so/nativelib.dll

  • 在 windows 平台运行时,将按以下顺序搜寻 dll:
    1. nativelib
    2. nativelib.dll
[DllImport("nativelib")]
[DllImport("nativelib.dll")]
static extern int ExportedFunction();
  • 在 linux 或 macOS 上运行时,运行时会尝试在 lib 前添加,并追加规范共享库扩展。在这些 OS 上按以下顺序尝试名称变体:
    1. nativelib.so/nativelib.dylib
    2. libnativelib.so/libnativelib.dylib
    3. nativelib
    4. libnativelib

基本数据类型传递

C# 关键字.Net类型C/C++
byteSystem.Byteuint8_t
sbyteSystem.SByteint8_t
shortSystem.Int16int16_t
ushortSystem.UInt16uint16_t
intSystem.Int32int32_t
uintSystem.UInt32uint32_t
longSystem.Int64int64_t
ulongSystem.UInt64uint64_t
charSystem.Charchar
nintSystem.IntPtrintptr_t
boolSystem.BooleanWin32 BOOL 类型
decimalSystem.DecimalCOM 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;
}
  1. 第一行代码中定义了一个 EXPORTAPI 的宏,对应的内容是 __declspec(dllexport) 意思是将后面修饰的内容定义为 Dll 中要导出的内容 ,当然也剋以不使用这个宏,直接将 __declspec(dllexport) 写在要导出的函数前。
  2. 第二行中的 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}");}}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】缺省参数与函数重载

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章博主将带你学习缺省参数与函数重载&…

.Net Core 6 运行环境手动安装流程

安装.NET Core 6 概述 在开始之前&#xff0c;我们首先需要了解一下整个安装过程的流程。下面的表格将展示安装.NET Core 6的步骤以及每一步需要做的事情。 步骤 动作 说明 1 下载.NET Core 6 SDK 从官方网站下载.NET Core 6 SDK安装包 2 安装.NET Core 6 SDK …

Unnatural Instructions: Tuning Language Models with (Almost) No Human Labor

本文是LLM系列文章&#xff0c;针对《Unnatural Instructions: Tuning Language Models with (Almost) No Human Labor》的翻译。 TOC 摘要 指令调优使预训练的语言模型能够从推理时间的自然语言描述中执行新的任务。这些方法依赖于以众包数据集或用户交互形式进行的大量人工…

win10如何取消文件夹分组

问题描述 最近不知道把哪里碰了&#xff0c;win10文件夹显示的文件都是按照日期分组了&#xff0c;很讨厌。如下图所示 修改方法 1、文件夹空白处-右击 2、分组依据(P)-选择(无)(N) 下面是操作好之后的效果图 结束 -----华丽的分割线&#xff0c;以下是凑字数&#xff0c;大…

超声波清洗机需要注意什么?不能错过的超声波清洗机

超声波清洗机在当今社会已经越来越受到人们的欢迎&#xff0c;它利用超声波的振动来清洁物品表面&#xff0c;能够快速、高效地清除污垢、油脂等。但是&#xff0c;在购买超声波清洗机时&#xff0c;需要注意哪些问题呢&#xff1f;本文将为您介绍购买超声波清洗机需要注意的几…

2023/10/15总结

学习总结 最近开始写项目了&#xff0c;然后写的过程中遇到了跨域问题。 为什么会出现跨域问题 由于浏览器的同源策略限制。同源策略是一种约定&#xff0c;它是浏览器最核心也是最基本的安全功能。如果缺少了同源策略&#xff0c;那么浏览器的正常功能可能都会收到影响。所谓…

gitlab 维护

一 环境信息 二 日常维护 2.1 gitlab mirror 2.1.1 常见方法 社区版本gitab mirror 只能push&#xff0c;默认限制了局域网内mirror 需要修改admin/setting/network(网络)/outbound(出站请求) 勾选允许局域网即可。 2.1.2 疑难问题 内网有三个gitlab A: GITLAB 12 B\C GI…

hash join的基本原理是怎样的?

我们知道数据库里面两表关联主要有三种常见的关联方式&#xff0c;即 nested loop joinhash joinmerge join nested loop join在OLTP交易场景占比是最多的&#xff0c;常用于关联字段为主键或索引字段的情况&#xff0c;通过主键或索引以及loop的方式&#xff0c;A表可以快速…

【特纳斯电子】基于单片机的火灾监测报警系统-仿真设计

视频及资料链接&#xff1a;基于单片机的火灾监测报警系统-仿真设计 - 电子校园网 (mcude.com) 编号&#xff1a; T0152203M-FZ 设计简介&#xff1a; 本设计是基于单片机的火灾监测报警系统&#xff0c;主要实现以下功能&#xff1a; 1.通过OLED显示温度、烟雾、是否有火…

摩尔信使MThings的设备高级参数

摩尔信使MThings支持三级参数管理方案&#xff0c;依次为&#xff1a;数据级、设备级、通道级。 设备级参数不仅包含设备名称、设备地址等常用信息&#xff0c;同时提供了诸多高级参数&#xff0c;其同样是为了满足不同用户应用场景中所面临的差异化需求&#xff0c;以更加灵活…

PostGIS是否有方法能将一个Polygon面切割成若干份小的Polygon面,且每一份的面积差不多大

问题 PostGIS是否有方法能将一个Polygon面切割成若干份小的Polygon面&#xff0c;且每一份的面积差不多大&#xff1f;其实并没有现成的方法&#xff0c;但是通过灵活运用postgis函数可以快速实现这样的功能&#xff0c;总共只要简单的5步就可以了&#xff0c;下文具体说明。二…

【数据结构C/C++】优先(级)队列

文章目录 什么是优先队列&#xff1f;堆排序代码实现408考研各数据结构C/C代码&#xff08;Continually updating&#xff09; 什么是优先队列&#xff1f; 下面的内容来自于百度百科。 如果我们给每个元素都分配一个数字来标记其优先级&#xff0c;不妨设较小的数字具有较高的…

JAVAEE初阶相关内容第十四弹--网络初识

写在前&#xff1a; 这一部分开启网络部分的相关知识&#xff0c;这一弹内容初始网络将主要进行网络相关知识的简单介绍&#xff0c;以及着重介绍协议、协议分层、OSI七层模型、TCP/IP五层模型、封装和分用。 需要认识协议&#xff0c;并知道协议的效果是什么&#xff1b;知道…

不写注释就是耍流氓?

不写注释就是耍流氓&#xff1f; 关于写代码不写注释这么说“我”不想写注释的原因如何才能写出漂亮的注释 关于写代码不写注释这么说 关于代码注释的争论一直存在&#xff0c;程序员社区中有不同的观点和实践。写代码时是否应该写注释是一个有深度的话题&#xff0c;我认为需…

word如何设置页码?教你快速提升文档颜值!

在创建文档时&#xff0c;为了更好地组织内容&#xff0c;页码是一个必不可少的元素。但是很多人不知道word如何设置页码&#xff0c;其实word提供了多种设置页码的方法&#xff0c;以满足不同文档的需求。本文将详细介绍3种设置页码的方法&#xff0c;无论您是初学者还是有经验…

LMI FocalSpec 3D线共焦传感器 使用笔记1

一.硬件介绍 以上特别注意: 屏蔽线必须接地,因为在现场实际调试中,使用软件调试发现经常 弹窗 传感器丢失警告!! 以上 Position LED 的灯被钣金挡住,无法查看异常现象,能否将指示灯设置在软件界面上? 需要确认是软触发还是硬触发,理论上 硬触发比软触发速度要快.(我们目前使用…

LinkedList集合

LinkedList集合 底层数据结构是双链表,查询慢,增删快,但如果操做的是首元素,速度也是极快的 本身多了很多直接操做首尾元素的特有API 这些特有方法不常用,了解即可 LinkedList源码分析 迭代器的源码分析 iterator():生成一个迭代器对象,默认指向集合的0索引处hasNext():判…

javascript利用xhr对象实现http流的comet轮循,主要是利用readyState等于3的特点

//此文件 为前端获取http流 <!DOCTYPE html> <html xmlns"http://www.w3.org/1999/xhtml" lang"UTF-8"></html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"/&g…

sqlserver系统存储过程添加用户学习

sqlserver有一个系统存储过程sp_adduser&#xff1b;从名字看是添加用户的&#xff1b;操作一下&#xff0c; 从错误提示看还需要先添加一个登录名&#xff0c;再执行一个系统过程sp_addlogin看一下&#xff0c; 执行完之后看一下&#xff0c;安全性-登录名下面有了rabbit&…

【Linux】屏蔽项目服务非正常日志输出到message文件中

屏蔽项目服务非正常日志输出到message文件中 # vi /etc/rsyslog.conf ### ....省略n行 :programname, isequal, "进程名" stop *.info;mail.none;authpriv.none;cron.none /var/log/messages### 或者 if $programname 进程名 then stop *.info;mail…