First, ByVal .. As Any
for an _Out_
argument is not a good idea (I'm not even sure if that's possible); if you use ByVal
for such you want it to be As Long
(see further below for the "why").
So, for APIs having one or more _Out_
arguments meant to represent a buffer/variable/memory location, there are two ways (for each concerned argument anyway) to write the declaration, depending on what you want to pass:
ByRef lpBuffer As Any
, or simply lpBuffer As Any
: You use this in the declaration for an _Out_
argument if, when calling the API, you intend to pass the actual variable where data should be copied to. For example, you could use a Byte array like so:
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)
Note that the callee (here, ReadProcessMemory()
) will fill whatever you provide as lpBuffer
with data, regardless of the actual size of the variable passed. That's why the size of the buffer must be provided through nSize
, because otherwise the callee has no way to know the size of the buffer being provided. Also note that we're passing the first item of the (byte) array, as this is where the callee should start writing data to.
With the same declaration, you could even pass a long if you wanted to (if, for example, what you want to retrieve is an address or a DWord value of some sort), but then nSize
must be 4 bytes (at most).
Also note that the last argument, lpNumberOfBytesWritten
, is also an _Out_
argument and passed ByRef but you don't need to provide the callee with its size; that's because there's an agreement between the caller & callee that whatever variable is passed, exactly 4 bytes will always be written to it.
ByVal lpBuffer As Long
: You use this in a declaration for an _Out_
argument if, when calling the API, you intend to pass a memory location in the form of a 32-bit value (i.e. a pointer); the value of the Long
being passed will not change, what will be overwritten is the memory location being referenced by the value of that Long
. Reusing the same example, but with a slightly different declaration, we get:
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)))
See, this is practically the same thing again, the only difference being we're providing a pointer (memory address) to bytBuffer
instead of passing bytBuffer
directly. We could even provide the value returned by VarPtr()
directly instead of using a Long
(here, lPointer
):
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, VarPtr(bytBuffer(0)), 256, _
lWrittenBytes)
Warning #1: For _Out_
arguments, if you declare them ByVal
they should always be As Long
. This is because the calling convention expects the value to be composed of exactly 4 bytes (32-bit value/DWORD). If you were to pass the value through an Integer
type, for example, you'd get unexpected behaviour because what will be used as the value for the memory location are the 2 bytes of that Integer
plus the next 2 bytes that come right after the content of that Integer
variable in memory, which could be anything. And if this happens to be a memory location the callee will write to, you'll probably crash.
Warning #2: You DO NOT want to use VarPtrArray()
(which would need to be explicitly declared anyway), as the value returned will be the address of the SAFEARRAY structure of the array (number of items, size of items, etc.), not the pointer to the array's data (which is the same address as the first item in the array).
In essence, for Win32 APIs (i.e. stdcall) arguments are always passed as 32-bit values, always. The meaning of those 32-bit values will depend on what the specific API expects, so its declaration must reflect this. So:
- whenever an argument is declared
ByRef
, what will be used is the memory location of whatever variable is being passed;
- whenever an argument is declared
ByVal .. As Long
, what will be used is the (32-bit) value of whatever variable is being passed (the value must not necessarily be a memory location, e.g. the hProcess
argument of ReadProcessMemory()
).
Finally, even if you declare an _Out_
argument ByRef
(or if, for example, that's the way an API is declared and you cannot change it because if comes from a typelib) you can always pass a pointer instead of the actual variable by adding ByVal
before it when making the call. Going back to the first declaration of ReadProcessMemory()
(when lpBuffer
is declared ByRef
), we would do the following:
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)
Adding ByVal
tells the compiler that what should be passed on stack is not the address of VarPtr()
but instead the value returned by VarPtr(bytBuffer(0))
. But if the argument was declared ByVal .. As Long
then you don't have a choice, you can only pass a pointer (i.e. address of a memory location).
NOTA: this answer assumed throughout the architecture being discussed was IA32 or an emulation of it
lpBuffer
in your snippet is being passedByRef
. VBA/VB6 passes parameters by reference when unspecified, so how come using this code to declare it works is because it's passedByRef
, only implicitly. You did mean to ask why it works even if you explicitly specify it'sByVal
, right? – Seato_Out_
parameters by reference. – Seato