在VBA中使用StrPtr函数有哪些好处和风险?

12

在寻找一种测试用户取消InputBox的方法时,我偶然发现了StrPtr函数。我相信它会检查一个变量是否被赋值,并在它从未被赋值时返回零,在它被赋值时返回一些神秘的数字。

这似乎是一个有用的函数!我从下面这段代码开始:

Dim myVar as string
myVar = InputBox("Enter something.")
MsgBox StrPtr(myVar)

如果用户取消,消息框将显示零。

太棒了!但是为什么有些人坚持不使用StrPtr?我读到它不受支持。那为什么会影响呢?

一个好的答案将解释使用StrPtr函数的好处(除了我的上面的例子之外)和风险,可能会介绍如何在使用或不使用时使用它,而不发表对每个人或没有人都应该使用它的意见。

5个回答

18

简而言之; 使用StrPtr没有真正的风险,但也没有实际的好处。

虽然从InputBox调用中看起来您会得到一个空指针,但实际上不是这样的。将StrPtr的结果与VarPtr进行比较:

Sub Test()
    Dim result As String
    result = InputBox("Enter something.")        'Hit cancel
    Debug.Print StrPtr(result)                   '0
    Debug.Print VarPtr(result)                   'Not 0.
End Sub

这是因为InputBox返回的是一个带有VT_BSTR子类型的Variant。以下代码演示了这一点(请注意,我已将result声明为Variant,以防止它被隐式转换 - 关于此更多信息请参见下面):

Sub OtherTest()
    Dim result As Variant
    result = InputBox("Enter something.")   'Hit cancel
    Debug.Print StrPtr(result)              '0
    Debug.Print VarPtr(result)              'Not 0.
    Debug.Print VarType(result)             '8 (VT_BSTR)
    Debug.Print TypeName(result)            'String
End Sub
StrPtr返回0的原因是因为InputBox的返回值实际上存在格式错误(我认为这是实现中的一个错误)。BSTR是一种自动化类型,它在实际字符数组前面加上字符串长度。这避免了C风格的以空字符终止的字符串自动化中存在的一个问题 - 您要么必须将字符串长度作为单独参数传递,要么调用者不知道将要接收多大的缓冲区。 InputBox返回值的问题在于,它包装的Variant在数据区域中包含一个空指针。通常,这将包含字符串指针 - 调用者将解引用数据区域中的指针,获取大小,创建缓冲区,并读取跟随长度头的N个字节。通过在数据区域中传递空指针,InputBox依赖于调用代码来检查数据类型(VT_BSTR)实际上是否与数据区域(VT_EMPTYVT_NULL)相匹配。

将结果作为StrPtr检查实际上是依赖于该函数的怪癖。当它在Variant上被调用时,它返回指向数据区域中存储的底层字符串的指针,并通过长度前缀进行偏移,使其与需要C字符串的库函数兼容。这意味着StrPtr 必须在数据区域上执行空指针检查,因为它不返回指向实际数据开头的指针。此外,像任何其他在数据区域中存储指针的VARTYPE一样,它必须进行两次解引用。 VarPtr实际上会给您一个内存地址,因为它会给出您传递的任何变量的原始指针(数组除外,但这不在此范围之内)。

因此...它与使用Len没有什么不同。 Len只返回BSTR头中的值(根本不计算字符数),并且由于类似StrPtr的原因,它也需要进行空测试。 它得出的逻辑结论是空指针长度为零 - 这是因为vbNullstring 空指针:

Debug.Print StrPtr(vbNullString) '<-- 0

话虽如此,你现在依赖的是InputBox中存在问题的行为。如果微软修复了实现(他们不会这么做),那么你的代码就会出现问题(这就是他们不会这么做的原因)。但一般来说,不要依赖这种不可靠的行为是一个更好的主意。除非你想要区分用户单击“取消”和用户未输入任何内容并单击“Enter”,否则使用StrPtr(result) = 0没有太多意义,而用Len(result) = 0result = vbNullString则更加清晰明了。我认为如果你需要做出这种区分,应该自己制作一个UserForm并在自己的对话框中显式地处理取消和数据验证。


