【问题标题】:How do I pass an array of arguments ByRef with CallByName?如何使用 CallByName 传递参数数组 ByRef?
【发布时间】:2016-05-25 08:59:32
【问题描述】:

我目前正在使用 CallByName 来动态调用方法。我每天从服务器中的表格中获取几种方法以及参数。出于这个原因,我将参数数组发送给 CallByName 而不是参数数组,因为直到运行时我才知道参数的数量。鉴于 CallByName 需要一个参数数组,我使用私有声明函数来绕过 VBA 类型定义。

Private Declare PtrSafe Function rtcCallByName Lib "VBE7.DLL" ( _
  ByVal Object As Object, _
  ByVal ProcName As LongPtr, _
  ByVal CallType As VbCallType, _
  ByRef Args() As Any, _
  Optional ByVal lcid As Long) As Variant

Public Function CallByNameMethod(Object As Object, ProcName As String, ByRef Args () As Variant)
  AssignResult CallByNameMethod, rtcCallByName(Object, StrPtr(ProcName), VbMethod, Args)
End Function 

 Private Sub AssignResult(target, Result)
  If VBA.IsObject(Result) Then Set target = Result Else target = Result
End Sub

当我传递一个方法更改其基础属性的对象时,这有效。但是,有一些方法我传递一个对象和一个改变传递参数值的方法。例如,我传递一个带有以下参数的数组

 Dim Name as String, Value1 as double, Value2 as double, Value3 as double
 Dim Array(3) as Variant

  String = "Name"
  Value1 = 0
  Value2 = 0
  Value3 = 0

  Array(0) = Name
  Array(1) = Value1
  Array(2) = Value2
  Array(3) = Value3

当我传递该数组时,该方法只返回具有相同值的数组,但我期望 Array(1)、Array(2)、Array(3) 的 double 类型值。有什么想法吗?

【问题讨论】:

    标签: vba dynamic marshalling pass-by-reference callbyname


    【解决方案1】:

    事实证明这实际上是可能的,请参阅这篇文章中的RunMacro 方法:

    https://codereview.stackexchange.com/q/273741/146810

    将参数数组复制到变体数组中以传递给rtcCallByName同时保留变体的ByRef 标志,使用此CloneParamArray 方法:

    https://github.com/cristianbuse/VBA-MemoryTools/blob/f01b0818930fb1708caaf5fc99812abdeaa9f1df/src/LibMemory.bas#L890

    【讨论】:

      【解决方案2】:

      答案的第一个线索在于 rtcCallByName 的函数声明(从 vbe7.dll 的导出表中提取):

      function CallByName(Object: IDispatch; ProcName: BSTR; CallType: VbCallType; Args: ^SafeArray; out lcid: I4): Variant; stdcall;
      

      注意Args 被声明为指向SafeArray 的指针,但 被声明为out 参数。这意味着该函数的 COM 合同基本上是说,如果您传递一个 ParamArray,它所做的唯一保证是它不会更改指向 ParamArray 本身的指针。 Declare Function 中的ByRef 仅表示您正在传递一个指针。

      至于ParamArray 内部的值,我确实没有多少Microsoft 文档可以挖掘特定于VBA,但VB.NET version of the documentation 提供了第二条线索:

      始终使用 ByVal (Visual Basic) 声明 ParamArray 参数。

      CallByName 的上下文中,这非常有意义。 rtcCallByName 函数本身不(也不可能)知道被调用方法的哪些参数是它们自己声明的ByRefByVal,所以它必须 假设它不能改变它们。

      就解决此限制的实现而言,我建议要么进行重构以消除在CallByName 调用的代码中传递ByRef 的返回值,要么将所需的功能包装在一个类中。

      【讨论】:

      • 我相信你的段落以“......它必须假设它不能改变它们”结尾。不正确。在 VBA ByRef 参数中直接使用 CallByName 时,按预期工作,因此 CallByName 实现中不能存在固有限制。 IMO 的问题在于将 Args/Array 传递给声明的函数。
      • 我同意 byRef 参数直接与 CallByName 一起使用,所以会有限制似乎很奇怪。有办法解决这个问题吗?我需要传递一个参数数组,因为我不知道在运行时之前会有多少个参数
      • @PhilS - 对 CallByName 的直接调用不会通过 COM 边界封送。
      • 不幸的是,这些方法是一个专有的外部库,因为它们允许我通过 COM 对象连接到外部软件,因此我无法重构或创建类。
      • @maracuja 将参数包装在 Array() 调用中,并让函数接受常规数组作为其参数,而不是 ParamArray
      猜你喜欢
      • 2022-01-04
      • 1970-01-01
      • 1970-01-01
      • 2020-09-04
      • 2019-07-28
      • 1970-01-01
      • 2013-12-11
      • 2012-12-30
      • 2017-06-24
      相关资源
      最近更新 更多