首先,ByVal .. As Any
争论_Out_
不是一个好主意(我什至不确定这是否可能);如果您使用ByVal
它,您希望它是As Long
(请参阅下面的“为什么”)。
因此,对于具有一个或多个用于表示缓冲区/变量/内存位置的参数的 API _Out_
,有两种方法(无论如何对于每个相关参数)来编写声明,具体取决于您要传递的内容:
ByRef lpBuffer As Any
,或者简单地说:如果在调用 API 时,您打算传递数据应该复制到的实际变量lpBuffer As Any
,则在参数声明中使用 this 。例如,您可以像这样使用 Byte 数组:_Out_
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
,因为否则被调用者无法知道所提供缓冲区的大小。另请注意,我们正在传递(byte) 数组的第一项,因为这是被调用者应该开始向其中写入数据的地方。
使用相同的声明,您甚至可以根据需要传递一个 long(例如,如果您要检索的是地址或某种 DWord 值),但nSize
必须是 4 个字节(最多)。
另请注意,最后一个参数 ,lpNumberOfBytesWritten
也是一个_Out_
参数并通过 ByRef 但您不需要向被调用者提供其大小;那是因为调用者和被调用者之间有一个协议,无论传递什么变量,总是会写入 4 个字节。
ByVal lpBuffer As Long
_Out_
:如果在调用 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 位值/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 的方式并且您无法更改它,因为 if 来自类型库),您始终可以通过在它之前添加一个指针而不是实际变量来传递ByVal
它打电话。回到ReadProcessMemory()
(when lpBuffer
is declared 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 或它的仿真