• VB.NET 内存指针和非托管内存的应用


    介绍

    Visual Basic 从来不像在C或C++里一样灵活的操纵指针和原始内存。然而利用.NET框架中的structures 和 classes,可以做许多类似的事情。它们包括 IntPtr,   Marshal 以及 GCHandle。 这些structures(结构) 和classes(类) 允许你在托管和非托管环境中进行交互。本文中将向您展示如何使用这些structures 和 classes 去完成指针和内存的操作。

    关于 IntPtr 结构

    IntPtr  结构的行为像一个整型指针以便能应用到专门的平台。这个结构可以应用到支持或不支持指针的语言中。 .NET 文件 IO 类使用这个 结构扩展操作文件句柄。这个 IntPtr 结构形如整型指针的行为,但是它没有写或读相应位置内存的能力。这时你就需要System.Runtime.InteropServices 命名空间中的 Marshal 类。

    关于 Marshal 类

    这个类提供了全部分配非托管内存、拷贝非托管内存块,以及转换托管到非托管类型的方法。要保证你的代码导入了   System.Runtime.InteropServices

    关于 GCHandle 结构

    GCHandle 结构提供了从非托管内存中处理托管对象的方法。在非托管代码使用它时,可以控制垃圾碎片收集。

    整数值的(读) 与 (写)

    在我们的第一个例子中,使用 Marshal 类在内存中储存一个整数,得到一个内存指针去指向它在内存中的位置,并存到一个 IntPtr 结构中。最终我们将使用 Marshal  类读回这个值。

    这里我们声明一个IntPtr类型,并且使用Marshal类中的AllocHGlobal   共享方法从全局堆中分配一个4字节的内存,并返回它的地址,存储在 IntPtr   变量中。接下来我们使用Marshal类中的WriteInt32 方法将整数值写到内存中。

                   Dim ptr As IntPtr : Dim n As Integer = 123

                   tr = Marshal.AllocHGlobal(4)    '在进程的非托管内存中划分 4 字节大小的内存空间,并返回内存的指针地址

                   Marshal.WriteInt32(ptr, n)    '将32位有符号的整数写入划分的内存空间中

    Marshal.AllocHGlobal 方法的返回值为:指向新分配的内存的指针。

    用完后必须使用 Marshal.FreeHGlobal 方法释放划分的内存

    用下面的方法,你可以读指定位置的内存中的数。

                   Dim myInt As Integer = Marshal.ReadInt32(ptr)   '参数 ptr 为内存空间指针

    Marshal.ReadInt32 方法的返回值为:内存空间中的数据值。

    字符串的(读) 与 (写)

    在这个例子中,我们写一个托管字符串到堆中,并且随后读出来。

            Dim str As String = "hello world"  '字符串为引用类型,所以存储在非托管堆上
            Dim ptr As IntPtr = Marshal.StringToHGlobalAuto(str) '向非托管内存中复制托管内容,并转换为ANSI格式     

    这里,我们使用了 Marshal类 中的 StringToHGlobalAuto() 方法拷贝托管字符串到非托管内存中。将内存指针地址保存在 IntPtr 结构中。为了从指定内存位置读回字符串,我们使用  Marshal 类中的 PtrToStringAuto() 方法 。

            Dim mystring As String = Marshal.PtrToStringAuto(ptr) '从存储在非托管内存中的字符串复制等数量的字符到托管内存中

    结构的(读) 与 (写)

    在下一个例子中,我们将看到如何使用 Marshal 类和 IntPtr 结构,进行结构的读与写。

        Public Class A

            Public Structure PointA

                Dim x As Integer

                Dim y As Integer

                'Public Sub New(ByVal _x As Integer, ByVal _y As Integer)

                '    x = _x

                '    y = _y

                'End Sub

            End Structure

            Private Sub RWMemory()

                Dim myStruct As PointA   '其实.net框架自带 Point 坐标抽x,y类型(这里我们用自定义的结构类)

                myStruct.x = 100 : myStruct.y = 200

                '通过指定的字节数,在非托管堆划分内存空间(这里的字节数是结构对象,它又包含了x,y坐标值,所以字节数就是x,y坐标)

                Dim ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(myStruct))   '返回内存指针地址

                Marshal.StructureToPtr(myStruct, ptr, True '将数据从托管中(堆栈)封送到非托管(托管堆)的内存块中

                Marshal.PtrToStructure(ptr, myStruct.GetType)  '将数据从非托管(托管堆)的内存块中封送到托管(堆栈)

            End Sub

        End Class

    这里我们使用一个名为 PointA 的结构。这个代码,要注意,我们使用了 Marshal 类中的 SizeOf 方法,它返回结构变量的尺寸。 而StructureToPtr 接收要被写的结构变量,将数据从托管中(堆栈)封送到非托管(托管堆)的内存块中。一个IntPtr 实例将被返回,同时标明是否Marshal类应该调用。

      DestroyStructure 方法,建议标记为   True,如果标记为 false 可能导致内存泄漏。最后我们使用  Marshal 类的 PtrToStructure 方法反封送结构的值,展示了两个方法很好的实现了交互式功能。

    对象的(读) 与 (写)

    现在我们了解了如何用 Marshal 和 IntPtr 类读写值类型。

    而托管对象的处理稍微不同。需要使用 GCHandle 来读写这些对象,

    下面的代码显示如何做:

     Public Class Employee
            Public Name As String
            Public Salary As Decimal
     End Class
     Private Sub wp()
            Dim gh As GCHandle   'GCHandle类提供用于从非托管内存访问托管对象的方法
            Dim emp As New Employee
            emp.Name = "John"
            emp.Salary = 12345.67
            gh = GCHandle.Alloc(emp)  '从非托管内存中访问托管对象,Alloc 方法能指定的对象分配句柄
            Dim emp2 As Employee = gh.Target   '设置该句柄表示的对象
            gh.Free()   '释放GCHandles句柄
     End Sub

    这里,对于自定义的 Employee 类我们使用 GCHandle (碎片手机句柄) 类进行分配和释放内存。共享(静态)的 Alloc 方法接受对象并存储在内存中并一个 GCHandle 类的实例。

    我们可以使用这个对象的Target属性指向对象的引用。我们可以使用 GCHandle 实例的 Free() 方法来销毁相应的内存。

    总结

    在VB.NET中使用指针和非托管操作并不是易事,然而 IntPtr ,GCHandle 结构 和 Marshal 类可以让你完成类似的事情。

    作者
    Bipin   Joshi
    Bipin   Joshi  BinaryIntellect Consulting  的所有者,在那里他提供了许多关于 .NET 技术的训练程序。
  • 相关阅读:
    Linux 下安装JDK1.8
    Linux 常规操作
    C3P0连接池拒绝连接
    Oracle查看并修改最大连接数
    Oracle 建立 DBLINK
    Oracle 数据 update后怎么恢复到以前的数据
    Oracle 11g中解锁被锁定的用户
    身份证15位转18位
    Druid数据库连接池
    CentOS 下安装 LEMP 服务(Nginx、MariaDB/MySQL 和PHP)
  • 原文地址:https://www.cnblogs.com/lfls128/p/4932185.html
Copyright © 2020-2023  润新知