在VB5中使用指针

thriller 2000-02-23 09:23:00
加精
有的人看到这题目,会大嚷起来:“NO,NO,NO,VB中哪里来的指针?书上说VB不支持指针!”
别着急,听我慢慢道来。
在一般的VB教科书中,你不会发现有关“指针”这个概念,你甚至可能不会发现“指针”这个词。或者,你的唯一发现是:“VB不支持指针”。
难道我们真的无法在VB中使用指针吗?抑或VB中真的没有指针吗?
在VB5出现以前,使用指针是非常困难的。——但我没说不可以!——而现在,我们可以在VB中使用指针,而且VB中确实存在指针。本文只针对VB5、6。
例如,当你使用 Dim sTmp As String时,有没有想过这意味着什么?在遥远的VB3时代,我们无须知道也不必考虑,现在,我可以告诉你的是,你已经使用了指针!sTmp现在是指向一个内存地址的指针——不管你愿不愿意知道。不过,跟C中不同的是,这个地址包括字符串的长度。
数组又是什么?在内存中是什么形态?数组其实是对指向内存中数组项的地址的指针的引用。让我们来证明这一点。
有一个用来画多边形的API——Polygon,它在Win32API.txt中的定义是:
Declare Function Polygon Lib "gdi32" Alias "Polygon" _
(ByVal hdc As Long, lpPoint As POINTAPI, _
ByVal nCount As Long) As Long
有关lpPoint项,《Win32 程序员参考》中的解释是:
“Points to an array of POINT structures that specify the vertices of the polygon”,意即“指向一个指出多边形各顶点的POINT结构数组”,这是虾米意思?不知你有没有注意到,lpPoint的前面不象其他两项有一个“ByVal”!这是关键。这说明,对于这个参数,要“按引用传送”,而不是“按值传送”,其实质,就是传送指针。如何用VB语言调用呢?试试下面的方法如何:

Me.AutoRedraw = True
Dim p(5) As POINTAPI '即POINT结构的VB描述
Dim i%
For i = 0 To 5
p(i).x = Rnd * 100
p(i).y = Rnd * 100
Next
Polygon hDC, p(), 6
Me.Refresh

VB运行到黑体字的一行就停下,轻声告诉你说:“ByRef 参数类型不符”。
试使用下面的语句来代替:
Polygon hDC, p(0), 6
成功了。
p(0)所引用的就是数组的地址,数组中所有的其它数据都在它后面排成一排。
VB的作者通过隐藏和包装指针、内存地址和许多晦涩难懂的概念,使VB成为一种高级而相对简单,易用而功能强大的编程工具,这是我们VB爱好者的幸运;但同时,我们要想提高基于VB的应用程序的性能,或实现更高级甚至系统级的操作,必须掌握教科书所没有告诉你的,因为要这样就必须掌握API,而许多API中充满了指针的概念,那些原来我们不需要考虑的问题,现在来到我们面前。
我们来看一个例子:
Dim a() As Byte
a = "指针"
Dim b(0 To 3) As Byte
b = a
这段代码用来给一个字节数组“a”赋值,并试图给另一个字节数组“b”赋值。
当然运行时VB会毫不犹豫的告诉你,“不能给数组赋值”。
这时怎么办?容易!循环历遍a、b数组,把a的每个元素赋值给b的每个元素。对!这是我所喜欢的纯VB方法,安全而可靠。但问题是如果这个数组很大,例如,200000个,这个方法就非常耗时。为什么不能简单的使用b=a的格式呢?答案很简单,VB不支持数组的直接赋值。要提高速度和简化操作,只有选择使用API。下面我们来讨论实现简单复制的方法。
聪明的你一定想到,把数组a指向的数据复制到数组b指向的位置,不就可以了吗?确实是这样。但查遍《Win32 程序员参考》(C和DEPHI所附的Win32.hlp),似乎找不到直接实现这种功能的API。别急,这个API隐藏kernel32.dll里,原名是RtlMoveMemory。
为了便于记忆,可以这样声明:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal ByteLen As Long)
pDest ' 要复制到的地址指针
pSource ' 要复制的数据源地址指针
ByteLen ' 要复制的字节数
看,前两个参数都要求传递一个指针。下面是具体实现的例子:

Dim a() As Byte
a = "指针"
Dim b(0 To 3) As Byte
CopyMemory b(0), a(0), 4 ' b=a

