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#学习汇总 - 总目录

C#【高级篇】 IntPtr是什么?怎么用?相关推荐

  1. C#【高级篇】.NET平台调用Win32 API

    C#学习汇总 - 总目录 C#[高级篇] .NET平台调用Win32 API 前言 一.基础知识 1.Win32 API函数是什么? 2.Win32 API放在哪? 3.C#如何调用Win32 API ...

  2. 『高级篇』docker之APIGateway(17)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之APIGateway(17) 这次说最后一个模块APIGateway,他的功能就是将我们客户端的请求统 ...

  3. SpringBoot高级篇MongoDB之修改基本使用姿势

    原文: 190218-SpringBoot高级篇MongoDB之修改基本使用姿势 本篇依然是MongoDB curd中的一篇,主要介绍document的更新,主要内容如下 常见类型成员的修改 数组类型 ...

  4. 【.net深呼吸】动态类型(高级篇)

    前面老周给大家介绍了动态类型使用的娱乐级别用法,其实,在很多情景下,娱乐级别的用法已经满足需求了. 如果,你想自己来控制动态类型的行为和数据的存取,那么,就可以考虑用今天所说的高大上技术了.比如,你希 ...

  5. 『高级篇』docker容器来说什么是微服务(三)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker容器来说什么是微服务(三) 上一节说了单体架构,单体架构也无法适应我们的服务,来说说微服务,看能否解决单 ...

  6. 12面魔方公式图解法_【高级篇】(三)三阶魔方CFOP高级玩法之——F2L

    一.F2L这一步要干什么 1.先了解一下"棱角对"和"槽位"的概念 棱角对:即由一个棱块和一个角块构成,是F2L的基本单元(共四组) 槽位:给"棱角对 ...

  7. 『高级篇』docker之DockerSwarm的集群环境搭建(28)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之DockerSwarm的集群环境搭建(28) 上次了解了docker Swarm,这次一起动手操作,搭 ...

  8. 『高级篇』docker之开发课程EdgeService(16)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之开发课程EdgeService(16) 课程的edgeService依赖于课程服务的dubbo服务,对 ...

  9. java搭配oracle,Java联接Oracle(高级篇)

    Java连接Oracle(高级篇) 在项目工程目录下新建一个config文件夹,在config文件夹里创建一个database.properties文件,配置相关Oracle数据库的driver.ur ...

  10. c#扩展方法奇思妙用高级篇七:“树”通用遍历器

    我的上一篇随笔<c#扩展方法奇思妙用高级篇六:WinForm 控件选择器>中给出了一个WinForm的选择器,其实质就是一个"树"的遍历器,但这个遍历局限于WinFor ...

最新文章

  1. POJ2406简单KMP
  2. OpenCASCADE绘制测试线束:几何命令之预测
  3. 最小硬盘实现单原子信息存储 超现有硬盘500倍
  4. Asp.NET生成静态页面并分页
  5. 量子计算机西南交大,交大量子光电实验室
  6. 小网站架构优化-提升抗并发能力:子应用程序分离方案
  7. 元月份退休能享受涨养老金的待遇吗?
  8. vertica数据库将一个字段用逗号分割与拼接
  9. 第三周 3.14 --- 3.20
  10. Linux启动系统时不启动防火墙,Linux系统启动并配置防火墙的方法
  11. 新年2021HTML,2021新年倒计时html代码
  12. golang struct数组排序_go语言中排序sort的使用方法示例
  13. pe怎么看计算机mac地址,在winPE下肿么看MAC地址 急求!!!
  14. 配置ST-GCN体会
  15. web前端基础联系作业
  16. 【CSS】CSS盒子模型
  17. 设计篇:一文分清UIUE
  18. loadrunner11补丁
  19. ZBrush中Magnify膨胀笔刷介绍
  20. 最后一次——时间序列分析

热门文章

  1. 计算机硬盘使用率,硬盘占用率和速度
  2. js实现身份证号查询相关信息
  3. 移动端微信、QQ、浏览器调用qq临时会话功能
  4. 特洛伊木马 (计算机木马程序)
  5. matlab数字图像处理常用操作
  6. 电脑连接移动设备android驱动程序,手机连接电脑驱动程序下载汇总
  7. js 动画函数库 GreenSock velocityjs
  8. 某计算机房空气调节系统设计,空气调节工程思考题习题答案(精品DOC)
  9. matlab能不能查焓湿图,焓湿图上,湿球温度要怎么查呢?
  10. html静态模板资源,可下载源码