3
更好的理由是使用 Application.InputBox 并获取一个真正的 Variant,该 Variant 始终具有StrPtr。 - ThunderFrame
1
我不得不读两遍。虽然我没有正式的编程背景,但你的大部分回答都很有道理。我依靠点赞和缺乏任何反对意见来接受你的答案。 - ChrisB
6
我不理解为什么你会说这种做法没有好处,因为另一种方法无法区分用户是提交了空值还是实际点击了取消。对我来说,这似乎是一个明显的优点,但是我是否漏掉了什么? - nateAtwork
1
因此,TL;DR:是“当您需要将空字符串视为有效输入时,请使用StrPtr”。 - Mathieu Guindon
1
我对这个答案非常困惑,以至于想要将其点踩。未经资格认证的 InputBoxVBA.InputBox,它返回一个 String,而不是 Variant/String。在线文档中错误地省略了 As String,但在对象浏览器中查看时则正确包含了 As String。检查其返回值的 StrPtr 是检测取消操作的唯一方法。相反,Application.InputBox 返回一个 Variant,你应该检查是否等于 False - GSerg
1
VarPtr 的涉及是另一个令人困惑/无关的点,因为当然你声明的变量位于内存中的某个位置,所以它的 VarPtr 总是等于某个值,无论你是否将 InputBox 的结果存储在其中。而且,即使你这样做了,那个 VarPtr 也不会改变。 - GSerg

14

我认为已接受的答案有些误导,所以我被迫发表另一个答案。

一个好的答案将解释使用StrPtr函数的好处(超出我上面的示例)和风险,可能会解释如何使用(或不使用)它,而不发表任何人应该使用还是不使用的意见。

有三个“隐藏”的函数:VarPtrStrPtrObjPtr

  • VarPtr用于在需要获取变量地址(即指向变量的指针)时使用。
  • StrPtr用于在需要获取字符串文本数据地址时使用(即BSTR,指向字符串第一个Unicode字符的指针)。
  • ObjPtr用于在需要获取对象地址(即指向对象的指针)时使用。

它们是隐藏的,因为搞乱指针可能是不安全的。
但你不能完全没有它们。

那么,什么时候使用它们?
当你需要做它们所做的事情时,你就使用它们

当你的问题是“我需要知道该变量的地址”(例如,因为您想将该地址传递给CopyMemory),则使用VarPtr
当你的问题是“我需要知道我的BSTR字符串的第一个字符的地址”(例如,因为你想将它传递给仅接受宽字符串的API函数,但如果你只是声明参数As String,VB会为您将字符串转换为ANSI,所以你必须传递StrPtr)时,则使用StrPtr
当你的问题是“我需要知道该对象的地址”(例如,因为你想检查它的vtable或手动检查对象地址是否等于之前已知的某个值)时,则使用ObjPtr

这些函数正确执行它们的预期功能,你应该毫不犹豫地使用它们。

如果你的任务不同,那么你可能不应该使用它们,但不是因为它们会返回错误的值——它们不会。


在完美的世界中,您会停止于此结论。不幸的是,并非总是这样,而您提到的InputBox情况就是其中之一。
根据上述内容,似乎您不应使用StrPtr来确定是否在InputBox中按了取消键。但实际上,您别无选择。 VBA.InputBox返回一个String。(当前文档中错误地省略了这个事实,使其看起来像返回一个Variant。)将字符串传递给StrPtr是完全可以的。
然而,InputBox返回空指针以表示取消并未记录在文档中。这只是一种观察结果。尽管从现实角度来看,这种行为永远不会改变,但在Office的未来版本中,理论上可能会发生变化。但这种观察结果是您所拥有的全部内容;没有记录下取消的返回值。
考虑到这一点,您需要决定是否愿意在InputBox结果中使用StrPtr。如果您愿意承担未来可能更改导致应用程序崩溃的极小风险,则可以使用StrPtr;否则,请切换到Application.InputBox,它返回一个Variant并被记录为在取消时返回False
但是,您的决定不应该基于StrPtr所说的正确性。它是正确的。始终可以将VBA.InputBoxString结果安全地传递给它。
太棒了!但是为什么有些人坚持不使用StrPtr?我看到它不受支持。这有什么影响吗?
当有人坚持永远不应该使用某个东西时,几乎总是错误的。甚至GoTo也有其正确的用途

0

我尝试过使用StrPtr和不使用StrPtr。我用几个例子测试了我的Sub程序。除了一种情况外,我得到了相同的结果-当用户输入空值(nothing)并按下OK时。

