Sta*_*avm 6 vb6 vba windows-api-code-pack
我在VBA/VB6中使用了windows函数ReadProcessMemory,我不明白为什么当我更改ByVal的传递机制时lpBuffer
,该函数仍然会修改通过此参数传递的原始对象的值.在文档中,此参数被指定为应通过引用传递的输出.不应该通过值更改传递机制来防止原始实例被修改?为什么不呢?
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
Run Code Online (Sandbox Code Playgroud)
首先,ByVal .. As Any
争论_Out_
不是一个好主意(我什至不确定这是否可能);如果您使用ByVal
这样的东西,您希望它是这样的As Long
(请参阅下面的“为什么”)。
因此,对于具有一个或多个_Out_
参数来表示缓冲区/变量/内存位置的 API,有两种方法(无论如何对于每个相关参数)来编写声明,具体取决于您想要传递的内容:
ByRef lpBuffer As Any
,或者简单地lpBuffer As Any
说:_Out_
如果在调用 API 时您打算传递数据应复制到的实际变量,则可以在参数声明中使用它。例如,您可以像这样使用 Byte 数组: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)
Run Code Online (Sandbox Code Playgroud)
请注意,被调用者(此处为ReadProcessMemory()
)将用数据填充您提供的任何内容lpBuffer
,无论传递的变量的实际大小如何。这就是为什么必须通过 提供缓冲区的大小nSize
,因为否则被调用者无法知道所提供的缓冲区的大小。另请注意,我们正在传递(字节)数组的第一项,因为这是被调用者应开始写入数据的位置。
使用相同的声明,您甚至可以根据需要传递 long(例如,如果您要检索的是地址或某种类型的双字值),但必须nSize
是4 个字节(最多)。
另请注意,最后一个参数lpNumberOfBytesWritten
也是一个_Out_
参数并通过 ByRef 传递,但您不需要向被调用者提供其大小;这是因为调用者和被调用者之间有一个协议,即无论传递什么变量,都将始终写入 4 个字节。
ByVal lpBuffer As Long
:如果在调用 API 时,您打算以 32 位值(即指针)的形式_Out_
传递内存位置,则可以在参数声明中使用它;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)))
Run Code Online (Sandbox Code Playgroud)
看,这实际上又是同样的事情,唯一的区别是我们提供一个指针(内存地址)而bytBuffer
不是bytBuffer
直接传递。我们甚至可以提供返回的值VarPtr()
而不是使用 a Long
(此处为lPointer
):
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, VarPtr(bytBuffer(0)), 256, _
lWrittenBytes)
Run Code Online (Sandbox Code Playgroud)
警告#1:对于_Out_
参数,如果你声明它们,ByVal
它们应该总是 As Long
. 这是因为调用约定期望该值恰好由 4 个字节组成(32 位值/DWORD)。例如,如果您要通过类型传递值Integer
,您会得到意外的行为,因为将用作内存位置值的是该内存位置的 2 个字节Integer
加上紧随其内容之后的接下来的 2 个字节。内存中的变量Integer
,可以是任何东西。如果这恰好是被调用者将写入的内存位置,您可能会崩溃。
警告#2:您不想使用VarPtrArray()
(无论如何都需要显式声明),因为返回的值将是数组的 SAFEARRAY 结构的地址(项目数、项目大小等),而不是指向数组数据的指针(与数组中第一项的地址相同)。
本质上,对于 Win32 API(即 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)
Run Code Online (Sandbox Code Playgroud)
添加ByVal
告诉编译器应该在堆栈上传递的不是地址VarPtr()
,而是 的返回值VarPtr(bytBuffer(0))
。但是,如果声明了参数ByVal .. As Long
,那么您别无选择,只能传递指针(即内存位置的地址)。
注意:这个答案在整个讨论的架构中假设是 IA32 或其仿真