怎么样,简单和直观多了吧?而且,你获得的速度提升是非常可观的,200000记录情况下,大约是用循环遍历法所消耗时间的1/100。
另外,C中可以直接对数组赋值,你也可以用Array来对VB数组赋值,但CopyMemory更简单、直接和强大——特别是对于需要从另外一个数组的某项开始而非第一项的赋值。例如:
Dim a() As Byte
a = "指针"
Dim b(0 To 1) As Byte
CopyMemory b(0), a(2), 2
经过这段代码,在数组b中包含的是数组a的后两个元素。
等你明白VB用户自定义类型——Type也是指针时,下面的代码就显而易见了:
Dim p1 As POINTAPI
p1.x = 10
p1.y = 1000
Dim p2 As POINTAPI
CopyMemory p2, p1, LenB(p1)
而以前,复制类型结构只能采用LSet。
那么如何使用CopyMemory 在字符串和字节数组间复制呢?问得好:
Dim a(3) As Byte
Dim s As String
s = "指针"
CopyMemory a(0), ByVal s, LenB(s)
注意s前面的ByVal!String类型本身就是地址指针,不能按引用,而必须按值传递。当然,这里的“按值传递”并不是说真要把s复制到堆栈上,而是按s所指向的地址的值。
另外,由于CopyMemory传递字节数,必须保证第三个参数合法且准确,否则,可能会发生崩溃。另外,复制汉字字符串时应注意Unicode格式转换。
看,CopyMemory带来许多方便吧?
但我们还没有看到它更擅长的呢。
稍微有胆量试图超越教科书的VB爱好者可能会弄明白,事件驱动的VB如何使瞬息万变的Windows 消息世界变得安静异常,一个VB程序员可能完全不用理会什么是消息。然而,存在并不以人的意志为转移。
篇幅所限,无法在此详述消息的捕获和处理,而只讲一下如何在消息处理中应用CopyMemory。这里假定大家对消息有一定了解,否则,请先了解一下,再继续读下去。
比如在自画菜单(Ownerdrawn Menus)技术中,对WM_DRAWITEM消息的截获至关重要,当我们应用AddressOf拦截此消息并希望分析其内容时,CopyMemory决不是可有可无的。
大家知道,一个消息拦截处理函数需要处理下面4个参数:
hWnd 发送消息的窗口句柄;
Msg 所截获的消息;
LParam 第一消息参数;
WParam 第二消息参数;
对于WM_DRAWITEM消息来讲,LParam消息为标识,WParam为DRAWITEMSTRUCT结构(即用户定义类型)的地址。用通常的VB方法,无法从一个地址得到一个用户定义类型。但是DRAWITEMSTRUCT中的信息我们如此迫切的需要,该怎么办呢?现在你无须忍受我曾经历的这种痛苦了。请看CopyMemory的杰作:

Dim di As DRAWITEMSTRUCT
CopyMemory di, ByVal lParam, LenB(di)

