【问题标题】:Passing an rvalue as a ByRef parameter to VB6?将右值作为 ByRef 参数传递给 VB6?
【发布时间】:2024-01-16 16:45:01
【问题描述】:

背景:我有一组共享一个通用“接口”的 VB6 DLL。无论在本地安装哪个版本,都会通过 COM 互操作调用此接口的成员(来自 VB.Net 代码,我怀疑这可能很重要)。我今天注意到其中一个调用将 [我理解为] 一个右值(以下简称“右值”)传递给一个 VB6 函数,该函数没有将特定参数定义为 ByVal

示例代码:

VB6:

Public Function VB6Function(input As String) As String
    ' Do interesting things with input
End Function

VB.Net:

' get an instance of the VB6 class and pass our trimmed localString to it
result = vb6Instance.VB6Function(localString.Trim())
' Do interesting things with localString

我还没有注意到更改input 值的 VB6 代码实例,但我也没有对不同的 DLL 实现进行详尽的搜索(有数百个)。

如果VB6Function 确实在input 是“右值”时改变了input 的值会发生什么?就此而言,为什么在传递“右值”时该方法调用不会简单地出错?

【问题讨论】:

  • 将其视为 r 值是没有用的。 System.String 是不可变的,Trim() 的返回值不是额外的不可变的。 pinvoke marshaller 确保它保持这种状态,底层互操作类型是 BSTR。在调用之前用 SysAllocString() 创建,之后用 SysFreeString() 销毁。 VB6 运行时可以重新分配参数,但这只会导致其字符串被 SysFreeString 破坏。 .NET 字符串不会受到影响。
  • @HansPassant 谢谢。将其视为 r 值实际上似乎是导致我陷入兔子洞的误解。但那是因为我将“rvalue”与“immutable”混为一谈,所以我需要提出一个澄清问题,以确保我理解您关于“.NET 字符串不会受到影响”的评论。如果我调用VB6Function(localString) 并且该代码为input 分配了一个新值,那么localString 的值在VB6Function 返回时会有所不同吗?
  • @arootbeer 当然。这就是 byref 的主要目的。
  • @GSerg 我知道这是 ByRef 在 VB6 内在 VB.Net 内的主要目的。我的问题是试图澄清调用 VB6 COM 服务器的 VB.Net COM 客户端的行为是否不同。我不清楚“在调用之前使用SysAllocString() 创建并在之后使用SysFreeString() 销毁”是否意味着重新分配的值在函数调用返回后仍然可用,或者将不仍然可用。
  • 你试过了吗?

标签: com vb6 parameter-passing


【解决方案1】:

如果输入是“右值”时VB6Function确实改变了输入的值会发生什么?

什么都没有。或者说,没什么有趣的。

当被调用函数更改其参数的值时,无论参数是由val 还是byref 提供的,对于该函数的内部 都没有区别。重要的是存在某种类型的变量,因此可以对其进行操作。

对于这个问题,为什么在传递“一个右值”时这个方法调用不会简单地出错?

为什么会出错?传递的参数是正确的类型(字符串),这很重要。

VB 中没有右值的概念。
当您将您将调用的右值传递给通过引用接受某些内容的方法时,编译器会自动将引用传递给右值实际所在的temporary location。方法通过ref获取值,调用者不关心指针。

localString.Trim() 分配并返回一个字符串。它有一个地址,可以传递。 您的代码 没有明确地捕获该地址,但编译器将其传递给VB6Function byref 没有问题。如果VB6Function 更改了值,它会更改该临时位置指向的内容,这没有可观察到的差异,因为无论哪种方式,它都会在调用后被销毁。

至于为什么有些人可能更喜欢在VBA中通过ref接收字符串,具体是为了避免每次调用函数时都复制整个字符串。在 VB.NET 中这不是问题,因为字符串是不可变的,因此可以在不复制的情况下通过 byval 传递,但在 VBA 中并非如此,因此出于调用的目的需要克隆 byval 字符串。人们通过指定 byref 来避免这种情况,尽管从技术上讲这使他们有能力处理传递的变量。

【讨论】:

  • 此行为与your linked answer 中的行为之间的有趣类比:“因为您使用的是括号,所以值是通过值而不是通过引用强制传递的(编译器创建一个临时副本并通过 那个参考)。”我认识到在 VB6 和 VB.Net 之间特定的 调用约定(使用括号调用 subs)发生了变化,但类比是非常有启发性的,因为它展示了相同的 行为,即使调用机制(VB6->VB6 vs VB.Net->VB6)不同。
  • @arootbeer 在链接的答案中使用括号是 99% 的错误,对于那些编码风格不好的情况,还有 1% 的错误。如果需要临时副本,则使用 CLng(longVar)CStr(stringVar) 之类的东西来明确表达想法。
  • @wqw 如果我看到CLng(longVar),其中longVar 实际上是Long,那肯定不会让我明白这个想法。相反,我认为这是longVar 不是Long 时的剩余代码,我会删除CLng。另一方面,Parentheses 使意图明确且明确。
  • @GSerg 如果我在单个参数函数上看到额外的括号,我肯定不会开始高度评价这段代码的原作者,并且可能会尝试联系他们进行简短的交谈。不同的商店有不同的约定。有些使用stringVar & ""longVar + 0。为此,我们总是使用 cmets ——“传递一个副本以防止在以下编写不佳的程序中修改参数。自我注意:下次放一个ByVal 以保护无辜者”