I dug through some old code recently and saw some really strange stuff. One of these was use of the wvnsprintf() function in shlwapi.dll so I decided to play with making a wrapper class for it.
There are a few nuisance things like the way C handles varying length argument lengths, the va_list. The macros they use are not clearly documented so a lot had to be reversed engineered through some trial and error.
The wvnsprintf() function is basically a cut down STDCALL equivalent of _vsnwprintf() in CRT libraries. The major omission is floating-point formatting but there also seem to be a number of other differences and quirks.
Here are a few formats that work:
Code:
Option Explicit
Private Sub Prt(ParamArray Text() As Variant)
Dim I As Long
With Text1
.SelStart = &H7FFF
For I = 0 To UBound(Text)
.SelText = Text(I)
Next
.SelText = vbNewLine
End With
End Sub
Private Sub Form_Load()
Dim LongLong As Variant
With New SPrintF
'Unicode char (c) format which ignores the upper word::
Prt "[", .SPrintF(10, "%c", 34), "]"
Prt "[", .SPrintF(10, "%c", &HFFFF0141), "]" 'The "L with stroke," shows ANSI L in the TextBox.
'ANSI char (C) format which ignores the upper 3 bytes:
Prt "[", .SPrintF(10, "%C", &HABCD141), "]" 'Produces "A" (&H41).
Prt
'Strings:
Prt "[", .SPrintF(10, "%s", "Test"), "]"
Prt "[", .SPrintF(10, "%5s", "Test"), "]"
Prt "[", .SPrintF(10, "%-5s", "Test"), "]"
Prt "[", .SPrintF(10, "%.3s", "Test"), "]"
Prt "[", .SPrintF(30, "%s, %s, and %s", "Test", "test", "testing"), "]"
'Note that a vbNullChar will terminate strings early:
Prt "[", .SPrintF(30, "%s, %s, and %s", "Test", "test", "test" & vbNullChar & "ing"), "]"
'Percent (%) insertion after format spec:
Prt "[", .SPrintF(10, "%s%%", "39.2"), "]"
Prt
'"Decimal" (as in not hex) integers:
Prt "[", .SPrintF(10, "%+d %+d", -678, 678), "]"
Prt "[", .SPrintF(10, "%d", 888), "]"
Prt "[", .SPrintF(10, "%5d", 666), "]"
Prt "[", .SPrintF(10, "%*d", 5, 111), "]"
Prt "[", .SPrintF(10, "%-*d", 5, 222), "]"
'These give leading 0's two different ways:
Prt "[", .SPrintF(10, "%.5d", 777), "]"
Prt "[", .SPrintF(10, "%0*d", 5, 333), "]"
'These use "invisible +" i.e. a space for + and - for -:
Prt "[", .SPrintF(10, "% d", -123), "]"
Prt "[", .SPrintF(10, "% d", 123), "]"
Prt "[", .SPrintF(10, "% *d", 7, -123), "]"
Prt "[", .SPrintF(10, "% *d", 7, 123), "]"
Prt "[", .SPrintF(10, "% -*d", 7, -123), "]"
Prt "[", .SPrintF(10, "% -*d", 7, 123), "]"
'Use the (i) instead of the (d), same result:
Prt "[", .SPrintF(10, "% -*i", 7, 123), "]"
Prt
'Use a "sHort" i.e. Integer, 16-bit (h) format which ignores the upper word:
Prt "[", .SPrintF(10, "%hd", &H7FFF0020), "]"
Prt
Prt "[", .SPrintF(10, "%X", &HEEDDCC), "]"
Prt "[", .SPrintF(10, "%x", &HEEDDCC), "]"
Prt "[", .SPrintF(10, "%8x", &HEEDDCC), "]"
Prt "[", .SPrintF(10, "%-8x", &HEEDDCC), "]"
Prt "[", .SPrintF(10, "%08x", &HEEDDCC), "]"
'Pass two Long values but use a LongLong (ll - two lowercase L) format:
Prt "[", .SPrintF(20, "%016llx", &HFFEEDDCC, 0), "]"
Prt "[", .SPrintF(18, "&H%016llX", &H12345678, &HFFEEDDCC), "]"
Prt
'Pass a 64-bit LongLong, use the alternate 64-bit size (I64) format:
LongLong = (.CLongLong(&H7FFFFFFF) + 1) * &H10000 * &H10000 _
Or (.CLongLong(&HACBDCED) * &H10 Or &HF&)
Prt "[", .SPrintF(22, "%I64x", LongLong), "]"
Prt "[", .SPrintF(22, "%I64X", LongLong), "]"
Prt
'Pass some VB-native 64-bit types:
Prt "[", .SPrintF(22, "%016I64x", 1.0001@), "]"
Prt "[", .SPrintF(22, "%016I64x", 1#), "]"
Prt "[", .SPrintF(22, "%016I64X", #1/31/2001 11:59:59 PM#), "]"
Prt
'Octal:
Prt "[", .SPrintF(22, "%llo", &H12345678, &HFFEEDDCC), "]"
Prt "[", .SPrintF(22, "%I64o", &H12345678, &HFFEEDDCC), "]"
End With
End Sub
Private Sub Form_Resize()
If WindowState <> vbMinimized Then
Text1.Move 0, 0, ScaleWidth, ScaleHeight
End If
End Sub
Even though it doesn't handle floating-point formatting you can still use it to dump 64-bit types in hex, so that might be useful I suppose. But we can do pretty much anything in VB that this function can do even if it can take a few lines of code.
For example if we really want to dump a Double in hex we can always use UDTs, CopyMemory, etc. to convert it into two Longs.
Rather than just throw it away I decided to post it here instead of the CodeBank. I don't think many people will find it very useful but opinions might be interesting to read.
Those warnings are accurate but they are also plastered over tons of APIs we use all of the time. They are really disclaimers, advising you to be very careful.
The "alternatives" are macros that call bounds checking routines and finally end up calling "unsafe" entrypoints themselves. To be safe you'd want to add similar logic to a wrapper class.
But for that matter calling CopyMemory is far more unsafe and people blindly copy/paste that willy-nilly.
RE: Those warnings. If using VB strings/property values vs string pointers from elsewhere, that specific warning doesn't apply. VB strings are always null terminated. I'd imagine anyone using this will be using VB-related strings a vast majority of the time.
Insomnia is just a byproduct of, "It can't be done"