【问题标题】:What are the benefits and risks of using the StrPtr function in VBA?在 VBA 中使用 StrPtr 函数有哪些好处和风险?
【发布时间】:2019-10-29 04:13:14
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

    标签: vba


    【解决方案1】:

    tldr; 像这样使用StrPtr 并没有真正的风险,但也没有真正的好处

    虽然看起来您从InputBox 调用中返回了一个空指针,但实际上并没有。比较StrPtrVarPtr 的结果:

    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 类似做。它得出的逻辑结论是空 指针 的长度为零 - 这是因为 vbNullstring 一个空指针:

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

    也就是说,您依赖于 InputBox 中的错误行为。如果微软要修复实现(他们不会),它会破坏你的代码(这是为什么他们不会)。但总的来说,最好不要依赖这种狡猾的行为。除非您希望以不同的方式对待用户点击“取消”而不是用户不输入任何内容并点击“Enter”,否则使用StrPtr(result) = 0 来支持很多更清晰的Len(result) = 0result = vbNullString。我断言,如果您需要进行区分,您应该将自己的 UserForm明确在您自己的对话框中处理取消和数据验证放在一起。

    【讨论】:

    • 更有理由使用Application.InputBox 并获得一个始终具有StrPtr正版变体
    • 我不得不读了两遍。虽然我没有正式的编程背景,但您的大部分回答都是有道理的。我依靠赞成票和缺乏任何反对意见并接受您的回答。
    • 我不明白为什么你会说以这种方式使用它没有好处,因为替代方法无法区分用户提交空白值和用户实际单击取消。这对我来说似乎是一个明显的好处,但我错过了什么吗?
    • 所以 TL;DR: 是“当您需要将空字符串视为有效输入时使用 StrPtr”。
    • 我对这个答案感到非常困惑,以至于想要否决它。未验证的InputBoxVBA.InputBox,它返回String,而不是Variant/String。它错误地错过了在线文档中的As String,但是当您在对象浏览器中查看它时,它正确地具有As String。检查 StrPtr 的返回值是检测 Cancel 的唯一方法。相反,Application.InputBox 返回一个 Variant,因此您应该检查 = False
    【解决方案2】:

    我发现接受的答案相当具有误导性,因此我不得不发布另一个答案。

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

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

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

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

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

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

    这些函数正确地完成了它们应该做的事情,你不应该害怕将它们用于预期目的。

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


    在一个完美的世界里,你会停留在那个结论上。不幸的是,这并不总是可能的,您提到的InputBox 情况就是其中一个例子。

    根据上面的概述,您似乎应该使用StrPtr 来确定是否在InputBox 中按下了取消。但实际上,您别无选择。

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

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

    考虑到这一点,您可以决定是否愿意在 InputBox 结果上使用 StrPtr。如果您乐于冒非常小的的风险,这种行为在未来发生变化并且您的应用程序因此而崩溃,您确实使用StrPtr,否则您切换到返回VariantApplication.InputBox记录在取消时返回False

    但该决定不会基于StrPtr 告诉您的内容是否正确。这是。将VBA.InputBoxString 结果传递给它总是安全的。


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

    当有人坚持应该永远使用某些东西时,它几乎总是错的。甚至GoTohas its correct uses

    【讨论】:

      【解决方案3】:

      我厌倦了使用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
      

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

      【讨论】:

        【解决方案4】:

        通读该线程并最终完成以下操作...这正是我想要的...如果用户删除了上一个默认条目...并单击确定..它会向前移动并删除后端数据(未显示)。如果用户单击取消,则它存在子而不做任何事情。这是最终目标,并且……这使它能够按预期工作……除非单击取消,否则继续前进。

        hth, ..鲍勃

        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
        

        【讨论】:

          【解决方案5】:

          在我看来:使用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 程序员。

          【讨论】:

          • 使用StrPtr的重点是当用户取消对话框时,它only返回0。如果用户没有输入任何内容并按回车,它将返回非零结果。 OP 的代码正在测试取消 - 而不是返回值是否为 vbNullString
          • 这一定是大多数人用评论回答问题的原因,这样他们就不会被否决。我的回答解释了可能出现的实际问题。 OP 询问为什么使用不受支持的功能很重要。我这样回答。为了鼓励人们使用不受支持的功能(如果是的话,我想我应该对此进行研究,而不仅仅是接受 OP 的话)会产生问题来解决问题。
          • 未记录并不意味着不受支持。这些函数甚至不再被记录在案 - they received first class support (and documentation) in VBA beginning with Office 2010 因为它们是必需与本机库一起使用的。
          猜你喜欢
          • 2014-05-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-27
          • 1970-01-01
          • 2019-06-17
          • 1970-01-01
          相关资源
          最近更新 更多