在 VBA 中,我声明了一个 Windows API 函数,该函数需要一个指向
struct
参数的指针。为了表示这个 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
始终是0
,并且Err.LastDllError
始终返回87 (0x57): ERROR_INVALID_PARAMETER
。
有什么想法吗?
为结构体指针传递
NULL
的惯用 VBA 方式是将参数声明为 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&
为空。
'@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
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 = 14 '(0xE)
Private Const ERROR_INVALID_PARAMETER = 87 '(0x57)
Private Const ERROR_INSUFFICIENT_BUFFER = 122 '122 (0x7A)
Private Const ERROR_INVALID_FLAGS = 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)
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, Optional ByVal customFormat As String = vbNullString) As String
Const NULL_PTR As LongPtr = 0
Const MAX_BUFFER_LENGTH As Long = 100
Const CHAR_LENGTH As Long = 2
Dim buffer() As Byte
ReDim buffer(MAX_BUFFER_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.67
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