65.9K
CodeProject 正在变化。 阅读更多。
Home

使用未公开函数在 Visual Basic 中使用指针

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (24投票s)

2003年12月19日

CPOL

5分钟阅读

viewsIcon

211774

VB-> 使用 VarPtr, StrPtr, ObjPtr 和 CopyMemory API 实现指针

引言

也许你们中的一些人会认为我写错了标题,是的……有时我会弄混,但这次不会。你可以在 Visual Basic 中实现指针。有一篇关于“如何在 Visual Basic 中实现指针”的优秀文章,我推荐你在阅读本文之前或之后阅读它,如果你还没有读过的话。

未公开的函数

现在我们来看看这些未公开的函数是什么。打开 **对象浏览器** 窗口,选择 **VBA** 库,右键单击对象浏览器,然后从菜单中选择 **显示隐藏成员**。现在选择 `_HiddenModule` 类,你将看到三个隐藏函数 – `ObjPtr`, `StrPtr` 和 `VarPtr`。这些函数没有被公开,因为微软不保证它们会在 VB 的未来版本中可用。

  • VarPtr - 返回变量的内存地址
  • StrPtr - 返回字符串内容所在内存的地址
  • ObjPtr - 返回对象(接口)的内存地址

当然,我们可以用这些函数查看变量的内存地址,但还缺少一些东西。我们如何设置内存地址的*内容*呢?为此,我们需要 API 函数 CopyMemory,它位于 kernel32.dll 中。

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
                   (Destination As Any, Source As Any, ByVal Length As Long) 

此函数将一块内存从一个内存地址复制到另一个内存地址。请注意,根据声明,DestinationSource 是通过引用传递的(默认是 ByRef),这意味着 CopyMemory 函数期望 DestinationSource 参数是内存地址。尽管它们被声明为 ByRef,但我们可以通过在调用中包含 ByVal 来覆盖它(覆盖……你不喜欢这个词吗?)。最后看看那个 Any 数据类型。Any 意味着我们可以为 DestinationSource 参数传递“任何”数据类型,但最终传递的是一个 4 字节长的内存地址。请记住这一点。这在处理 Win32 API 时非常重要。

使用 VarPtr

指针 是一个包含另一个变量在内存中地址的变量。在 Windows 中,存储一个内存地址需要 4 个字节。因此,如果我们想在 VB 中声明一个指针,我们必须使用 Long 数据类型。

看看这段代码示例

Dim myInt As Integer         
Dim ptr As Long            ' Long pointer – need 4 bytes to hold a memory address
    
ptr = VarPtr(myInt)        ' ptr now points to myInt
Debug.Print ptr            ' this will print the memory address of myInt
    
CopyMemory ByVal ptr, 123, 2    ' copy 2 bytes (Integer size - 2 bytes)
   
Debug.Print myInt            ' now contains the value 123

如果你运行这段代码,值 123 将被复制到 myInt 的内存地址,所以最终 myInt 将包含复制的值 123。

现在仔细看看这一行

CopyMemory ByVal ptr, 123, 2    ' copy 2 bytes (Integer size - 2 bytes)  

变量 ptrmyInt 的指针,换句话说 ptr 存储了 myInt 的内存地址。所以如果我们写这行代码时不加 ByVal 关键字,会发生什么?因为 Destination 被声明为 ByRef(默认),所以传递的将是 ptr 的内存地址而不是 myInt 的内存地址。

毫不费力地,我们可以将它重写成如下形式

CopyMemory myInt, 123, 2

所以 myInt 是以 ByRef 的方式传递的,换句话说,传递的是 myInt 的内存地址,这正是我们想要做的。记住这个小技巧:当你想传递的内存地址在一个变量中时,你必须使用 ByVal 关键字来覆盖 ByRef

如果我们想将变量 intA 的值复制到变量 intB,我们可以通过以下方式实现:

CopyMemory ByVal VarPtr(intB), ByVal VarPtr(intA), 2

CopyMemory intB, intA, 2

希望你现在对 CopyMemoryVarPtr 没有疑问了。让我们继续 StrPtr

使用 StrPtr

