C#【高级篇】 IntPtr是什么?怎么用?

news/2024/4/27 20:50:04/文章来源:https://blog.csdn.net/sinat_40003796/article/details/127244155

C#学习汇总 - 总目录

C#【高级篇】 IntPtr是什么?怎么用?

  • 前言
  • 一、IntPtr(IntPointer)的由来
  • 二、IntPtr(属于结构体)的说明
  • 三、IntPtr的使用示例
    • 1、int类型与IntPtr类型之间的转换
    • 2、string类型与IntPtr之间的转换
    • 3、结构体与IntPtr之间的转换
    • 4、微软官方示例【使用托管指针来反转数组中的字符】
  • 补充:
    • 1、获取数组的指针(IntPtr)
    • 2、获取某个变量的指针


前言

在C#编程中,当调用C++写的dll时,有时会用到IntPtr,那IntPtr是什么,又怎么用呢?


一、IntPtr(IntPointer)的由来

参考:https://www.cnblogs.com/cdaniu/p/15789803.html

.NET提供了一个结构体System.IntPtr专门用来代表句柄或指针

句柄是对象的标识符,当调用这些API创建对象时,它们并不直接返回指向对象的指针,而是会返回一个32位或64位的整数值,这个在进程或系统范围内唯一的整数值就是句柄(Handle),随后程序再次访问对象,或者删除对象,都将句柄作为Windows API的参数来间接对这些对象进行操作。

  • 句柄指向就是指向文件开头,在windows系统所有的东西都是文件,对象也是文件。所以句柄和指针是一样的意思。句柄是面向对象的指针的称呼。
  • 指针是对存储区域的引用,该区域包含您感兴趣的一些数据。指针是面向过程编程的称呼。

intPtr类是intPointer的缩写。C#中用来取代指针,也可以说对指针进行封装。指向非托管内存。它也不常用,因为C#项目中指针都被弃用了,那指针的封装——句柄,自然也被弃用了。

但总有特殊的地方会用到指针,比如调用C++动态库之类的;所以微软贴心的为我们做了个句柄,毕竟指针用起来太难受了。

句柄是一个结构体,简单的来说,它是指针的一个封装,是C#中指针的替代者,下面我们看下句柄的定义。

配图!

从图中我们可以看到,句柄IntPtrt里包含创建指针,获取指针长度,设置偏移量等等方法,并且为了编码方便还声明了些强制转换的方法。

看了句柄的结构体定义,相信稍微有点基础的人已经明白了,在C#中,微软是希望抛弃指针而改用更优秀的intPtr代替它的。

但我们还会发现,句柄里还提供一个方法是ToPointer(),它的返回类型是Void*,也就是说,我们还是可以从句柄里拿到C++中的指针,既然,微软期望在C#中不要使用指针,那为什么还要提供这样的方法呢?

这是因为,在项目开发中总是会有极特殊的情况,比如,你有一段C++写的非常复杂、完美的函数,而将这个函数转换成C#又及其耗时,那么最简单省力的方法就是直接在C#里启用指针进行移植

也就是说,C#支持指针,其实是为了体现它的兼容性,并不是提倡大家去使用指针

二、IntPtr(属于结构体)的说明

参考:
https://zhidao.baidu.com/question/559571801.html
https://blog.csdn.net/lvjiyang/article/details/107040215

  1. C#中的IntPtr类型被称之为“平台特定的整数类型”,用于本机资源,例如窗口句柄

  2. 资源的大小取决于使用的硬件和操作系统,即此类型的实例在32位硬件和操作系统中将是32位,在64位硬件和操作系统中将是64位;但其大小总是足以包含系统的指针(因此也可以包含资源的名称)。
    IntPtr 类型被设计成整数,其大小适用于特定平台。

  3. 在调用API函数时,类似含有窗口句柄参数(HANDLE)的原型函数,应显式地声明为IntPtr类型。

  4. IntPtr类型对多线程操作是安全的

  5. IntPtr 类型可以由支持指针的语言使用,并可作为在支持与不支持指针的语言间引用数据的一种通用方式。

  6. IntPtr 对象也可用于保持句柄。例如,IntPtr 的实例广泛地用System.IO.FileStream 类中来保持文件句柄。
    、、、、、、、以下为补充、、、、、、、、、、、

  7. IntPtr其实就是 HANDLE,无类型的指针。无类型的指针不能直接使用,需要传给接受它的函数。

  8. 托管window中的句柄,一般在window api 中使用, IntPtr a=(IntPtr)1;

