ByRef与ByVal的ReadProcessMemory函数

时间:2015-05-22 18:10:57

标签: vba vb6 windows-api-code-pack

我在VBA / VB6中使用了windows函数ReadProcessMemory,我不明白为什么当我将lpBuffer的传递机制更改为 ByVal 时,函数仍然存在修改通过此参数传递的原始对象的值。在文档中,此参数被指定为应通过引用传递的输出。不应该通过值更改传递机制来防止原始实例被修改?为什么不呢?

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

MSDN ReadProcessMemory

4 个答案:

答案 0 :(得分:4)

首先,ByVal .. As Any参数的_Out_不是一个好主意(我甚至不确定这是否可行);如果您使用ByVal,则希望它为As Long(请参阅下面的“为什么”)。

因此,对于具有一个或多个_Out_参数的API来表示缓冲区/变量/内存位置,有两种方法(对于每个相关参数无论如何)编写声明,具体取决于是什么你要通过:

  1. ByRef lpBuffer As Any,或简称为lpBuffer As Any:如果在调用API时,您打算传递实际变量_Out_参数strong>应将数据复制到哪里。例如,您可以像这样使用字节数组:
  2. 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个字节。

    1. ByVal lpBuffer As Long:如果在调用API时打算以32的形式传递内存位置,则在_Out_参数的声明中使用此参数位值(即指针);传递的Long的值将更改,将被覆盖的内容是内存位置被引用的值Long。重复使用相同的示例,但声明略有不同,我们得到:
    2. 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位)值(该值不一定是内存位置,例如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)
      

      添加ByVal告诉编译器应该在堆栈上传递的内容不是VarPtr()的地址,而是VarPtr(bytBuffer(0))返回的值。但是如果参数被声明为ByVal .. As Long,那么你没有选择,你只能传递指针(即内存位置的地址)。

      NOTA:在所讨论的架构中假设的答案是IA32或它的模拟

答案 1 :(得分:1)

@ polisha989我相信" lp" in lpBuffer表示类型为长指针。我怀疑,因为您传递的对象是指针,如果它通过值或引用传递,它将不会有任何区别。即使您按值传递参数,系统也只是复制指针 - 因此两个对象都将指向内存中的相同值。因此,无论是通过ref还是通过val传递指针,您都会看到更新的值,因为这就是指针的作用;它指向到内存中的值。无论你有多少指针,如果它们都指向内存中的同一个位置,它们都将显示相同的内容。

如果您正在进行API调用,请提出一条建议,那就是您真的无法花费很多时间来浏览MSDN。您可以越好地理解 函数如何工作,实现它就越容易。确保将正确的对象类型传递给函数将帮助您确保获得预期的结果。

答案 2 :(得分:1)

CBRF23是正确的。当API函数具有字符串参数时,传递的值是指向缓冲区的长指针。该指针值是一个长整数,并且对于指针的生命周期,它的值是不可变的。因此,您是否有两个指针值副本是无关紧要的,因为该值永远不会改变。

无论是否传递byref或byval,值都会更改,因为更改的内容是lpbuffer指向的缓冲区中的内存。指针只是说在哪里做工作,它不是工作完成的实体。

指针(大致)类似于您的电子邮件地址,它指向的内存类似于您的收件箱,如果这有助于可视化概念。

答案 3 :(得分:0)

{ ifstream inFile("Backstreet Boys - Incomplete.mp3",ios::binary); ofstream outFile("Output.mp3",ios::binary); // don't bother calling .close(), it happens automatically. } 声明永远不会被值传递。

  

删除类型限制时,Visual Basic假定参数通过引用传递。 在实际调用过程中包含ByVal以按值传递参数。

请注意我为“never”添加例外的斜体。