你有没有想过 Len(str) 函数是如何工作的。嗯,你可能会猜它从 string 的开头开始计数,直到找到一个 null 字符。再猜猜,VB 处理 strings 的方式不是这样的。当我们声明一个 String 类型的变量时,我们声明的是一种名为 BSTR 的数据类型的成员,它代表 Basic StringBSTR 是一个指向 Unicode(每个字符 2 字节)字符数组的指针,该数组前面有一个 4 字节的长度字段,并且以一个 2 字节的空字符结尾。看下面的图以获得更好的理解。

所以当我们写代码时

Dim str as string
str = "hello"

变量 str 实际上是 BSTR 类型的一个成员,它存储了实际 Unicode 字符数组开头的内存地址。注意它并不是指向 4 字节的长度字段。这个长度字段存储了不包含终止空字符的字节数。所以 "hello" 在 Unicode 中表示需要 10 个字节。

Dim str As String
Dim length As Long                  ' variable to hold the length of string
Dim ptrLengthField As Long          ' pointer to 4-byte length field
          
str = "hello"
Debug.Print StrPtr(str)             ' address of the character array
    
ptrLengthField = StrPtr(str) - 4    ' length field is 4 bytes behind

' // CopyMemory ByVal ptrLengthField, 200&, 4
CopyMemory length, ByVal ptrLengthField, 4

' // this is also correct
' // CopyMemory ByVal VarPtr(length), ByVal ptrLengthField, 4

Debug.Print length                  ' number of bytes (without null terminator)
Debug.Print Len(str)                ' number of characters

在你运行这段代码之前,我们先理清一些事情。检查这一行

ptrLengthField = StrPtr(str) - 4    ' length field is 4 bytes behind

StrPtr(str) 返回的是字符数组的地址,所以我们必须减去 4 个字节才能得到长度字段的地址。

看看这一行

CopyMemory length, ByVal ptrLengthField, 4

变量 ptrLengthField 存储了 str 的长度字段的内存地址,所以我们必须使用 ByVal 关键字将其按值传递。

现在运行代码,输出是

Debug.Print length                  ' number of bytes (without null terminator)
Debug.Print Len(str)                ' number of characters

将是

10
5

变量 length 被设置为 10,因为 Unicode,"hello" 占用 10 个字节,而 Len(str) 返回 5,这是 "hello" 中的字符数。现在,让我们找出 Len() 函数是如何真正工作的!

取消注释这一行

CopyMemory ByVal ptrLengthField, 200&, 4

这里发生的是值 200 被复制到 str 的长度字段,取代了原始值 10200& 中的 & 符号是为了表明将 200 视为 LongLong 的类型声明字符是 &)。

运行代码,输出是

Debug.Print length                  ' number of bytes (without null terminator)
Debug.Print Len(str)                ' number of characters

将是

200
100

当我们 str 包含 "hello" 时,Len(str) 返回 100,呃?所以 VB 返回了我们长度字段除以 2 的值。我想你现在知道 Len() 对于 strings 是如何工作的了。

使用 ObjPtr

最后我们将看到 ObjPtr 的实际应用。

Dim obj As New Form1
Debug.Print ObjPtr(obj) ' gives the address to the object (new instance of Form1)
Debug.Print VarPtr(obj) ' gives the address to the variable - obj

这里没有什么需要解释的。这里有另一个简单的代码示例。

Dim objA As New Form1
Dim objB As New Form1

Debug.Print "before"
Debug.Print ObjPtr(objA)
Debug.Print ObjPtr(objB)

Set objA = objB

Debug.Print "after"
Debug.Print ObjPtr(objA)
Debug.Print ObjPtr(objB)

输出将是:

before
 1849600 
 1761448 

after
 1761448 
 1761448

正如你所见,在这一行之后

Set objA = objB

objA 现在指向与 objB 相同的内存位置,正如它应该的那样。你也可以用以下方式替换上面那行

CopyMemory ByVal VarPtr(objA), ByVal VarPtr(objB), 4

或者更简单的版本

CopyMemory objA, objB, 4

它能工作,但不要使用这种方式来设置对象,因为有时当对象被销毁时会抛出非法操作。

就是这样。这些函数用于快速 string 处理例程和子类化。我在这里不打算讨论它们,因为……嗯……我对此一无所知。记住……我只能为你指明方向,而你需要自己走过去!

编码愉快!!!

历史

  • 2003 年 12 月 18 日:首次发布
© . All rights reserved.