例如:
一个C#程序调用Win32API mciSendString函数控制光盘驱动器,这个函数的函数原型是:

MCIERROR mciSendString(
LPCTSTR lpszCommand,
LPTSTR lpszReturnString,
UINT cchReturn,
HANDLE hwndCallback
);

首先在C#中声明这个函数

[DllImport("winmm.dll")]
private static extern long mciSendString(string a,string b,uint c,IntPtr d);

然后用这样的方法调用

mciSendString("set cdaudio door open", null, 0, this.Handle);

也可以使用IntPtr.Zero将句柄设置为0;
或者使用类型强制转换:

mciSendString("set cdaudio door open", null, 0, (IntPtr)0 );

或者,使用IntPtr构造函数:

IntPtr a = new IntPtr(2121);

完整代码:

using System;
using System.Runtime.InteropServices;namespace ConsoleApp5
{class Program{[DllImport("winmm.dll")]private static extern long mciSendString(string a, string b, uint c, IntPtr d);static void Main(string[] args){int Handle = 1;mciSendString("set cdaudio door open", null, 0, (IntPtr)Handle);//使用IntPtr.Zero将句柄设置为0mciSendString("set cdaudio door open", null, 0, IntPtr.Zero);//或者使用类型强制转换mciSendString("set cdaudio door open", null, 0, (IntPtr)0);//或者,使用IntPtr构造函数IntPtr a = new IntPtr(2121); mciSendString("set cdaudio door open", null, 0, a);Console.WriteLine("可以正确使用");Console.ReadLine();}}
}

注意:
1、在C#中声明Win32API时,一定要按照WinAPI的原型来声明,不要改变它的数据类型
2、尽量不要过多使用类型强制转换或构造函数的方式初始化一个IntPtr类型的变量,这样会使程序变得难于理解并容易出错。

三、IntPtr的使用示例

参考:https://blog.csdn.net/lvjiyang/article/details/107040215

特别说明:下边示例均需启动“允许不安全代码”
项目属性——>生成——>勾选“允许不安全代码”。
在这里插入图片描述

1、int类型与IntPtr类型之间的转换

using System;
using System.Runtime.InteropServices;namespace MyIntPtr
{class Program{static void Main(string[] args){int nValue1 = 10;int nValue2 = 20;//AllocHGlobal(int cb):通过使用指定的字节数,从进程的非托管内存中分配内存。IntPtr ptr1 = Marshal.AllocHGlobal(sizeof(int));IntPtr ptr2 = Marshal.AllocHGlobal(sizeof(int));//WriteInt32(IntPtr ptr, int val):将 32 位有符号整数值写入非托管内存。//int->IntPtrMarshal.WriteInt32(ptr1, nValue1);Marshal.WriteInt32(ptr2, nValue2);// ReadInt32(IntPtr ptr, int ofs):从非托管内存按给定的偏移量读取一个 32 位带符号整数//IntPtr->intint nVal1 = Marshal.ReadInt32(ptr1, 0);int nVal2 = Marshal.ReadInt32(ptr2, 0);//FreeHGlobal(IntPtr hglobal):释放以前从进程的非托管内存中分配的内存。Marshal.FreeHGlobal(ptr1);Marshal.FreeHGlobal(ptr2);Console.WriteLine("Test Success");Console.ReadLine();}}
}

2、string类型与IntPtr之间的转换

using System;
using System.Runtime.InteropServices;namespace MyIntPtr
{class Program{static void Main(string[] args){string str = "aa";IntPtr strPtr = Marshal.StringToHGlobalAnsi(str);string ss = Marshal.PtrToStringAnsi(strPtr);Marshal.FreeHGlobal(strPtr);Console.WriteLine("Test Success");Console.ReadLine();}}
}

3、结构体与IntPtr之间的转换

using System;
using System.Runtime.InteropServices;namespace MyIntPtr
{class Program{public struct stuInfo{public string Name;public string Gender;public int Age;public int Height;}static void Main(string[] args){stuInfo stu = new stuInfo(){Name = "张三",Gender = "男",Age = 23,Height = 172,};//获取结构体占用空间的大小int nSize = Marshal.SizeOf(stu);//声明一个相同大小的内存空间IntPtr intPtr = Marshal.AllocHGlobal(nSize);//Struct->IntPtrMarshal.StructureToPtr(stu, intPtr, true);//IntPtr->StructstuInfo Info = (stuInfo)Marshal.PtrToStructure(intPtr, typeof(stuInfo));Console.WriteLine(Info.Name);Console.WriteLine(Info.Gender);Console.WriteLine(Info.Age);Console.WriteLine(Info.Height);Console.WriteLine("Test Success");Console.ReadLine();}}
}

运行结果:
在这里插入图片描述

4、微软官方示例【使用托管指针来反转数组中的字符】

https://learn.microsoft.com/zh-cn/dotnet/api/system.intptr?view=net-6.0
以下示例使用托管指针来反转数组中的字符。 初始化 String 对象并获取其长度后,它将执行以下操作:

  1. Marshal.StringToHGlobalAnsi调用该方法以 ANSI (单字节) 字符的形式将 Unicode 字符串复制到非托管内存。 该方法返回一个 IntPtr 对象,该对象指向非托管字符串的开头。 转换为指向字节的指针。

  2. 调用该方法 Marshal.AllocHGlobal 分配与非托管字符串占用的字节数相同的字节数。 该方法返回一个 IntPtr 对象,该对象指向非托管内存块的开头。

  3. Visual Basic 示例定义一个名为offset等于 ANSI 字符串长度的变量。 它用于确定将 ANSI 字符串中下一个字符复制到的非托管内存中的偏移量。 由于其起始值为字符串的长度,因此复制操作会将字符串开头的字符复制到内存块的末尾。

    C#、F# 和 C++ 示例调用 ToPointer 该方法以获取指向字符串起始地址和非托管内存块的非托管指针,并将一个小于字符串长度的字符串添加到 ANSI 字符串的起始地址。 由于非托管字符串指针现在指向字符串的末尾,因此复制操作会将字符串末尾的字符复制到内存块的开头。

  4. 使用循环将字符串中的每个字符复制到非托管内存块

  5. 所有示例都调用 Marshal.PtrToStringAnsi 用于将包含复制的 ANSI 字符串的非托管内存块转换为托管 Unicode String 对象。

  6. 显示原始字符串和反向字符串后,所有示例都调用FreeHGlobal该方法以释放为非托管 ANSI 字符串分配的内存和非托管内存块

using System;
using System.Runtime.InteropServices;class NotTooSafeStringReverse
{static public void Main(){string stringA = "I seem to be turned around!";int copylen = stringA.Length;// Allocate HGlobal memory for source and destination stringsIntPtr sptr = Marshal.StringToHGlobalAnsi(stringA);IntPtr dptr = Marshal.AllocHGlobal(copylen + 1);//【这里为何要加1???】// The unsafe section where byte pointers are used.unsafe{byte *src = (byte *)sptr.ToPointer();byte *dst = (byte *)dptr.ToPointer();if (copylen > 0){// set the source pointer to the end of the string// to do a reverse copy.src += copylen - 1;while (copylen-- > 0){*dst++ = *src--;}*dst = 0;//【因为上边的copylen + 1而有这行代码】}}string stringB = Marshal.PtrToStringAnsi(dptr);Console.WriteLine("Original:\n{0}\n", stringA);Console.WriteLine("Reversed:\n{0}", stringB);// Free HGlobal memoryMarshal.FreeHGlobal(dptr);Marshal.FreeHGlobal(sptr);}
}// The progam has the following output:
//
// Original:
// I seem to be turned around!
//
// Reversed:
// !dnuora denrut eb ot mees I

运行结果:
在这里插入图片描述

补充:

1、获取数组的指针(IntPtr)

通过Marshal.UnsafeAddrOfPinnedArrayElement(Array,Int32)方法获得一个数组的第某个元素的内存地址。

  • Array是数组
  • Int32是元素的索引,第一个元素是0。

注:内存地址以字节为单位,第一个元素地址为n,第二个为n+数据类型的字节数int32是4个字节,那么元素地相邻址之间差4

例如:

using System;
using System.Runtime.InteropServices;class ConsoleApp1
{static public void Main(){int[] ary = new int[] { 1, 2, 3 };IntPtr inp = Marshal.UnsafeAddrOfPinnedArrayElement(ary, 0);IntPtr inp1 = Marshal.UnsafeAddrOfPinnedArrayElement(ary, 1);IntPtr inp2 = Marshal.UnsafeAddrOfPinnedArrayElement(ary, 2);//内存地址以字节为单位,第一个元素地址为:n,第二个为:n+数据类型的字节数。int32是4个字节,那么元素相邻址之间差4//每次运行结果的内存地址都不一样!!!但地址却都相差4【系统随机分配内存】Console.WriteLine(inp.ToString());//输出的就是一串数字,就是内存地址。输出结果:nConsole.WriteLine(inp1.ToString());//输出的就是一串数字,就是内存地址。输出结果:n+4Console.WriteLine(inp2.ToString());//输出的就是一串数字,就是内存地址。输出结果:n+8Console.ReadLine();}
}

第1次运行结果:
在这里插入图片描述

第2次运行结果:
在这里插入图片描述

2、获取某个变量的指针

这里就要用到C#中的指针,用unsafe {}关键字,并设置:项目属性——>生成——>勾选“允许不安全代码”。

例如:【注:和指针p相关的变量只能出现在unsafe{}内部,外部无法使用】

using System;
using System.Runtime.InteropServices;class ConsoleApp2
{static public void Main(){       int num = 999;unsafe{int* p = # //建立指针P,指向变量numConsole.WriteLine((int)p); //num的内存地址Console.WriteLine(*p); //引用p指向的数据,即numIntPtr op = new IntPtr((int)p);//构造c#类型的指针Console.WriteLine(Marshal.ReadInt32(op));//输出的是变量num的值Console.ReadLine();}}
}

运行结果:
在这里插入图片描述

C#学习汇总 - 总目录

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

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

相关文章

Java collection集合的体系特点

Java collection集合的体系特点 集合类体系结构 collection单列集合,每个元素(数据)只包含一个值。 Map双列集合,每个元素包含两个值(键值对)。 collection集合体系(常见) colle…

位段是什么玩意?你听说过吗??

当我们学完结构体之后,我们就要好好学学结构体实现位段的能力!!! 目录 一、位段是什么? 二、位段的内存分配 三、位段的跨平台问题 总结 一、位段是什么? 位段的声明和结构体大体相同,但是有两…

【Unity_AssetBundle】(二)AB包资源打包、Asset Bundle Browser工具的使用

1.Asset Bundle Browser包安装 Asset Bundle Browser是AB包打包工具较底版本Unity编辑器: Windows——>Package Mannger——>搜索Asset Bundle Browser进行下载导入即可 较高版本Unity编辑器: 高版本Unity在Addressables功能中封装了AB包功能 安…

Telnet、DHCP、静态路由、等价路由、环回接口、浮动静态路由详解

文章目录前言一、Telnet二、DHCP----动态主机配置协议手工配置缺陷报文类型DHCP租期地址池DHCP中继代理路由信息来源直连路由静态路由优先级数据流量是双向的静态路由的扩展配置等价路由环回接口手工汇总路由黑洞缺省路由空接口路由浮动静态路由前言 一、Telnet Telnet是位于…

【数据结构】 归并排序、 基数排序

目录 一、什么是归并排序? 二、归并排序 三、什么是基数排序? 四、基数排序 五、各种排序的比较 一、什么是归并排序? 归并排序是建立在归并操作上的一种有效,稳定的排序算法。是将已有序的子序列合并,得到完全有…

09-Pawn类 UE4 C++

1.首先创建一个C的Pawn类 右键点击Public,选择新建C类 选择Pawn,然后点击下一步 命名后,点击创建 创建完毕,双击打开MyPawn 2.在MyPawn.h中添加如下代码: UPROPERTY(EditAnywhere) class UStaticMeshComponent* Mes…

SpringCloud-31-Spring Cloud Config微服务与配置文件解耦

11.8 微服务与配置文件解耦 我们可以将之前的子模块中的配置提取出来,托管到gitee上统一管理,这样运维人员维护配置文件就不变动子模块了,实现了模块与配置的解耦。 下面用例子来解释下这种做法的好处 在基础工程spring-cloud-microservice…

热血江湖服务端架设开服搭建教程

热血江湖服务端架设开服搭建教程 玩网游比较多的小伙伴,相信对热血江湖这款游戏也不陌生,摆脱了传统武侠游戏阴暗血腥的游戏风格,提倡一种“明朗而愉快的武侠”精神。画面上即不会太随意又不会过于沉重,画面干净清新。活泼可爱的…

ORACLE新增数据库(用户),使用navicate

oracle新增数据库并不像mysql直接指令就行,百度一圈都是用Oracle Database Configuration Assistant的,其实navicate就直接可以新建,以下是新建方法: 1.连接数据库 2.新建表空间 点击navicate上方工具栏中"其它"&…

何为功能平价?特斯拉「抛弃」多传感融合,背后有哪些门道

技术与成本,永远是博弈的两方。 当大部分车企都在寻求通过增加更多、更高性能的传感器(也就是通常所说的多传感融合技术)来强化智能驾驶功能可靠性和拓展性的大背景下,特斯拉依然我行我素,继续沿着纯视觉感知的路线前…

盘点一个Python列表(元素多样)处理的实战题目(使用正则表达式也可以实现)

大家好,我是Python进阶者。 一、前言 前几天在Python白银交流群【凡人不烦人】问了一个Python列表处理的问题,提问截图如下:下面是他的部分数据: lst = [(问答题)(2) 假设镀锌钢管, http://admintk.sc.zzstep.com/UpLoadImage/2019-10-10/a84f340e-6c67-42b1-8eae-3dc14281…

队列的操作实验(数据结构)

队列的操作实验(数据结构) 一、实验目的 1.掌握队列存储结构的表示和实现方法。 2.掌握队列的入队和出队等基本操作的算法实现。 3.了解队列在解决实际问题中的简单应用。 二、实验内容 1.建立顺序循环队列…

【LeetCode】【二叉搜索树迭代器】

173. 二叉搜索树迭代器 实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器: BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指…

【优化充电】基于matlab粒子群算法电动汽车充电动态优化策略【含Matlab源码 2163期】

一、粒子群算法电动汽车充电优化 1 电动汽车充电负荷估算 电动汽车的充电负荷主要与电动汽车起始充电时刻和充电时长相关,而起始充电时刻是由电动汽车用户的到家时间决定的,充电时长主要与电动汽车的行驶里程和充电倍率相关。 目前电动汽车还没有大规模运营, 只能通过统计燃油…

ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis

ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis 目录 ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis 项目创建 StackExchange.Redis操作示例 引包【using StackExchange.Redis;】 ConnectionMultiplexer RedisDBHelper …

Git学习总结

目录: (1)版本控制 (2)Git和SVN的区别 (3)Git历史 (4)安装Git及环境配置 (5)常用的Linux命令 (6)Git的必要配置 &a…

PMO和PM如何实现从战略解码到项目执行的端到端闭环?

一、PMO的使命与职责 PMO的使命是提升端到端组织效能,赋能于精细化管理,成为企业的加速器,保障战略项目的交付。 那么PMO要保障战略的交付,核心职责有哪些呢? 二、组织为什么需要端到端项目管理? 核心价…

【ZooKeeper】ZooKeeper 应用场景

ZooKeeper 应用场景发布订阅命名服务集群管理分布式锁分布式队列管理负载均衡配置管理ZooKeeper:分布式协调服务,仲裁机构。基于ZNode数据模型和Watcher监听机制可以解决很多问题,比如分布式锁问题。 应用场景如下: 1、发布/订阅 …

servlet基础知识

早期的Web应用主要用于浏览新闻等静态页面,HTTP服务器(比如 Apache、Nginx)向浏览器返回静态 HTML,浏览器负责解析HTML,将结果呈现给用户。随着互联网的发展,还希望进行一些交互操作来获取动态结果&#xf…

Python Turtle绘图基础(一)——Turtle简介、绘图窗体与绘图区域

今天继续给大家介绍渗透测试相关知识,本文主要内容是Python Turtle绘图基础,包括Turtle简介、绘图窗体与绘图区域。 一、Turtle库简单介绍 Turtle库时Python语言的标准库(所谓标准库,就是在安装Python时自带的库,与之…