首先,ByVal .. As Any 的 _Out_ 参数不是一个好主意(我什至不确定这是否可能);如果你使用 ByVal 这样你希望它是 As Long (请参阅下面的“为什么”)。
因此,对于具有一个或多个 _Out_ 参数来表示缓冲区/变量/内存位置的 API,有两种方法(无论如何对于每个相关参数)来编写声明,具体取决于 什么 em> 你想通过:
-
ByRef lpBuffer As Any 或简单的 lpBuffer As Any:如果在调用 API 时,您打算将数据复制到的实际变量传递给 _Out_ 参数,则可以在声明中使用它。例如,您可以像这样使用 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)
请注意,无论传递的变量的实际大小如何,被调用者(此处为 ReadProcessMemory())都会用数据填充您作为 lpBuffer 提供的任何内容。这就是为什么缓冲区的大小必须通过nSize 提供的原因,因为否则被调用者无法知道所提供的缓冲区的大小。另请注意,我们正在传递(字节)数组的第一项,因为这是被调用者应该开始向其中写入数据的地方。
使用相同的声明,您甚至可以根据需要传递一个 long(例如,如果您要检索的是地址或某种 DWord 值),但是 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)))
看,这实际上又是一回事,唯一的区别是我们提供了指向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 个字节紧跟在内存中那个 Integer 变量的内容之后,它可以是任何东西。如果这恰好是被调用者将写入的内存位置,那么您可能会崩溃。
警告 #2:您不想使用 VarPtrArray()(无论如何都需要明确声明),因为返回的值将是 SAFEARRAY 结构的地址 数组(项数、项大小等),而不是指向数组数据的指针(与数组中的第一项地址相同)。
本质上,对于 Win32 API(即 stdcall),参数总是作为 32 位值传递,总是。这些 32 位值的含义将取决于特定 API 的预期,因此其声明必须反映这一点。所以:
- 无论何时声明参数
ByRef,将使用正在传递的任何变量的内存位置;
- 无论何时声明参数
ByVal .. As Long,将使用正在传递的任何变量的(32 位)值(该值不一定是内存位置,例如@987654357 的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() 的地址,而是VarPtr(bytBuffer(0)) 返回的值。但是如果参数声明为ByVal .. As Long,那么你别无选择,你只能传递一个指针(即内存位置的地址)。
注意:在整个讨论的架构中,这个答案假设是 IA32 或它的仿真