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

时间:2022-09-06 12:34:56

While looking for a way to test when a user cancels an InputBox, I stumbled across the StrPtr function. I believe it checks if a variable was ever assigned a value and returns zero if it was never assigned and some cryptic number if it was.

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

It seems like a useful function! I start with this code:

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

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

The message box shows a zero if the user cancelled.

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

Fantastic! But then why do some insist that StrPtr never be used? I read it's unsupported. Why does that matter?

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

A good answer will explain benefits (beyond my example above) and risks of using the StrPtr function, possibly how you use (or don't use) it without giving an opinion as to whether everyone or no one should use it.

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

2 个解决方案

#1


10  

tldr; There's no real risk to using StrPtr like that, but there's not really a benefit either.

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

While it might look like you get a null pointer back from the InputBox call, you actually don't. Compare the result of StrPtr to VarPtr:

虽然看起来你可能会从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

That's because InputBox is returning a Variant with a sub-type of VT_BSTR. This code demonstrates (note that I've declared result as a Variant so it doesn't get implicitly cast - more on this below):

那是因为InputBox返回一个Variant,子类型为VT_BSTR。这段代码演示了(注意我已经将结果声明为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

The reason why StrPtr returns 0 is because the return value of InputBox is actually malformed (I consider this a bug in the implementation). A BSTR is an automation type that prefixes the actual character array with the length of the string. This avoids one problem that a C-style null terminated string presents automation - you either have to pass the length of the string as a separate parameter or the caller won't know how large to size a buffer to receive it. The problem with the return value of InputBox is that the Variant that it's wrapped in contains a null pointer in the data area. Normally, this would contain the string pointer - the caller would dereference the pointer in the data area, get the size, create a buffer for it, and then read the N bytes following the length header. By passing a null pointer in the data area, InputBox relies on the calling code to check that the data type (VT_BSTR) actually matches what is in the data area (VT_EMPTY or VT_NULL).

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

Checking the result as a StrPtr is actually relying on that quirk of the function. When it's called on a Variant, it returns the pointer to the underlying string stored in the data area, and it offsets itself by the length prefix to make it compatible with library functions that require a C-string. That means the StrPtr has to perform a null pointer check on the data area, because it's not returning a pointer to the start of the actual data. Also, like any other VARTYPE that stores a pointer in the data area, it has to dereference twice. The reason VarPtr actually gives you a memory address is that it gives you the raw pointer to whatever variable you pass it (with the exception of arrays, but that's not really in scope here).

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

So... it's really no different than using Len. Len just returns the value in the header of the BSTR (no, it doesn't count characters at all), and it also needs a null test for the similar reason that StrPtr does. It makes the logical conclusion that a null pointer has zero length - this is because vbNullstring is a null pointer:

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

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

That said, you're relying on buggy behavior in InputBox. If Microsoft were to fix the implementation (they won't), it would break your code (which is why they won't). But in general, it's a better idea to not rely on dodgy behavior like that. Unless you're looking to treat the user hitting "Cancel" differently than the user not typing anything and hitting "Enter", there really isn't much point in using StrPtr(result) = 0 in favor of the much clearer Len(result) = 0 or result = vbNullString. I'd assert that if you need to make that distinction, you should throw together your own UserForm and explicitly handle cancellation and data validation in your own dialog.

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

#2


-3  

In my opinion: Using StrPtr in order to identify if a value converts to 0 is extra code to write. if you use the following function like your example above

在我看来:使用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

Now is this the only reason to not use StrPtr no not at all. The other issue you run into with using unsupported functions is that eventually they can break the application. Whether its a library issue or another programmer looking through your code and trying to find that function it just is not a good idea. This may not seem like a big deal if your script is only 100 lines long. But what about when it is thousands of lines long. If you have to look at this code 2 years down the road because something broke it would not be very fun to have to find this magical function that just does not work anymore and try to figure out what it did. Lastly especially in VBA you can get overflow errors. If StrPtr is used and it goes past the allocated space of your data type that you declared it's another unnecessary error.

现在这是不使用StrPtr的唯一原因。使用不受支持的函数遇到的另一个问题是最终它们可能会破坏应用程序。无论是图书馆问题还是其他程序员正在查看您的代码并试图找到该功能,这都不是一个好主意。如果您的脚本只有100行,那么这似乎不是什么大问题。但是当它长达数千行时呢。如果你不得不在未来两年看这段代码,因为有些事情发生了变化,找到这个不再适用的神奇功能并尝试弄清楚它做了什么并不是一件非常有趣的事情。最后,尤其是在VBA中,您可能会遇到溢出错误。如果使用了StrPtr并且它超过了您声明的数据类型的已分配空间,那么这是另一个不必要的错误。

Just my 2 cents but due to being able to use less code and the function being more stable without it I would not use it.

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

10+ years Excel Programmer.

10年以上的Excel程序员。

#1


10  

tldr; There's no real risk to using StrPtr like that, but there's not really a benefit either.

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

While it might look like you get a null pointer back from the InputBox call, you actually don't. Compare the result of StrPtr to VarPtr:

虽然看起来你可能会从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

That's because InputBox is returning a Variant with a sub-type of VT_BSTR. This code demonstrates (note that I've declared result as a Variant so it doesn't get implicitly cast - more on this below):

那是因为InputBox返回一个Variant,子类型为VT_BSTR。这段代码演示了(注意我已经将结果声明为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

The reason why StrPtr returns 0 is because the return value of InputBox is actually malformed (I consider this a bug in the implementation). A BSTR is an automation type that prefixes the actual character array with the length of the string. This avoids one problem that a C-style null terminated string presents automation - you either have to pass the length of the string as a separate parameter or the caller won't know how large to size a buffer to receive it. The problem with the return value of InputBox is that the Variant that it's wrapped in contains a null pointer in the data area. Normally, this would contain the string pointer - the caller would dereference the pointer in the data area, get the size, create a buffer for it, and then read the N bytes following the length header. By passing a null pointer in the data area, InputBox relies on the calling code to check that the data type (VT_BSTR) actually matches what is in the data area (VT_EMPTY or VT_NULL).

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

Checking the result as a StrPtr is actually relying on that quirk of the function. When it's called on a Variant, it returns the pointer to the underlying string stored in the data area, and it offsets itself by the length prefix to make it compatible with library functions that require a C-string. That means the StrPtr has to perform a null pointer check on the data area, because it's not returning a pointer to the start of the actual data. Also, like any other VARTYPE that stores a pointer in the data area, it has to dereference twice. The reason VarPtr actually gives you a memory address is that it gives you the raw pointer to whatever variable you pass it (with the exception of arrays, but that's not really in scope here).

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

So... it's really no different than using Len. Len just returns the value in the header of the BSTR (no, it doesn't count characters at all), and it also needs a null test for the similar reason that StrPtr does. It makes the logical conclusion that a null pointer has zero length - this is because vbNullstring is a null pointer:

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

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

That said, you're relying on buggy behavior in InputBox. If Microsoft were to fix the implementation (they won't), it would break your code (which is why they won't). But in general, it's a better idea to not rely on dodgy behavior like that. Unless you're looking to treat the user hitting "Cancel" differently than the user not typing anything and hitting "Enter", there really isn't much point in using StrPtr(result) = 0 in favor of the much clearer Len(result) = 0 or result = vbNullString. I'd assert that if you need to make that distinction, you should throw together your own UserForm and explicitly handle cancellation and data validation in your own dialog.

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

#2


-3  

In my opinion: Using StrPtr in order to identify if a value converts to 0 is extra code to write. if you use the following function like your example above

在我看来:使用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

Now is this the only reason to not use StrPtr no not at all. The other issue you run into with using unsupported functions is that eventually they can break the application. Whether its a library issue or another programmer looking through your code and trying to find that function it just is not a good idea. This may not seem like a big deal if your script is only 100 lines long. But what about when it is thousands of lines long. If you have to look at this code 2 years down the road because something broke it would not be very fun to have to find this magical function that just does not work anymore and try to figure out what it did. Lastly especially in VBA you can get overflow errors. If StrPtr is used and it goes past the allocated space of your data type that you declared it's another unnecessary error.

现在这是不使用StrPtr的唯一原因。使用不受支持的函数遇到的另一个问题是最终它们可能会破坏应用程序。无论是图书馆问题还是其他程序员正在查看您的代码并试图找到该功能,这都不是一个好主意。如果您的脚本只有100行,那么这似乎不是什么大问题。但是当它长达数千行时呢。如果你不得不在未来两年看这段代码,因为有些事情发生了变化,找到这个不再适用的神奇功能并尝试弄清楚它做了什么并不是一件非常有趣的事情。最后,尤其是在VBA中,您可能会遇到溢出错误。如果使用了StrPtr并且它超过了您声明的数据类型的已分配空间,那么这是另一个不必要的错误。

Just my 2 cents but due to being able to use less code and the function being more stable without it I would not use it.

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

10+ years Excel Programmer.

10年以上的Excel程序员。