ReadProcessMemory 函数的 ByRef 与 ByVal

ByRef vs. ByVal for the ReadProcessMemory function

我在VBA/VB6中使用了windows函数ReadProcessMemory,我不明白为什么我将lpBuffer的传递机制更改为[=18] =]ByVal 函数仍然修改通过此参数传递的原始对象的值。在文档中,此参数被指定为应通过引用传递的输出。不应该将传递机制更改为按值来防止原始实例被修改吗?为什么不呢?

Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any  _
,byVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long

@polisha989 我相信 lpBuffer 中的"lp" 指示类型为长指针。我怀疑因为你传递的对象是一个指针,如果它是按值或引用传递的,它不会有任何区别。即使您按值传递参数,系统也只是制作指针的副本 - 因此两个对象都将指向内存中的相同值。因此,无论您是通过 ref 还是通过 val 传递指针,您看到更新值的原因是因为指针就是这样做的;它指向内存中的一个值。不管你有多少个指针,如果它们都指向内存中的同一个地方,它们都会显示相同的东西。

如果您要接到 API 电话,请提一句忠告,您真的不能花 太多 太多时间浏览 MSDN。您对如何 函数的工作原理了解得越多,实现起来就越容易。确保将正确的对象类型传递给函数将帮助您确保获得预期的结果。

As Any 声明永远不会按值传递。

When you remove type restrictions, Visual Basic assumes the argument is passed by reference. Include ByVal in the actual call to the procedure to pass arguments by value.

请注意我为 "never."

的例外添加的斜体

首先,对于 _Out_ 参数,ByVal .. As Any 不是一个好主意(我什至不确定这是否可能);如果你为此使用 ByVal,你希望它是 As Long(请参阅下面的 "why")。

因此,对于具有一个或多个 _Out_ 参数的 API 表示 buffer/variable/memory 位置的 APIs,有两种方法(无论如何对于每个相关参数)来编写声明, 取决于你想传递什么:

  1. ByRef lpBuffer As Any,或简单地 lpBuffer As Any:如果在调用 API 时,您打算将 _Out_ 参数的声明中使用它=85=]实际变量 数据应复制到的位置。例如,您可以像这样使用字节数组:

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
   ByVal lpBaseAddress As Long, lpBuffer As Any, ByVal nSize As Long, _
   lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lWrittenBytes As Long, lReturn As Long
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, bytBuffer(0), 256, lWrittenBytes)

请注意,被调用者(此处为 ReadProcessMemory())将用数据填充您作为 lpBuffer 提供的任何内容,而不管传递的变量的实际大小如何。这就是为什么必须通过 nSize 提供缓冲区大小的原因,因为否则被调用者无法知道所提供缓冲区的大小。另请注意,我们正在传递 (字节)数组的第一项,因为这是被调用者应该开始写入数据的地方。

使用相同的声明,如果您愿意,您甚至可以传递 long(例如,如果您要检索的是地址或某种 DWord 值),但是 nSize 必须 为 4 个字节(最多)。

另请注意,最后一个参数 lpNumberOfBytesWritten 也是一个 _Out_ 参数并已通过 ByRef,但您无需向被调用方提供其大小;那是因为调用者和被调用者之间有一个协议,无论传递什么变量,总是会写入恰好 4 个字节。

  1. ByVal lpBuffer As Long:如果在调用 API 时,您打算传递 内存位置 以32位值的形式(即指针);被传递的 Long 的值将 不会 改变,将被覆盖的是 内存位置被引用 的值Long。重复使用相同的示例,但声明略有不同,我们得到:

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
   ByVal lpBaseAddress As Long, ByVal lpBuffer As Long, ByVal nSize As Long, _
   lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lPointer As Long, lWrittenBytes As Long, lReturn As Long
lPointer = VarPtr(bytBuffer(0))
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, lPointer, 256, lWrittenBytes)
' If we want to make sure the value of lPointer didn't change:
Debug.Assert (lPointer = VarPtr(bytBuffer(0)))

看,这实际上又是一回事,唯一的区别是我们提供指向 bytBuffer 的指针(内存地址)而不是直接传递 bytBuffer。我们甚至可以直接提供 VarPtr() 返回的值,而不是使用 Long(这里是 lPointer):

lReturn = ReadProcessMemory(hTargetProcess, &H400000&, VarPtr(bytBuffer(0)), 256, _
          lWrittenBytes)

警告 #1:对于 _Out_ 参数,如果您声明它们 ByVal,它们应该 始终是 As Long。这是因为调用约定期望该值恰好由 4 个字节(32 位 value/DWORD)组成。例如,如果您要通过 Integer 类型传递值,您会得到意想不到的行为,因为将用作内存位置值的是 Integer 的 2 个字节加上内存中 Integer 变量内容之后的下一个 2 个字节,可以是任何内容。如果这恰好是被调用者将写入的内存位置,您可能会崩溃。

警告 #2:您不想使用 VarPtrArray()(无论如何都需要显式声明),因为返回的值将是 SAFEARRAY 结构的地址 数组(项数、项大小等),而不是指向数组数据的指针(与数组中第一项的地址相同) .

本质上,对于 Win32 APIs(即 stdcall)参数 总是作为 32 位值传递,总是。这些 32 位值的含义将取决于特定 API 的期望,因此它的声明必须反映这一点。所以:

  • 无论何时声明参数 ByRef,将使用的是传递的任何变量的内存位置;
  • 无论何时声明参数 ByVal .. As Long,将使用的是传递的任何变量的(32 位)值(该值不一定是内存位置,例如 hProcess ReadProcessMemory() 的参数).

最后,即使您声明了一个 _Out_ 参数 ByRef(或者,例如,这是声明 API 的方式并且您无法更改它,因为如果来自一个类型库)你总是可以传递一个指针而不是实际变量,方法是在调用时在它之前添加ByVal。回到 ReadProcessMemory() 的第一个声明(当 lpBuffer 声明为 ByRef 时),我们将执行以下操作:


Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
   ByVal lpBaseAddress As Long, lpBuffer As Any, ByVal nSize As Long, _
   lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lWrittenBytes As Long, lReturn As Long
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, ByVal VarPtr(bytBuffer(0)), 256, _
          lWrittenBytes)

添加ByVal 告诉编译器应该传递到堆栈上的不是VarPtr() 的地址,而是VarPtr(bytBuffer(0)) 返回的值。但是如果参数被声明为 ByVal .. As Long 那么你别无选择,你只能传递一个指针(即内存位置的地址)。

注意:在整个正在讨论的体系结构中,这个答案假设是 IA32 或其仿真

CBRF23 是正确的。当 API 函数有一个字符串参数时,您传递的值是一个指向缓冲区的长指针。该指针值是一个长整数,并且在指针的生命周期内其值是不可变的。因此,您是否拥有指针值的两个副本无关紧要,因为该值永远不会改变。

无论传递 byref 还是 byval,值都会改变,因为改变的是 lpbuffer 指向的缓冲区中的内存。指针只是说在哪里做工作,它不是完成工作的实体。

指针(大致)类似于您的电子邮件地址,它指向的内存类似于您的收件箱,如果这有助于形象化概念的话。