在VBA中使用StrPtr函数有什么好处和风险?

时间:2017-02-03 02:20:09

标签: vba

在寻找一种方法来测试用户何时取消InputBox时,我偶然发现了StrPtr功能。我相信它会检查一个变量是否被分配了一个值,如果它从未被分配则返回零,如果是,则返回一些神秘数字。

这似乎是一个有用的功能!我从这段代码开始:

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

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

优秀!但是为什么有些人坚持认为永远不会使用StrPtr?我读它不受支持。为什么这很重要?

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

5 个答案:

答案 0 :(得分:12)

tldr; 这样使用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正在返回Variant,其子类型为VT_BSTR。此代码演示(请注意,我已将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相似的原因。它得出逻辑结论,即null 指针的长度为零 - 这是因为vbNullstring 一个空指针:

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

那就是说,你依靠InputBox中的错误行为。如果微软要修复实施(他们不会),那么它会破坏你的代码(为什么他们赢了#)。但总的来说,不要依赖这样的狡猾行为是一个更好的主意。除非您正在寻求对待用户点击&#34;取消&#34;不同于用户没有输入任何内容并点击&#34;输入&#34;,使用StrPtr(result) = 0支持更清晰的{{1}并不是很重要}或Len(result) = 0。我断言,如果你需要做出这样的区分,你应该在你自己的对话框中将自己的result = vbNullString显式处理取消和数据验证放在一起。

答案 1 :(得分:0)

我既使用StrPtr又不使用StrPtr感到很累。我用几个例子测试了我的Sub。除了在一种情况下,我得到了相同的结果-当用户输入空值(无)并按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

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

答案 2 :(得分:0)

我发现被接受的答案颇具误导性,因此我不得不发表另一个答案。

  

一个好的答案将说明使用StrPtr函数的好处(除了上面的示例之外)和风险,可能还会说明您如何使用(或不使用)该函数,而无需给出是否所有人或所有人的看法。应该使用它。

共有三个“隐藏”功能:VarPtrStrPtrObjPtr

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

它们被隐藏了,因为弄乱指针可能是不安全的。
但是没有它们,您将无法完全放弃。

那么,什么时候使用它们?
在需要执行操作时使用它们

当您遇到的问题是“我需要知道该变量的地址”时(例如,因为您要将该地址传递给VarPtr),请使用CopyMemory
当您遇到的问题是“我需要知道我的BSTR字符串的第一个字符的地址”时,可以使用StrPtr(例如,因为您要将其传递给仅接受宽字符串的API函数,但是如果只需声明参数As String,VB就会为您将字符串转换为ANSI,因此您have to pass StrPtr)。
当您遇到的问题是“我需要知道该对象的地址”时,可以使用ObjPtr(例如,因为您要检查其vtable或手动检查对象地址是否等于或不等于您先前知道的某个值) )。

这些功能可以正确地执行其应做的事情,您不应该害怕将其用于预期的目的。

如果您要处理的任务不同,则可能不应该使用它们,但不要因为担心它们会返回错误的值而不会担心。


在一个完美的世界中,您会停止得出这个结论。不幸的是,这并非总是可能的,您提到的InputBox情况就是其中一个例子。

从上面概述的内容看来,您应该不要使用StrPtr来确定是否在InputBox中按下了Cancel。但实际上,您别无选择。

VBA.InputBox返回一个String。 (当前文档中错误地忽略了这一事实,使其看起来像返回了Variant。)将字符串传递给StrPtr是完全可以的。

但是,没有记录InputBox在取消时返回空指针。这仅仅是一个观察。尽管实际上该行为永远不会改变,但从理论上讲,它可能会在Office的未来版本中改变。但是这种观察就是你所拥有的。没有取消的记录返回值。

考虑到这一点,您可以决定是否适合在StrPtr结果中使用InputBox。如果您乐意冒此行为将来发生的很小风险,并且您的应用因此被破坏,请使用StrPtr,否则请切换到Application.InputBox并返回一个Variant,并记录以在取消时返回False

但是该决定将不会基于StrPtr告诉您的内容是否正确。它是。将String的{​​{1}}结果传递给它总是安全的。


  

太棒了!但是,为什么有人坚持不使用VBA.InputBox?我读到它不受支持。为什么这么重要?

当有人坚持不应该使用某些东西时,这几乎总是错误的。甚至StrPtr has its correct uses

答案 3 :(得分:-1)

通读此线程,最终完成以下操作……这正是我想要的。...如果用户删除了默认的上一个条目,然后单击“确定”。删除后端数据(未显示)。如果用户单击了“取消”,则该子目录将不执行任何操作。这是最终目标,...使其能够按预期工作...除非单击取消,否则请继续前进。

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 :(得分:-3)

在我看来:使用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行,那么这似乎不是什么大问题。但是当它长达数千行时呢。如果你不得不在未来2年看一下这段代码,因为有些东西破坏了,找到这个不再有用的神奇功能并尝试找出它做了什么并不是很有趣。最后,尤其是在VBA中,您可能会遇到溢出错误。如果使用了StrPtr并且它超过了您声明的数据类型的已分配空间,则会发生另一个不必要的错误。

仅仅是我的2美分,但是由于能够使用更少的代码并且功能更稳定而没有它我不会使用它。

10年以上的Excel程序员。