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

C【高级篇】 IntPtr是什么?怎么用?在 C 编程中 当调用 C 写的 dll 时 有时会用到 IntPtr 那 IntPtr 是什么 又怎么用呢 intptr

大家好,欢迎来到IT知识分享网。

C#学习汇总 – 总目录


前言

在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(属于结构体)的说明

  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;
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); 
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将句柄设置为0 mciSendString("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的使用示例

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

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

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->IntPtr Marshal.WriteInt32(ptr1, nValue1); Marshal.WriteInt32(ptr2, nValue2); // ReadInt32(IntPtr ptr, int ofs):从非托管内存按给定的偏移量读取一个 32 位带符号整数 //IntPtr->int int 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->IntPtr Marshal.StructureToPtr(stu, intPtr, true); //IntPtr->Struct stuInfo 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、微软官方示例【使用托管指针来反转数组中的字符】

  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 strings IntPtr 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 memory Marshal.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());//输出的就是一串数字,就是内存地址。输出结果:n Console.WriteLine(inp1.ToString());//输出的就是一串数字,就是内存地址。输出结果:n+4 Console.WriteLine(inp2.ToString());//输出的就是一串数字,就是内存地址。输出结果:n+8 Console.ReadLine(); } } 

2、获取某个变量的指针

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

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

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

C#学习汇总 – 总目录

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121667.html

(0)
上一篇 2025-10-22 10:20
下一篇 2025-10-22 10:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信