就这么简单,现在di已经充满我们所需要的信息了。
如果必要,使用VB的隐藏函数VarPtr、StrPtr等可以获得变量或字符串的地址。但这并不能保证在以后的版本中继续得到支持。
综上所述,可见VB中同样可以使用指针,而且用的好的话可以极大提高程序性能、突破VB限制和简化代码。
注意:如同使用任何API一样,不正确使用CopyMemory可能导致数据错误或系统崩溃,请记得先保存您的代码,并认真检查所有参数是否合法。
下面几种情况下可能需要指针和CopyMemory:
1、快速复制一个结构(VB用户类型——Type)的全部或部分,几乎没有更好的复制部分结构的办法;
2、快速复制一个数组,可以从数组的任意位置开始复制;
3、快速转换字节数组和字符串;
4、快速处理大文件,可以将文件存入字节数组,并用CopyMemory得到任意数据;
5、从内存地址得到数据,可以复制到任何VB变量;
至此,大家已经发现了一个强大的武器,用好它,无疑会给您的编程带来突飞猛进的新进展。
另外,AddressOf语句本身就是对指针的使用,这可不是一篇小短文所能描述的。我可以为此写一本这么厚的书了。
作者水平所限,也由于本文观点仅代表个人观点而非MS官方描述,如有不同意的地方欢迎来信指正和商榷。
请到碧海港湾(http://thriller.533.net)或来信(mailto:thriller@163.net)说明您的观点。

...全文
509 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhoujiehg 2001-03-06
  • 打赏
  • 举报
回复
to tbbt:
去查ReadProcessMemory函数。
zhoujiehg 2001-03-06
  • 打赏
  • 举报
回复
to Un1:

你的VB和ASM混合编程对我很有启发。以往一直认为这种事情非要DLL做。
没想到竟有人想出了从更底层修改函数代码达到CallAddr的妙招。

你这个朋友我交定了。
TBBT 2000-03-07
  • 打赏
  • 举报
回复
To Unl:
能否帮我写一段读写指定内存的代码,如A000:0000,类似于VBASM中的VBPEEKW,VBPOKEW函数,VBASM的函数无法访问超出&HFFFF的地址。
我急需这个功能,让你费神,在此先多谢了!
dongcan 2000-03-06
  • 打赏
  • 举报
回复
thank you!
Un1 2000-03-05
  • 打赏
  • 举报
回复
上面一贴将到如何用VB实现代码指针,下面在说说如何利用数据指针。

很多人可能想实现TreeView的Node, Nodes那样的对象和集合对象, 但MSDN中提到那将会无限耗费内存知道程序结束,在Nodes.Remove一个Node时,Node并没有得到释放,原因就是Node.Collection之中仍然保存Nodes的引用, MS称之为“循环引用”。那我们是不是就要放弃我们的想法呢?是不是又VB没用论呢?CopyMemory告诉你不!

如果Node对象的Parent属性这样写,就不会有任何问题:

'Node.CLS
Public Property Get Collection() As Nodes
Set Collection = mCollection
End Property
Friend Property Set Collection(New_Collection As Nodes)
ObjectCopy mCollection, New_Parent
End Property

Private Sub Class_Terminate()
ObjectCopy mCollection, Nothing
End Sub

'Funcs.BAS
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

Public Sub ObjectCopy(Des As Object, Sou As Object)
CopyMemory Des, Sou, 4
End Sub

'Nodes.CLS
Private Sub Class_Terminate()

Dim n As Node
For Each n In mCol
Set n.Collection = Nothing
Next

Set mCol = Nothing
End Sub
Un1 2000-03-04
  • 打赏
  • 举报
回复
各位:
下面的代码能做什么?我们可以在VB中调用一个藏于RES文件中的子程序!这是不是意味着用VB做CIH能做到的事情?那要看你的智慧了!暂且称之为VB, ASM混合编程吧。
所以用类似方法可以很Easy地动态调用DLL中的函数或系统函数!
不过出“非法操作”这样的错误可不要怪我哟!^_^!

'Module ResFuncs.BAS

Public Const gconstrResCode As String = "Codes"
Public Const gconstrResCodeData As String = "#100"

Private Const conFuncvbOut As Long = &H404B22 - &H404908
Private Const conFuncvbOutw As Long = &H404B34 - &H404908
Private Const conFuncvbInp As Long = &H404B45 - &H404908
Private Const conFuncvbInpw As Long = &H404B56 - &H404908


Public Sub vbOut(ByVal Port As Integer, ByVal Data As Byte)
CallAddr conFuncvbOut
End Sub

Public Sub vbOutw(ByVal Port As Integer, ByVal Data As Integer)
CallAddr conFuncvbOutw
End Sub

Public Function vbInp(ByVal Port As Integer) As Byte
CallAddr conFuncvbInp
End Function

Public Function vbInpw(ByVal Port As Integer) As Integer
CallAddr conFuncvbInpw
End Function



'Module ResCall.BAS

Private Const conlngCodes As Long = &H55858
Private Const conlngCodes1 As Long = &HE0FF

Private Const constrResCall As String = "ResCall"
Private Const constrResCallErrorResourceNotFound As String = "代码资源未找到"
Private Const constrResCallErrorNotInit As String = "代码没有初始化"

Private Declare Function FindResourceEx Lib "kernel32" Alias "FindResourceExA" (ByVal hModule As Long, ByVal lpType As String, ByVal lpName As String, ByVal wLanguage As Long) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Boolean

Public Sub CallAddr(ByVal Addr As Long)
Err.Raise 17, constrResCall, constrResCallErrorNotInit
'9: pop eax;
'0040B518 58 pop eax
'10: pop eax;
'0040B519 58 pop eax
'11: add eax, 0x80000000;
'0040B51A 05 00 00 00 00 add eax,00000000h
'12: jmp eax;
'0040B51F FF E0 jmp eax
End Sub

Private Function GetAddr(ByVal Addr As Long) As Long
GetAddr = Addr
End Function

Public Sub PInitAddrCall()

Dim lh As Long
lh = App.hInstance

Dim lr As Long
lr = FindResourceEx(lh, gconstrResCode, gconstrResCodeData, 0)

If lr = 0 Then Err.Raise 48, constrResCall, constrResCallErrorResourceNotFound

CopyMemory lr, ByVal lr, 4
lr = lh + lr

Dim lc As Long
lc = GetAddr(AddressOf CallAddr)

Dim lp As Long
lp = GetCurrentProcess()

Dim l As Long
WriteProcessMemory lp, ByVal lc, conlngCodes, 3, l
WriteProcessMemory lp, ByVal lc + 3, lr, 4, l
WriteProcessMemory lp, ByVal lc + 7, conlngCodes1, 2, l

End Sub

Un1 2000-03-04
  • 打赏
  • 举报
回复
peacock :
多谢了!如果是VBASM那我不需要,不过我认为那里面的函数用VB自己就可以搞定!
peacock 2000-03-04
  • 打赏
  • 举报
回复
我是在http://leihuan.533.net/里下载的,32位的我还没找到!
还有Un1的邮箱不接受我的E-Mail!
thriller 2000-03-02
  • 打赏
  • 举报
回复
关于能否用bitblt函数把屏幕上的一个区域的点的颜色
用指针符给一个数组,答案是I do'no。
不过可以不用bitblt实现。:-)
这个问题无须探讨,兄弟也不敢当。有关的代码到处都是。
关于VBASM,我不敢多说什么。不过我无论如何无法运行。
而且,好象并不是关于使用ASM的。
peacock 2000-03-02
  • 打赏
  • 举报