使用StrPtr。这里的结果是“无效数字”

    ElseIf StrPtr(Max_hours_string) = 0 
            MsgBox "Cancelled"
    Else
            MsgBox "Invalid Number"

不使用 StrPtr。这里的结果是“已取消”。
 ElseIf Max_hours_string = "" Then
        MsgBox "Cancelled"
 Else
        MsgBox "Invalid Number"

这是我的代码。

Sub Input_Max_Hours_From_The_User()
        'Two Common Error Cases are Covered:
        '1. When using InputBox, you of course have no control over whether the user enters valid input.
        '        You should store user input into a string, and then make sure you have the right value.
        '2. If the user clicks Cancel in the inputbox, the empty string is returned.
                 'Since the empty string can't be implicitly coerced to a double, an error is generated.
                 'It is the same thing that would happen if the user entered "Haughey" in the InputBox.

     Dim Max_hours_string As String, Max_hours_double As Double
        Max_hours_string = InputBox("Enter Maximum hours of any Shift")
        If IsNumeric(Max_hours_string) Then
            Max_hours_double = CDbl(Max_hours_string) 'CDbl converts an expression to double
            Range("L6").Value = Max_hours_double
            Range("L6").Interior.ColorIndex = 37
        ElseIf StrPtr(Max_hours_string) = 0 Then 'ElseIf Max_hours_string = "" Then MsgBox "Cancelled"  also works !
            MsgBox "Cancelled"
        Else
            MsgBox "Invalid Number"
        End If

    End Sub

所以我认为这取决于你处理空值的重要性。所有其他测试用例,包括按取消、非数字输入等都会得到相同的结果。希望这可以帮到你。


-2

阅读了这个线程,最终采取了以下措施...它完全符合我的要求...如果用户删除了默认的上一个条目,并点击确定...它会向前移动并删除后端数据(未显示)。如果用户点击取消,则退出子程序而不执行任何操作。这是最终目标...这使其按预期工作...除非点击取消,否则继续前进。

hth, ..bob

Dim str As String

If IsNull(Me.Note) = False Then
   str = Me.Note
Else
    str = "Enter Note Here"
End If

Dim cd As Integer
cd = Me.ContractDetailsID

str = InputBox("Please Enter Note", "NOTE", str)


If StrPtr(str) = 0 Then
    Exit Sub    'user hit cancel
End If

-4
在我看来:使用StrPtr来判断一个值是否转换为0是多余的代码。如果你像上面的例子一样使用以下函数。
Sub woohoo()
    Dim myVar As String
    myVar = "hello"
    myVar = InputBox("Enter something.")
   'if Cancel is hit myVar will = "" instead of hello.
    'MsgBox StrPtr(myVar) not needed
    MsgBox myVar 'will show ""
End Sub

现在这是不使用StrPtr的唯一原因吗?当然不是。使用不受支持的函数会遇到的另一个问题是,它们最终可能会破坏应用程序。无论是库问题还是其他程序员查看您的代码并尝试找到该函数,都不是一个好主意。如果您的脚本只有100行,这可能看起来不像什么大问题。但是如果它有数千行呢?如果您必须在两年后查看此代码,因为某些东西出了问题,那么找到这个神奇的不再起作用的函数并尝试弄清楚它的作用将不会很有趣。最后,特别是在VBA中,您可能会遇到溢出错误。如果使用StrPtr并且它超过了您声明的数据类型的分配空间,则会产生另一个不必要的错误。

这只是我的个人意见,但由于可以使用更少的代码并且该函数更加稳定,我不会使用它。

10年以上的Excel程序员。


7
使用StrPtr的目的在于它仅在用户取消对话框时返回0。如果用户未输入任何内容并按下回车键,则返回非零结果。该问题的提问者代码是用于测试取消操作,而不是返回值是否为“vbNullString”。 - Comintern
1
这可能就是为什么大多数人用评论回答问题,以免被踩。我的回答解释了实际可能出现的问题。OP问为什么使用不支持的函数很重要,我回答了这个问题。鼓励人们使用不支持的函数(如果是的话,我想我应该进行研究,而不仅仅是听取OP的话)会在后期创建需要修复的问题。 - excelledsoftware
2
未记录并不意味着不受支持。这些函数甚至在VBA中已经获得了一流的支持(和文档),从Office 2010开始,因为它们与本地库的使用是必需的。 - Comintern

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接