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

Chr*_*isB 8 vba

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

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

Dim myVar as string
myVar = InputBox("Enter something.")
MsgBox StrPtr(myVar)
Run Code Online (Sandbox Code Playgroud)

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

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

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

Com*_*ern 13

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
Run Code Online (Sandbox Code Playgroud)

那是因为InputBox返回了Variant一个子类型VT_BSTR.这段代码演示了(注意我已声明resultVariant不会被隐式强制转换 - 更多内容如下):

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
Run Code Online (Sandbox Code Playgroud)

原因为何StrPtr返回0是因为返回值InputBox实际上是畸形的(我认为这是在实现中的错误).甲BSTR是前缀的实际字符数组与字符串的长度的自动化类型.这避免了C样式的空终止字符串呈现自动化的一个问题 - 您必须将字符串的长度作为单独的参数传递,否则调用者将不知道接收它的缓冲区的大小有多大.有返回值的问题InputBox在于Variant,它的包裹在包含一个空指针在数据区.通常,这将包含字符串指针 - 调用者将取消引用数据区中的指针,获取大小,为其创建缓冲区,然后读取长度标头后面的N个字节.通过在数据区域中传递空指针,InputBox依赖于调用代码来检查数据类型(VT_BSTR)是否实际匹配数据区域(VT_EMPTYVT_NULL)中的数据.

将结果检查为a StrPtr实际上是依赖于函数的那个​​怪癖.当它在a上调用时Variant,它返回指向存储在数据区域中的基础字符串的指针,并通过长度前缀使其自身偏移,以使其与需要C字符串的库函数兼容.这意味着StrPtr 必须对数据区域执行空指针检查,因为它没有返回指向实际数据开头的指针.此外,与在数据区域中存储指针的任何其他VARTYPE一样,它必须取消引用两次.VarPtr实际上给你一个内存地址的原因是它给你指向你传递它的任何变量的原始指针(数组除外,但这里并不是真正的范围).

所以...它与使用没什么不同Len. Len只返回标题中的值BSTR(不,它根本不计算字符),并且它需要进行空测试,原因与之类似StrPtr.它得出逻辑结论,空指针的长度为零 - 这是因为vbNullstring 一个空指针:

Debug.Print StrPtr(vbNullString) '<-- 0
Run Code Online (Sandbox Code Playgroud)

也就是说,你依赖于有缺陷的行为InputBox.如果微软要修复实现(他们不会),它会破坏你的代码(这就是为什么他们不会).但总的来说,不要依赖这样的狡猾行为是一个更好的主意.除非您希望用户点击"取消"的方式不同于没有输入任何内容并且点击"回车" 的用户,否则使用StrPtr(result) = 0更有利于更清晰Len(result) = 0或更有利的用户没有太大意义result = vbNullString.我断言如果你需要做出这种区分,你应该将自己的东西放在一起UserForm,并在你自己的对话框中明确地处理取消和数据验证.

  • 我不明白为什么你会说这种方式使用它没有好处,当替代方法无法区分用户提交空白值和用户实际点击取消.这对我来说似乎是一个明显的好处,但我错过了什么? (4认同)
  • 所有更多的理由使用`Application.InputBox`并得到一个*正版*变体*总是*有一个`StrPtr` (3认同)

GSe*_*erg 9

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

一个好的答案将解释使用该StrPtr功能的好处(超出我上面的例子)和风险,可能是您如何使用(或不使用)它,而无需就是否每个人都应该使用它发表意见。

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

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

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

那么,你什么时候使用它们?当您需要执行它们的操作时,您
可以使用它们

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

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

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


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

从上面概述的内容来看,您似乎应该使用StrPtr来确定是否在InputBox. 但实际上,你别无选择。

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

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

考虑到这一点,您可以决定是否StrPtrInputBox结果感到满意。如果您愿意承担这种行为在未来发生变化的很小风险并且您的应用程序因此而中断,您确实使用StrPtr,否则您切换到Application.InputBox返回 aVariant记录False在取消时返回 a 。

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


极好的!但是为什么有些人坚持StrPtr从不使用呢?我读到它不受支持。为什么这很重要?

当有人坚持的东西应该永远不会被使用,它几乎总是错的。甚至GoTo 有其正确的用途