回复
thriller:sorry!我给你的版本太低,是96的16位DLL,它是用在Windows 3.x上的,与Windows 9.x不兼容。不过它能对硬件的底层进行操作,是比较危险的DLL!!!我还没找到新版本的VBASM,你如果找到,请给我也来一份。
TBBT 2000-03-01
  • 打赏
  • 举报
回复
对了,我也想要一分VBASM,多谢!
tbbt@163.net
TBBT 2000-03-01
  • 打赏
  • 举报
回复
这位仁兄,小弟想和你探讨一下;
我想实现类似于getimage函数的功能,用bitblt函数把屏幕上的一个区域的点的颜色
用指针符给一个数组,不知可不可以?如行,how do?
Un1 2000-02-29
  • 打赏
  • 举报
回复
peacock:
是什么东东,VBASM吗,如果不是,给一分我吧,谢了:
Un1@21cn.com
peacock 2000-02-26
  • 打赏
  • 举报
回复
thiller,你很厉害,不过我不想讨论“VB指针”问题,看你是一位高手,我给你一个很好很好很好很好很好很好很好很好的东东!哈哈!VB能编汇编了!去你的E-Mail中看一下吧(35K)
thriller 2000-02-24
  • 打赏
  • 举报
回复
多谢!
不过我确实相信,VB可以做许多事情,不管DELPHIs说什么。
bjseaman 2000-02-24
  • 打赏
  • 举报
回复
Lin真会拍马屁!hehe
不过我也想,xixi
可是没有好词儿,就省了。
小弟是初学者,不敢高攀做朋友,
只要我问了问题你能帮我回答,我就谢谢你了!
E-Mail:bjseaman@sina.com
Un1 2000-02-24
  • 打赏
  • 举报
回复
我要补充一下:
动态对象(具有可变属性的对象)都可以实现。
其实所有VB程序员应该去学一下ASM,Dephi能做的VB也可以,甚至Call Ptr。
兴导 2000-02-23
  • 打赏
  • 举报
回复
文章非常好,我要转载,哈哈!
sywyh 2000-02-23
  • 打赏
  • 举报
回复
我习惯在VB中用C的参数调用方法,也就是引用。
毫无疑问,是传址调用,也就是指针。
其实这个问题应该归类于VC++,你说对吗?
只有VC程序员和用Windows API的程序员关心这件事。
Lin 2000-02-23
  • 打赏
  • 举报
回复
VB高手!!我知道的你早已知道,我不知道的你也知道。小弟想跟你交个朋友(揩油~=~)。
EMail: jiazhonglin@21cn.com

7,762

社区成员

发帖
与我相关
我的任务
社区描述
VB 基础类
社区管理员
  • VB基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