如何向 VBA 中的用户定义类型提供 Nothing(空指针)以将其用作 Windows API 函数参数?

问题描述 投票:0回答:2

在 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

有什么想法吗?

vba winapi vba7
2个回答
1
投票

为结构体指针传递

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&
为空。


-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

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
© www.soinside.com 2019 - 2024. All rights reserved.