可恶的VB 快乐的.NET
文章来源:eNet硅谷动力
由于Windows操作系统的系统功能大部分是以API函数的形式提供给用户,而API函数的最初编写基本上都是以C语言实现的,由此一来,C语言的指针操作遍布Windows编程的方方面面,以前的VB程序员低人一等原因很简单:VB所有的版本(不包括VB.net)都不支持完整的指针操作,所以根本无法调用一些Wincows的核心功能!微软既不能完全将API实现的功能封装到控件库或类库中,又不允许VB程序员使用指针以调用!所以当时很多用VC可以实现的程序VB程序员就实现不了并不是水平问题,而是微软的产品方针在作怪。那年头VB唯一的优势就是可视化界面编程,以大量的控件实现一拖即用的效果,但时至今日,C#的可视化编程和VB差不多没什么两样,界面编写的优势一去不复返,根据VB6留下来的没法进行底层处理的烂名声,一大批的VB程序员转投C#阵营。综上所述,要完全发挥windows的能量,首先必须掌握指针这一武器,我们VB程序员需要指针这把利剑,以刺穿老板的心脏,让他们以与C#程序员同等的待遇对待我们。
一、.net指针的定义
1、内存地址
由于可能看本文的VB读者或许没有接触过指针的概念,所我在此对指针的概念定义简单作一阐述。
计算机的数据处理是基于内存地址进行的,所谓内存地址就是将内存可以存储数据的空间人为地按字节顺序进行数字编号,当程序对某一变量进行存取时计算机就根据变量的地址以确定变量中实际存储的数据内容。表1 是一个假想的内存地址示意图,我们可以看到,假设计算机将字节数组a实际分配到2000开始的连续三个地址空间后,对数组的赋值就会在这三个字节的内存空间中进行。另一个变量b由于是一个32位的整数,所以它占用了2003、2004两个地址的空间,在3070中放了一个byte类型的c变量
表1:假想的内存地址示意图
内存地址 0 1 …… 2000 2001 2002 2003 2004 …… 3070 …… 最高编号取决于内存的容量
变量实际数据 系统保留 系统保留 a b c 70 0
变量声明 dim a(2) as byte={a,b,c} dim b as int32=70 dim c as byte
2、指针的定义和作用
1)指针是一特殊的变量,它存储的就是一个内存地址编号,当某一个地址编号存储在指针变量中时,我们称指针指向以这个地址开始的数据。由于现在使用的win98win2000winxp2003等操作系统均为32位系统,所以都是采用一个32位的整数记录内存地址(可表示的范围就是0~2的31次方),所以以上系统的指针存储的实际数据都是一个32位的整数。(但以后的64位Windows系统会以一个64位的整数记录内存地址,到时指针存储的将是一个64位的整数)。
指针到底有什么作用呢,在汇编语言及C语言的黑暗时代(很久很久以前的事了……),使用指针存储一个地址,然后根据这个地址进行数据的存储是获得高效率程序的最好途径,(一来,计算机内部是直接使用地址作数据存储的,计算机并不明白在高级语言中定义如A,B这样的变量名;二来地址的传送很方便,如函数的参数是以堆栈的方式传送的,以一个32或16位(Dos系统是16位的)的整数表示的数组地址入栈处理比将整个数组的内存入栈高效得多),但现在我们并不需要使用这种底级的程序优化方法,我们学习旨针的宗旨应该是:因为API是用C编写的,所以我们必须找到一个兼容使用这些函数的途径。
2)在net中的指针是没有指向类型之分的,所谓指向类型就是指针地址代表的实际数据类型(这是针对C语言而言的,在C语言中,如果一个指针的指向类型为char型,那么这个指针的指向的数据就是指针指向的地址值及其后一连续编号的地址所代表的内存区间中储存的16数据)。也就是说,net指针存储的是一个地址,而这个地址表示的数据类型可以理解为以这个地址开始的内存空间所能表示的各类数据。如表1中,假设我们使用一个储存着地址2000的指针p,我们可以理解p指向一个字节类型,其值就是a;也可以理解p指向一个3位字节的数组,其值是一个连续的a,b,c。
二、指针的使用
在本文的以下部分,我们假定程序已包括以下语句以引用net中处理指针的命名空间:
Imports System.Runtime.InteropServices
, GCHandleType.Pinned)
Dim tpObject As IntPtr = thObject.AddrOfPinnedObject() ’取得指向字节数组的指针
’取得一个指向32位内存数据的指针,
’由于使用gchandle取指针的方法只能对引用的对象有效,
’所以对如int32等值类型必须使用将其封装成为一个对象的方法以变为引用类型
Dim ti32Test As Object = Convert.ToInt32(0)
’以下语句告诉net垃圾回收进程不对ti32test进行处理,也就是说ti32Test的内存位置固定不变。
Dim thObject1 As GCHandle = GCHandle.Alloc(ti32Test, GCHandleType.Pinned)
Dim tpObject1 As IntPtr = thObject1.AddrOfPinnedObject() ’取得ti32Test的首地址
Dim tudtTest1 As DEFUDT_Test
’由于结构是一种值类型变量,为保证指针申请方便,我们申请
’取得一个和结构tudtTest1大小一致的字节数组指针,只要空间占用长度和结构一样就可以了
’由于net在结构封装中会插入额外的数据位,所以一定要用sizeof方法得到结构在非托管使用时的实际大小
Dim tudtTest(Marshal.SizeOf(tudtTest1)) As Byte
Dim thObject2 As GCHandle = GCHandle.Alloc(tudtTest, GCHandleType.Pinned)
Dim tpObject2 As IntPtr = thObject2.AddrOfPinnedObject() ’取得指向结构的指针
’在这儿你可以写对指针处理的任意代码(在例2中会给予补充)……
’在使用完毕后一定要释放指针指向的内存块,让垃圾回收器可对这个内存块回收处理
If thObject.IsAllocated Then
thObject.Free()
End If
If thObject1.IsAllocated Then
thObject1.Free()
End If
If thObject2.IsAllocated Then
thObject2.Free()
End If
End Function
上例中指针流程处理可以归纳为:
1、 定义一个具有合适内存长度的引用变量(关于引用变量和值变量的差异可以参观vb.net的书籍)
2、使用GCHandle.Alloc方法将变量的内存区域固定下来。
3、使用GCHandle对象的AddrOfPinnedObject取得该内存区域的首地址并赋值给指针变量.
4、对指针进行操作
5、使用GCHandle对象的free方法释放指针指向的内存区域以便net垃圾回收器可以回收这个内存空间
2、指针所指向数据的存取
在.net中,对指针指向数据的存储函数都封装在marshal类中,主要的函数包括:Copy、PtrToStringUni 、PtrToStructure 、OffsetOf、WriteXXX,RreadXXX等,其中WriteXXX的表示向指针所表示的地址中写入XXX类型的数据,而ReadXXX中作用就是将指针所在地址的数据以XXX类型方式读出。看例程2,我们使用这些方法演示对例1那几个指向不同类型数据的指针作数据存/取操作。
例2:演示向例1申请得到的几个指针执行写入及读取数据的操作.
Marshal.WriteInt32(tpObject1, 0, Convert.ToInt32(77)) ’向ti32Test变量指向的地址写入32位整数77
MsgBox("现在ti32Test的值为:" & ti32Test) ’因为变量存储地址的数据已改为77,所以显示为77
’以下这句之所以可行,因为ti32Test是32位整数,而tpObject指向的tabytTest数组刚好有4个元素
’而每一个byte元素都占用8位,合起来就是32位,和ti32Test占用的空间一样。这就印证了前面提’
’到的net中指针没有指向类型的说明。
Marshal.WriteInt32(tpObject, 0, ti32Test)
’以下代码再将tabytTest字节数组的内容理解为一个int32整数,’并将值赋值给tudtTest结构中的int32元素
’我们使用Marshal.OffsetOf(GetType(DEFUDT_Test), "i32a").ToInt32以取得i32a元素在结构中的内存偏移位置
’所以New IntPtr(tpObject2.ToInt32 + Marshal.OffsetOf(GetType(DEFUDT_Test), "i32a").ToInt32)就临时产生了
’一个指针并指向i32a所在的内存地址(, 这个方法也说明了指针可以以字节为单位进行加减计算以指向合适的变量。
’Marshal.ReadInt32的作用是从指针中读取一个32整数。
Marshal.WriteInt32(New IntPtr(tpObject2.ToInt32 + Marshal.OffsetOf(GetType(DEFUDT_Test), "i32a").ToInt32), _
0, Marshal.ReadInt32(tpObject))
’这儿可以将字节数组的内容复制到真正的结构中
MsgBox(Marshal.OffsetOf(tudtTest1.GetType, "i32a").ToInt32)
tudtTest1 = CType(Marshal.PtrToStructure(tpObject2, GetType(DEFUDT_Test)), DEFUDT_Test)
MsgBox("结构tidtTest1中i32a元素的值为:" & tudtTest1.i32a) ’此处将显示刚赋的值77
三、指针的疯狂应用
大伙应该已对指针的应用有了初步了解,最后我将已一个指针的简单例程所为本文的结束,这是我在论坛请教C#和VB.net的区别时,一个头上有两颗星星的大侠给出的一段他认为VB.net不具备指针功能而
补充:Web开发 , ASP.Net ,