首先,对于一个
_Out_
参数,
ByVal .. As Any
不是个好主意(我甚至不确定这是否可能)。如果你为这样的参数使用了
ByVal
,则应将其作为
As Long
(有关 "why" 的更多信息请参见下文)。
因此,对于具有一个或多个旨在表示缓冲区/变量/内存位置的
_Out_
参数的 API,有两种方法(对于每个相关的参数),可以根据要传递的内容来编写声明:
1.
ByRef lpBuffer As Any
,或者简单地
lpBuffer As Any
:如果在调用 API 时您打算传递数据应被复制到的实际变量,则在
_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
提供缓冲区的大小,因为否则被调用者无法知道所提供的缓冲区大小。还要注意我们传递了(字节)数组的
第一项,因为这是被调用者应该开始写入数据的位置。
使用相同的声明,即使您想要传递一个 long(例如,如果您要检索的是某种地址或 DWord 值),也可以这样做,但是
nSize
的大小
必须最多为 4 字节。
还要注意,最后一个参数
lpNumberOfBytesWritten
也是一个
_Out_
参数,并通过按引用传递,但您不需要为其提供大小;这是因为调用方和被调用方之间存在协议,无论传递哪个变量,始终会向其中写入确切的 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)))
看,这实际上又是同样的内容,唯一的区别是我们提供了一个指针(内存地址)给bytBuffer
,而不是直接传递bytBuffer
。我们甚至可以直接提供VarPtr()
返回的值,而不是使用一个Long
类型的变量(这里是lPointer
):
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, VarPtr(bytBuffer(0)), 256, _
lWrittenBytes)
警告 #1:对于 _Out_
参数,如果你将它们声明为 ByVal
,则它们应该始终为 As Long
。这是因为调用约定期望值由恰好 4 个字节(32 位值 / DWORD)组成。例如,如果你通过 Integer
类型传递值,你将得到意外的行为,因为内存位置的值将是该 Integer
的 2 个字节以及紧随其后的下一个 2 个字节,这可能是任何内容。如果这发生在被调用者将要写入的内存位置上,那么你可能会崩溃。
警告 #2:你不想使用 VarPtrArray()
(需要明确声明),因为返回的值将是数组的 SAFEARRAY 结构的地址(项目数量、项目大小等),而不是数组数据的指针(与数组中第一个项目的地址相同)。
实际上,对于 Win32 API(即 stdcall),参数总是作为 32 位值传递。这些 32 位值的含义取决于特定 API 的期望,因此其声明必须反映这一点。所以:
- 每当参数声明为
ByRef
时,将使用传递的任何变量的内存位置;
- 每当参数声明为
ByVal .. As Long
时,将使用传递的任何变量的(32 位)值(该值不一定是内存位置,例如 ReadProcessMemory()
的 hProcess
参数)。
最后,即使你将 _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(bytBuffer(0))
返回的值,而不是VarPtr()
的地址。但如果参数声明为ByVal .. As Long
,那么你只能传递指针(即内存位置的地址)。
注意:本答案假定讨论的架构始终为IA32或其仿真
_Out_
参数。 - Mathieu Guindon