How can I provide Nothing (Null-Pointer) to a user defined type in VBA for using it as Windows API function argument?

huangapple go评论79阅读模式
英文:

How can I provide Nothing (Null-Pointer) to a user defined type in VBA for using it as Windows API function argument?

问题

VBA中,我声明了一个需要指向struct参数的Windows API函数。为了表示这个struct,我创建了一个Public Type,并将函数参数声明为ByRef

现在,这个struct指针可能为null,所以我尝试将Nothing赋值给我的UDT变量,但这并不起作用。

我该如何使这个工作?

以下是我代码的基本摘录:

Private Declare PtrSafe Function GetNumberFormatEx& Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags&, _
  ByVal lpValue As LongPtr, _
  ByRef lpFormat As NumberFormat, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber& _
)


Public Type NumberFormat
  NumDigits As Integer
  LeadingZero As Integer
  Grouping As Integer
  lpDecimalSep As LongPtr
  lpThousandSep As LongPtr
  NegativeOrder As Integer
End Type


Public Function FormatNumberLocale$(srcValue As Double, lcid$, Optional flags& = 0, Optional customFormat$ = vbNullString)
  Dim buffer$
  Dim charCount&
  Dim numFormat As NumberFormat

  buffer = String(100, 0)
  'numFormat = Nothing    ' THIS DOESN'T WORK !!!
  charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(Str$(srcValue)), numFormat, StrPtr(buffer), 100)
  
  If charCount > 0 Then FormatNumberLocale = Left$(buffer, charCount)
End Function

编辑

我将声明更改为:

Private Declare PtrSafe Function GetNumberFormatEx& Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags&, _
  ByVal lpValue As LongPtr, _
  ByVal lpFormat As LongPtr, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber& _
)

并像这样调用函数:

...
Dim value$

buffer = String(100, 0)
value = Str$(srcValue)
charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(value), CLngPtr(0&), StrPtr(buffer), 100)

但即使使用基本参数调用它,例如?FormatNumberLocale(123,"en")charCount始终为0Err.LastDllError始终返回87 (0x57): ERROR_INVALID_PARAMETER

有什么建议吗?

英文:

In VBA, I declared a Windows API function that requires a pointer to a struct argument. For representing this struct I created a Public Type and declared the function parameter as ByRef.

Now, this struct pointer may be null, so I tried to assign Nothing to a variable of my UDT, but that doesn't work.

How can I make this work?

Here are the basic excerpts from my code:

Private Declare PtrSafe Function GetNumberFormatEx& Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags&, _
  ByVal lpValue As LongPtr, _
  ByRef lpFormat As NumberFormat, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber& _
)


Public Type NumberFormat
  NumDigits As Integer
  LeadingZero As Integer
  Grouping As Integer
  lpDecimalSep As LongPtr
  lpThousandSep As LongPtr
  NegativeOrder As Integer
End Type


Public Function FormatNumberLocale$(srcValue As Double, lcid$, Optional flags& = 0, Optional customFormat$ = vbNullString)
  Dim buffer$
  Dim charCount&
  Dim numFormat As NumberFormat

  buffer = String(100, 0)
  'numFormat = Nothing    ' THIS DOESN'T WORK !!!
  charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(Str$(srcValue)), numFormat, StrPtr(buffer), 100)
  
  If charCount > 0 Then FormatNumberLocale = Left$(buffer, charCount)
End Function

Edit

I changed the declaration to:

Private Declare PtrSafe Function GetNumberFormatEx& Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags&, _
  ByVal lpValue As LongPtr, _
  ByVal lpFormat As LongPtr, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber& _
)

and called the function like this:

...
Dim value$

buffer = String(100, 0)
value = Str$(srcValue)
charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(value), CLngPtr(0&), StrPtr(buffer), 100)

But even when calling it with basic parameters, like ?FormatNumberLocale(123,"en"), charCount is always 0, and Err.LastDllError always returns 87 (0x57): ERROR_INVALID_PARAMETER.

Any ideas?

答案1

得分: 2

VBA中传递NULL给结构指针的习惯做法是将参数声明为ByRef As Any

Private Declare PtrSafe Function GetNumberFormatEx Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags As Long, _
  ByVal lpValue As LongPtr, _
  ByRef lpFormat As Any, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber As Long _
) As Long

然后传递要么是一个结构变量(在你的示例中是numFormat),要么是ByVal 0&以表示空指针。

英文:

The idiomatic VBA way of passing NULL for a struct pointer is declaring the argument as ByRef As Any:

Private Declare PtrSafe Function GetNumberFormatEx Lib "Kernel32" ( _
  ByVal lpLocaleName As LongPtr, _
  ByVal dwFlags As Long, _
  ByVal lpValue As LongPtr, _
  ByRef lpFormat As Any, _
  ByVal lpNumberStr As LongPtr, _
  ByVal cchNumber As Long _
) As Long

and then passing either a struct variable (numFormat in your example) or ByVal 0& for null.

答案2

得分: -1

'@References
' https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getnumberformatex
' https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename
Option Explicit
'@Reference
' https://learn.microsoft.com/en-us/windows/win32/api/winnls/ns-winnls-numberfmta
Public Type NumberFormat
NumDigits As Long             'Number of fractional digits placed after the decimal separator.
LeadingZero As Long           '0   No leading zeros, 1   Leading zeros
Grouping As Long              'Values in the range 0 through 9 and 32 are valid
'Typical examples of settings for this member are: 0 to group digits as in 123456789.00;
'3 to group digits as in 123,456,789.00; and 32 to group digits as in 12,34,56,789.00.
lpDecimalSep As LongPtr       'Pointer to a null-terminated decimal separator string.
lpThousandSep As LongPtr      'Pointer to a null-terminated thousand separator string.
NegativeOrder As Long         'Negative number mode. This mode is equivalent to the locale information specified by the value
'https://learn.microsoft.com/en-us/windows/win32/intl/locale-ineg-constants
End Type
Private Const LOCALE_NOUSEROVERRIDE As Long = &H80000000
Private Const NULL_PTR As LongPtr = 0
' https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
' https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299-
Private Const ERROR_OUTOFMEMORY As Long = 14           '(0xE)
Private Const ERROR_INVALID_PARAMETER As Long = 87     '(0x57)
Private Const ERROR_INSUFFICIENT_BUFFER As Long = 122  '(0x7A)
Private Const ERROR_INVALID_FLAGS As Long = 1004       '(0x3EC)
Private Declare PtrSafe Function GetSystemDefaultLocaleName Lib "Kernel32" ( _
ByVal lpLocaleName As LongPtr, _
ByVal cchLocaleName As Long _
) As Long
Private Declare PtrSafe Function GetNumberFormatEx Lib "Kernel32" ( _
ByVal lpLocaleName As LongPtr, _
ByVal dwFlags As Long, _
ByVal lpValue As LongPtr, _
ByVal lpFormat As LongPtr, _
ByVal lpNumberStr As LongPtr, _
ByVal cchNumber As Long _
) As Long
'@Exceptions
'   ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.
Public Function GetSystemLocalName() As String
Const LOCALE_NAME_MAX_LENGTH As Long = 85
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(LOCALE_NAME_MAX_LENGTH * CHAR_LENGTH)
Dim bufferPtr As LongPtr
bufferPtr = VarPtr(buffer(0))
Dim charCount As Long
charCount = GetSystemDefaultLocaleName(bufferPtr, LOCALE_NAME_MAX_LENGTH)
If charCount > 0 Then
ReDim Preserve buffer((charCount - 1) * CHAR_LENGTH)
GetSystemLocalName = buffer
Else
Select Case Err.LastDllError
Case ERROR_INSUFFICIENT_BUFFER
Err.Raise Err.LastDllError, "GetSystemLocalName", "A supplied buffer size was not large enough, or it was incorrectly set to NULL."
Case Else
Err.Raise Err.LastDllError, "GetSystemLocalName", "Unexpected error occurred."
End Select
End If
End Function
'@Exceptions
'   ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.
'   ERROR_INVALID_FLAGS. The values supplied for flags were not valid.
'   ERROR_INVALID_PARAMETER. Any of the parameter values was invalid.
'   ERROR_OUTOFMEMORY. Not enough storage was available to complete this operation.
Public Function FormatNumberLocale(ByVal value As Double, ByVal lcid As String, Optional ByVal flags As Long = 0) As String
Const MAX_BUFFER_LENGTH As Long = 100
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(MAX_BUFFER_LENGTH * CHAR_LENGTH)
Dim bufferPtr As LongPtr
bufferPtr = VarPtr(buffer(0))
Dim charCount As Long
charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(CStr(value)), NULL_PTR, bufferPtr, MAX_BUFFER_LENGTH)
If charCount > 0 Then
ReDim Preserve buffer((charCount - 1) * CHAR_LENGTH)
FormatNumberLocale = buffer
Else
Select Case Err.LastDllError
Case ERROR_INSUFFICIENT_BUFFER
Err.Raise Err.LastDllError, "FormatNumberLocale", "A supplied buffer size was not large enough, or it was incorrectly set to NULL."
Case ERROR_INVALID_FLAGS
Err.Raise Err.LastDllError, "FormatNumberLocale", "The values supplied for flags were not valid."
Case ERROR_INVALID_PARAMETER
Err.Raise Err.LastDllError, "FormatNumberLocale", "Any of the parameter values was invalid."
Case ERROR_OUTOFMEMORY
Err.Raise Err.LastDllError, "FormatNumberLocale", "Not enough storage was available to complete this operation."
Case Else
Err.Raise Err.LastDllError, "FormatNumberLocale", "Unexpected error occurred."
End Select
End If
End Function
Public Sub FormatNumberLocaleTest()
Dim value As Double
Dim lcid As String
Dim valueLocal As String
value = 12345.678
lcid = GetSystemLocalName()
valueLocal = FormatNumberLocale(value, lcid, LOCALE_NOUSEROVERRIDE)
Debug.Print " Value: " & value
Debug.Print " Format value local: " & valueLocal
Debug.Print " System Local Name:  " & lcid
Debug.Print
lcid = "de-DE"
valueLocal = FormatNumberLocale(value, lcid)
Debug.Print " Value: " & value
Debug.Print " Format value local: " & valueLocal
Debug.Print " System Local Name:  " & lcid
Debug.Print
End Sub
'@Output:
' Value: 12345.67
' Format value local: 12,345.67
' System Local Name:  en-AU
'
' Value: 12345.67
' Format value local: 12.345,67
' System Local Name:  de-DE
'Notes UDT types are not allowed to optional and must be passed by reference
'Possible work around wrap the UDT in an object and check if missing use NULL_PTR or VarPtr of UDT of type NumberFormat
Public Function FormatNumberCustom(ByVal value As Double, ByVal lcid As String, ByVal flags As Long, ByRef customFormat As NumberFormat) As String
Const MAX_BUFFER_LENGTH As Long = 100
Const CHAR_LENGTH As Long =
<details>
<summary>英文:</summary>
&#39;@References
&#39; https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getnumberformatex
&#39; https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename
Option Explicit
&#39;@Reference
&#39; https://learn.microsoft.com/en-us/windows/win32/api/winnls/ns-winnls-numberfmta
Public Type NumberFormat
NumDigits As Long             &#39;Number of fractional digits placed after the decimal separator.
LeadingZero As Long           &#39;0   No leading zeros, 1   Leading zeros
Grouping As Long              &#39;Values in the range 0 through 9 and 32 are valid
&#39;Typical examples of settings for this member are: 0 to group digits as in 123456789.00;
&#39;3 to group digits as in 123,456,789.00; and 32 to group digits as in 12,34,56,789.00.
lpDecimalSep As LongPtr       &#39;Pointer to a null-terminated decimal separator string.
lpThousandSep As LongPtr      &#39;Pointer to a null-terminated thousand separator string.
NegativeOrder As Long         &#39;Negative number mode. This mode is equivalent to the locale information specified by the value
&#39;https://learn.microsoft.com/en-us/windows/win32/intl/locale-ineg-constants
End Type
Private Const LOCALE_NOUSEROVERRIDE As Long = &amp;H80000000
Private Const NULL_PTR As LongPtr = 0
&#39; https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
&#39; https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299-
Private Const ERROR_OUTOFMEMORY As Long = 14           &#39;(0xE)
Private Const ERROR_INVALID_PARAMETER As Long = 87     &#39;(0x57)
Private Const ERROR_INSUFFICIENT_BUFFER As Long = 122  &#39;(0x7A)
Private Const ERROR_INVALID_FLAGS As Long = 1004       &#39;(0x3EC)
Private Declare PtrSafe Function GetSystemDefaultLocaleName Lib &quot;Kernel32&quot; ( _
ByVal lpLocaleName As LongPtr, _
ByVal cchLocaleName As Long _
) As Long
Private Declare PtrSafe Function GetNumberFormatEx Lib &quot;Kernel32&quot; ( _
ByVal lpLocaleName As LongPtr, _
ByVal dwFlags As Long, _
ByVal lpValue As LongPtr, _
ByVal lpFormat As LongPtr, _
ByVal lpNumberStr As LongPtr, _
ByVal cchNumber As Long _
) As Long
&#39;@Exceptions
&#39;   ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.
Public Function GetSystemLocalName() As String
Const LOCALE_NAME_MAX_LENGTH As Long = 85
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(LOCALE_NAME_MAX_LENGTH * CHAR_LENGTH)
Dim bufferPtr As LongPtr
bufferPtr = VarPtr(buffer(0))
Dim charCount As Long
charCount = GetSystemDefaultLocaleName(bufferPtr, LOCALE_NAME_MAX_LENGTH)
If charCount &gt; 0 Then
ReDim Preserve buffer((charCount - 1) * CHAR_LENGTH)
GetSystemLocalName = buffer
Else
Select Case Err.LastDllError
Case ERROR_INSUFFICIENT_BUFFER
Err.Raise Err.LastDllError, &quot;GetSystemLocalName&quot;, &quot;A supplied buffer size was not large enough, or it was incorrectly set to NULL.&quot;
Case Else
Err.Raise Err.LastDllError, &quot;GetSystemLocalName&quot;, &quot;Unexpected error occurred.&quot;
End Select
End If
End Function
&#39;@Exceptions
&#39;   ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.
&#39;   ERROR_INVALID_FLAGS. The values supplied for flags were not valid.
&#39;   ERROR_INVALID_PARAMETER. Any of the parameter values was invalid.
&#39;   ERROR_OUTOFMEMORY. Not enough storage was available to complete this operation.
Public Function FormatNumberLocale(ByVal value As Double, ByVal lcid As String, Optional ByVal flags As Long = 0) As String
Const MAX_BUFFER_LENGTH As Long = 100
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(MAX_BUFFER_LENGTH * CHAR_LENGTH)
Dim bufferPtr As LongPtr
bufferPtr = VarPtr(buffer(0))
Dim charCount As Long
charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(CStr(value)), NULL_PTR, bufferPtr, MAX_BUFFER_LENGTH)
If charCount &gt; 0 Then
ReDim Preserve buffer((charCount - 1) * CHAR_LENGTH)
FormatNumberLocale = buffer
Else
Select Case Err.LastDllError
Case ERROR_INSUFFICIENT_BUFFER
Err.Raise Err.LastDllError, &quot;FormatNumberLocale&quot;, &quot;A supplied buffer size was not large enough, or it was incorrectly set to NULL.&quot;
Case ERROR_INVALID_FLAGS
Err.Raise Err.LastDllError, &quot;FormatNumberLocale&quot;, &quot;The values supplied for flags were not valid.&quot;
Case ERROR_INVALID_PARAMETER
Err.Raise Err.LastDllError, &quot;FormatNumberLocale&quot;, &quot;Any of the parameter values was invalid.&quot;
Case ERROR_OUTOFMEMORY
Err.Raise Err.LastDllError, &quot;FormatNumberLocale&quot;, &quot;Not enough storage was available to complete this operation.&quot;
Case Else
Err.Raise Err.LastDllError, &quot;FormatNumberLocale&quot;, &quot;Unexpected error occurred.&quot;
End Select
End If
End Function
Public Sub FormatNumberLocaleTest()
Dim value As Double
Dim lcid As String
Dim valueLocal As String
value = 12345.678
lcid = GetSystemLocalName()
valueLocal = FormatNumberLocale(value, lcid, LOCALE_NOUSEROVERRIDE)
Debug.Print &quot; Value: &quot; &amp; value
Debug.Print &quot; Format value local: &quot; &amp; valueLocal
Debug.Print &quot; System Local Name:  &quot; &amp; lcid
Debug.Print
lcid = &quot;de-DE&quot;
valueLocal = FormatNumberLocale(value, lcid)
Debug.Print &quot; Value: &quot; &amp; value
Debug.Print &quot; Format value local: &quot; &amp; valueLocal
Debug.Print &quot; System Local Name:  &quot; &amp; lcid
Debug.Print
End Sub
&#39;Output:
&#39; Value: 12345.67
&#39; Format value local: 12,345.67
&#39; System Local Name:  en-AU
&#39;
&#39; Value: 12345.67
&#39; Format value local: 12.345,67
&#39; System Local Name:  de-DE
&#39;Notes UDT types are not allowed to optional and must be passed by reference
&#39;Possible work around wrap the UDT in an object and check if missing use NULL_PTR or VarPtr of UDT of type NumberFormat
Public Function FormatNumberCustom(ByVal value As Double, ByVal lcid As String, ByVal flags As Long, ByRef customFormat As NumberFormat) As String
Const MAX_BUFFER_LENGTH As Long = 100
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(MAX_BUFFER_LENGTH * CHAR_LENGTH)
Dim bufferPtr As LongPtr
bufferPtr = VarPtr(buffer(0))
Dim charCount As Long
charCount = GetNumberFormatEx(StrPtr(lcid), flags, StrPtr(CStr(value)), VarPtr(customFormat), bufferPtr, MAX_BUFFER_LENGTH)
If charCount &gt; 0 Then
ReDim Preserve buffer((charCount - 1) * CHAR_LENGTH)
FormatNumberCustom = buffer
Else
Select Case Err.LastDllError
Case ERROR_INSUFFICIENT_BUFFER
Err.Raise Err.LastDllError, &quot;FormatNumberCustom&quot;, &quot;A supplied buffer size was not large enough, or it was incorrectly set to NULL.&quot;
Case ERROR_INVALID_FLAGS
Err.Raise Err.LastDllError, &quot;FormatNumberCustom&quot;, &quot;The values supplied for flags were not valid.&quot;
Case ERROR_INVALID_PARAMETER
Err.Raise Err.LastDllError, &quot;FormatNumberCustom&quot;, &quot;Any of the parameter values was invalid.&quot;
Case ERROR_OUTOFMEMORY
Err.Raise Err.LastDllError, &quot;FormatNumberCustom&quot;, &quot;Not enough storage was available to complete this operation.&quot;
Case Else
Err.Raise Err.LastDllError, &quot;FormatNumberCustom&quot;, &quot;Unexpected error occurred.&quot;
End Select
End If
End Function
Public Sub CustomFormatNumberTest()
Dim value As Double
Dim lcid As String
Dim customFormat As String
value = -12345.678
Dim decimalSeparator As String
decimalSeparator = &quot;@&quot;
Dim thousandSepartor As String
thousandSepartor = &quot;#&quot;
Dim customNumberFormat As NumberFormat
customNumberFormat.NumDigits = 2   &#39;
customNumberFormat.LeadingZero = 1 &#39;Leading zero&#39;s
customNumberFormat.Grouping = 3
customNumberFormat.lpDecimalSep = StrPtr(decimalSeparator)
customNumberFormat.lpThousandSep = StrPtr(thousandSepartor)
customNumberFormat.NegativeOrder = 4 &#39;Number, space, negative sign; for example, 1.1 -
lcid = &quot;de-DE&quot;
customFormat = FormatNumberCustom(value, lcid, 0, customNumberFormat)
Debug.Print &quot; Value: &quot; &amp; value
Debug.Print &quot; Custom format value : &quot; &amp; customFormat
Debug.Print &quot; System Local Name:  &quot; &amp; lcid
Debug.Print
lcid = &quot;en-AU&quot;
customFormat = FormatNumberCustom(value, lcid, 0, customNumberFormat)
Debug.Print &quot; Value: &quot; &amp; value
Debug.Print &quot; Custom format value : &quot; &amp; customFormat
Debug.Print &quot; System Local Name:  &quot; &amp; lcid
Debug.Print
value = 12345.678
lcid = &quot;en-AU&quot;
customFormat = FormatNumberCustom(value, lcid, 0, customNumberFormat)
Debug.Print &quot; Value: &quot; &amp; value
Debug.Print &quot; Custom format value : &quot; &amp; customFormat
Debug.Print &quot; System Local Name:  &quot; &amp; lcid
Debug.Print
End Sub
&#39;Output:
&#39; Value: -12345.678
&#39; Custom format value : 12#345@68 -
&#39; System Local Name:  de-DE
&#39;
&#39; Value: -12345.678
&#39; Custom format value : 12#345@68 -
&#39; System Local Name:  en-AU
&#39;
&#39; Value: 12345.678
&#39; Custom format value : 12#345@68
&#39; System Local Name:  en-AU
</details>

huangapple
  • 本文由 发表于 2023年7月23日 15:57:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76747186.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定