Determine the unit setting in powerpoint - vba

I am trying use VBA to determine the user preference for the measurement unit in PowerPoint, however, I can't find the correct method. Do you know a way to determine if the unit setting is in inches, cm, pt?

I had this need too; with Word or Excel is easy, because Word.Application.Options.MeasurementUnit or Excel.Application.MeasurementUnit returns it; but for other Office Apps you have to grab it from a registry key, if your project has no Word or Excel reference, of course.
I have in one module:
Option Explicit
Public Const KeyInternationalMeasurementUnits As String = "HKEY_CURRENT_USER\Control Panel\International\iMeasure"
Enum eMeasure
Metrics = 0
Imperial = 1
End Enum
Function eMeasure_ToEnum(str As String) As eMeasure
Select Case str
Case "Metrics": eMeasure_ToEnum = Metrics
Case "Imperial": eMeasure_ToEnum = Imperial
End Select
End Function
Function eMeasure_ToString(value As eMeasure) As String
Select Case value
Case Metrics: eMeasure_ToString = "Metrics"
Case Imperial: eMeasure_ToString = "Imperial"
End Select
End Function
Function RegKeyRead(ByVal ReadedKey As String) As String
Dim thisWS As Object
Set thisWS = CreateObject("WScript.Shell")
RegKeyRead = thisWS.RegRead(ReadedKey)
Set thisWS = Nothing
End Function
Function RegKeyExists(ByVal RegKey As String) As Boolean
Dim thisWS As Object
On Error GoTo ErrorHandler
Set thisWS = CreateObject("WScript.Shell")
thisWS.RegRead RegKey
RegKeyExists = True
GoTo ExitFunction
ExitFunction:
Set thisWS = Nothing
Exit Function
ErrorHandler:
RegKeyExists = False
GoTo ExitFunction
End Function
And I call it whenever I need to:
Function WhichInternationalMeasurementUnits() As String
If RegKeyExists(KeyInternationalMeasurementUnits) Then
WhichInternationalMeasurementUnits = eMeasure_ToString(CInt(RegKeyRead(KeyInternationalMeasurementUnits)))
End If
End Function
You can adapt too an IsWord or IsExcel precondition test like this one that I used to grab which decimal sign is on regional settings.

If you're on Windows, there is no setting for preferred measurement units. PPT picks up the units, metric or imperial, from your Windows settings.
If it's important to know what units the user is seeing, you'd need to query the Win API.
If your code needs to use coordinates, the setting the user sees is not relevant; your code will use points (72 to the inch).
Did a bit of digging in a couple of Dan Appleman's old books and cobbled up this API call to determine whether the system is set to US or Metric. If I pass it 1033 (US English), it returns 1 until I go into Control Panel and set the system for metric; then it returns 0. But with the system set to US, the function returns 0 if I pass it the locale code for e.g. Dutch.
Fair warning: I'm strictly a cut/paste/play 'til it stops crashing API programmer. Nearly incompetent at it. Take it all with a grain of salt, eh?
Option Explicit
Declare Function GetLocaleInfo& Lib "kernel32" Alias "GetLocaleInfoA" (ByVal _
Locale As Long, ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData _
As Long)
Function WindowsUSorMetric() As Long
' Returns 1 for U.S. or 0 for Metric
' NOTE: Needs modification before it'll work with 64-bit apps
' Assumes USEnglish
Dim Locale As Long
Dim LCType As Long
Dim lpLCData As String
Dim cchData As Long
' 1033 is the languageID for US English
' Use the Object Browser in the VBA IDE, look up msolanguageid for others
Locale = 1033
LCType = &HD
lpLCData = String$(255, 0)
cchData = 255
Call GetLocaleInfo(Locale, LCType, lpLCData, cchData)
WindowsUSorMetric = CLng(Left$(lpLCData, InStr(lpLCData, Chr$(0)) - 1))
End Function
Sub TestMe()
MsgBox WindowsUSorMetric
End Sub

Related

How do I perform unicode normalization for password storage in VBA?

I want to store and compare hashed passwords in VBA.
I've read How do I properly implement Unicode passwords?, but I have no clue about where to start.
How do I normalize a unicode string in VBA?
Preferably, I'd do this without downloading the ICU the linked post refers to, because I'd like my project not to be dependent on external code.
Windows provides a built-in for normalizing strings, the NormalizeString function. However, it can be a bit tricky to use.
Here is an implementation, based on the C example in the docs provided above:
'Declare the function
Public Declare PtrSafe Function NormalizeString Lib "Normaliz.dll" (ByVal NormForm As Byte, ByVal lpSrcString As LongPtr, ByVal cwSrcLength As Long, ByVal lpDstString As LongPtr, ByVal cwDstLength As Long) As Long
'And a relevant error code
Const ERROR_INSUFFICIENT_BUFFER = 122
'And a helper enum
Public Enum NORM_FORM
NormalizationC = &H1
NormalizationD = &H2
NormalizationKC = &H5
NormalizationKD = &H6
End Enum
'Available normalization forms can be found under https://learn.microsoft.com/en-us/windows/win32/api/winnls/ne-winnls-norm_form
'KD normalization is preferred(https://stackoverflow.com/a/16173329/7296893) when hashing characters
'If you already have hashes stored, C normalization is least likely to break them
Public Function UnicodeNormalizeString(str As String, Optional norm_form As Byte = NormalizationKD) As String
If Len(str) = 0 Then 'Zero-length strings can't be normalized
UnicodeNormalizeString = str
Exit Function
End If
Dim outlenestimate As Long
'Get an initial length estimate for the string
outlenestimate = NormalizeString(norm_form, StrPtr(str), Len(str), 0, 0)
Dim i As Long
'Try 10 times
For i = 1 To 10
'Initialize buffer
UnicodeNormalizeString = String(outlenestimate, vbNullChar)
'Get either the normalized string, or a new length estimate
outlenestimate = NormalizeString(norm_form, StrPtr(str), Len(str), StrPtr(UnicodeNormalizeString), outlenestimate)
If outlenestimate > 0 Then 'We got the normalized string
'Truncate off the unused characters
UnicodeNormalizeString = Left(UnicodeNormalizeString, outlenestimate)
Exit Function
Else
If Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then
Exit For 'An unexpected error occurred
End If
outlenestimate = outlenestimate * -1 'Use the new length estimate, try again
End If
Next
Err.Raise 5000, Description:="Failure to normalize unicode string"
End Function
Once you have declared the normalization function, always run your password through it before hashing:
If SomeHashFun(UnicodeNormalizeString(MyPassword)) = SomeHashedPassword Then
'We are in!
End If

Is there a VBA equivalent (or way to replicate) passing parameters as 'Out' like C#?

I generally use VBA but have been reading up on programming techniques in The C# Programming Yellow Book which, obviously, is more specific to C#. Anyway, it mentions a technique of passing parameters using the Out keyword.
I already know that VBA supports byVal and byRef and am fairly certain there is no direct equivalent for Out. Passing parameters using Out is subtly different to passing parameters by Ref.
This Answer https://stackoverflow.com/a/388781/3451115 seems to give a good explanation of the difference between Out & Ref.
The Ref modifier means that:
The value is already set and
The method can read and modify it.
The Out modifier means that:
The Value isn't set and can't be read by the method until it is set.
The method must set it before returning.
In the code base that I've inherited there are several places where values are assigned to variables using methods that accept parameters byRef. It seems to me that while passing byRef does the job, passing by Out would be safer... So (and here is the question) is there a way of safely / reliably replicating Out in VBA?
In my first iteration (original question) I imagined that the code would have a pattern like:
Sub byOutExample(byRef foo As String)
' Check before running code:
' foo must = vbNullString
If foo <> vbNullString then Err.Raise(someError)
' Do Something to assign foo
foo = someString
' Check before exiting:
' foo must <> vbNullString
If foo = vbNullString then Err.Raise(someError)
End Sub
Other considerations: is it worth doing, is there a better way, what could go wrong?
Edit: I noticed in the comments for the above definition of Ref vs Out that the passed parameter need not be null, nothing, empty etc. it can be preassigned - the main criteria seems that it is re-assigned.
In light of #ThunderFrame's answer below and the comment that a parameter passed by Out can be pre-assigned (and used), perhaps the following is a better approach:
Sub byOutExample(ByRef foo As String)
Dim barTemp As String
barTemp = foo
' Do Something to assign a new value to foo (via barTemp)
barTemp = someString
' Must assign new variable
foo = barTemp
End Sub
In which case would it be true to say that, as long as foo only appears in the 2 locations shown above, the above code is an accurate way to replicate passing a parameter by Out in VBA?
The answer is unequivocally 'no' you cannot replicate the C# out parameter modifier in VBA. From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier:
Variables passed as out arguments do not have to be initialized before
being passed in a method call. However, the called method is required
to assign a value before the method returns.
These aspects simply don't exist in VBA. All variables in VBA are initialised with default values, ie the concept of an unitialised variable does not exist in VBA, so point 1 isn't possible; and the compiler cannot object if a specified parameter has not had a value assigned within the procedure, so point 2 isn't possible either.
Even the coding patterns in your example would rely on the Do Something to assign foo not to resolve to the relevant data type's default value (which is obviously not the same as being unitialised). The following, for example, would wrongly throw an error:
Public Sub Main()
Dim income As Long, costs As Long
Dim result As Long
income = 1000
costs = 500
ProcessSpend income, costs, result
End Sub
Private Sub ProcessSpend(income As Long, costs As Long, ByRef outValue As Long)
Const TAX_RATE As Long = 2
Dim netCosts As Long
Dim vbDefaultValue As Long
netCosts = costs * TAX_RATE
outValue = income - netCosts
If outValue = vbDefaultValue Then Err.Raise 5, , "Unassigned value"
End Sub
So we're really left with the question of is there a way of getting close to the characteristics of out in VBA?
Unitialised variables: the closest I can think of are a Variant or Object type which by default initialise to Empty and Nothing respectively.
Assign value within the procedure: the simplest way would be to test if the address of the assigning procedure matches your desired procedure address.
It's all leaning towards a helper class:
Option Explicit
Private mNumber As Long
Private mTargetProc As LongPtr
Private mAssignedInProc As Boolean
Public Sub SetTargetProc(targetProc As LongPtr)
mTargetProc = targetProc
End Sub
Public Sub SetNumber(currentProc As LongPtr, val As Long)
mAssignedInProc = (currentProc = mTargetProc)
mNumber = val
End Sub
Public Property Get Number() As Long
If mAssignedInProc Then
Number = mNumber
Else
Err.Raise 5, , "Unassigned value"
End If
End Property
And then the previous example would look like this:
Public Sub Main()
Dim income As Long, costs As Long
Dim result As clsOut
income = 1000
costs = 500
ProcessSpend income, costs, result
Debug.Print result.Number
End Sub
Private Sub ProcessSpend(income As Long, costs As Long, outValue As clsOut)
Const TAX_RATE As Long = 2
Dim netCosts As Long
If outValue Is Nothing Then
Set outValue = New clsOut
End If
outValue.SetTargetProc AddressOf ProcessSpend
netCosts = costs * TAX_RATE
outValue.SetNumber AddressOf ProcessSpend, income - netCosts
End Sub
But that's all getting very onerous... and it really feels as if we are trying to force another language's syntax onto VBA. Stepping back a little from the out characteristics and developing in a syntax for which VBA was designed, then a function which returns a Variant seems the most obvious way to go. You could test if you forgot to set the 'out' value by checking if the function returns an Empty variant (which suits point 1 and 2 of the out characteristics):
Public Sub Main()
Dim income As Long, costs As Long
Dim result As Variant
income = 1000
costs = 500
result = ProcessedSpend(income, costs)
If IsEmpty(result) Then Err.Raise 5, , "Unassigned value"
End Sub
Private Function ProcessedSpend(income As Long, costs As Long) As Variant
Const TAX_RATE As Long = 2
Dim netCosts As Long
netCosts = costs * TAX_RATE
'Comment out the line below to throw the unassigned error
ProcessedSpend = income - netCosts
End Function
And if you wanted the option of passing in a pre-assigned value, then could just define an optional argument as a parameter to the function.
You can pseudo enforce an out type parameter in VBA by passing it in ByRef, and then checking that it is Nothing (or the default value for a value type) before continuing, much as you have done with the String in your example.
I wouldn't impose the exit condition - sometimes an empty string is a perfectly valid return value, as is a Nothing reference.

Which would yield better performance in general?

I have a small dilemma. As we all know, defining variables by types and avoiding the usage of variants is the most obvious performance trick. The problem is I'm trying to write a library of routines that would work with implicitly-typed arguments (basically variants).
Take for example:
Sub Test(A As String) ' Implicit ByRef
Debug.Print A
End Sub
Nothing crazy, right? If I did Test "ABC", it works as expected. The problem arises when I try to pass a value from an array (Test Array("ABC")(0)) or even a return value from another routine while chaining. I'd get a compile error saying "ByRef argument type mismatch".
I need these routines to take in various types of arguments and typecast them when possible. I then thought of the following:
Sub Test(ByVal A As String) ' Explicit ByVal
Debug.Print A
End Sub
Now it works fine. So ultimately, my question is: is the performance gain achieved by explicitly defining argument types worth the tradeoff of the performance loss imposed by making copies of argument values from the use of ByVal? I know there will be cases where one would be better than the other, but for a general usage library, which method would be more suitable?
I have setup a small benchmark. I'm not exactly using the most powerful computer in the world (Core i3-2120, 32-bit Windows 7, 4 GB RAM), so I can't say these will apply for other setups.
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private Sub NoOp(ParamArray Self()) ' Adds a small overhead; not sure if there's a better built-in routine to use
End Sub
Private Sub ByReference(ByRef Argument As Variant) ' ByRef and As Variant are the default keywords if all keywords are ignored
NoOp Argument
End Sub
Private Sub ByValue(ByVal Argument As String)
NoOp Argument
End Sub
Private Sub Benchmark()
Dim Index As Long, Argument As Variant: Argument = "ABC"
A = GetTickCount / 1000
For Index = 1 To 10000000
ByReference Argument
Next
B = GetTickCount / 1000
For Index = 1 To 10000000
ByValue Argument
Next
Debug.Print B - A, (GetTickCount / 1000) - B ' Seconds; we get higher precision if we divide them before taking their differences
End Sub
Result: 3.88499999999476 4.99199999999837
We can see a very obvious performance loss for a string (a short one, anyway).
Now, if I change Argument to 12345 and the routine definition's argument type to Long, I get:
Result: 4.07200000000012 4.05599999999686
I don't know whether or not that's within the margin of error, but it does tell us that different types will behave differently.
Feel free to try different types on your own system and let me know.
I made a quick test, have a look below. Calling ByRef is faster, which comes to no surprise, but the amount of time is so small that usually you really don't have to care. Tested on Win7 with a three years old laptop, so nothing fancy.
Running with 1 million iterations, result was 141 ms for ByRef and 281 ms for ByVal. Changing the string used as parameter every other time increased the time, but for both methods, so I assume that this is due to string handling.
My conclusion: Choose calling method by your needs, not for speed.
Option Explicit
Private mlngStart As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long
Sub test()
testPerformance 1000000, False
testPerformance 1000000, True
End Sub
Sub testPerformance(iterations, changeString)
Dim s As String
s = "Hello World, this is a really long string to test what will happen when we pass it by value"
StartTimer
Dim i As Long
For i = 1 To iterations
If changeString Then s = IIf(i Mod 2 = 0, "Hello world", "Hello World, this is a really long string to test what will happen when we pass it by value")
Call f1(s)
Next i
Debug.Print iterations, changeString, "ByRef: " & EndTimer
StartTimer
For i = 1 To iterations
If changeString Then s = IIf(i Mod 2 = 0, "Hello world", "Hello World, this is a really long string to test what will happen when we pass it by value")
Call f2(s)
Next i
Debug.Print iterations, changeString, "ByVal: " & EndTimer
End Sub
Sub f1(ByRef s As String)
'
If s = "X" Then
Debug.Print s
End If
End Sub
Sub f2(ByVal s As String)
If s = "X" Then
Debug.Print s
End If
End Sub
Public Sub StartTimer()
mlngStart = GetTickCount
End Sub
Public Function EndTimer() As Long
EndTimer = (GetTickCount - mlngStart)
End Function
output:
1000000 False ByRef: 141
1000000 False ByVal: 281
1000000 True ByRef: 655
1000000 True ByVal: 764

Access vba function called from Excel results in different value returned

My ultimate goal is to generate a tool to predict the width of a string, so that I can avoid text overflow when printing reports in MS Access 2010. Options like CanGrow are not useful, because my reports cannot have unpredicted page breaks. I cannot cut off text.
To this end I discovered the undocumented WizHook.TwipsFromFont function in Access. It returns the width in twips of a string given font and other characteristics. It has proven quite useful as a starting point. Based on various user generated guides, I developed the following in Access:
Public Function TwipsFromFont(ByVal sCaption As String, ByVal sFontName As String, _
ByVal lSize As Long, Optional ByVal lWeight As Long = 400, _
Optional bItalic As Boolean = False, _
Optional bUnderline As Boolean = False, _
Optional lCch As Long = 0, _
Optional lMaxWidthCch As Long = 0) As Double
'inspired by http://www.team-moeller.de/?Tipps_und_Tricks:Wizhook-Objekt:TwipsFromFont
WizHook.Key = 51488399
Dim ldx As Long
Dim ldy As Long
Call WizHook.TwipsFromFont(sFontName, lSize, lWeight, bItalic, bUnderline, lCch, _
sCaption, lMaxWidthCch, ldx, ldy)
'Debug.Print CDbl(ldx)
TwipsFromFont = CDbl(ldx)
'TwipsFromFont = 99999
End Function
However, the data that will end up in Access is initially going to be generated in Excel 2010. Therefore, I would like to call this function in Excel, so I can check strings as they are created. To this end, I developed the following in Excel:
Public Function TwipsFromFontXLS() As Double
Dim obj As Object
Set obj = CreateObject("Access.Application")
With obj
.OpenCurrentDatabase "C:\MyPath\Jeremy.accdb"
TwipsFromFontXLS = .Run("TwipsFromFont", sCaption = "Hello World!", _
sFontName = "Arial Black", lSize = 20)
.Quit
End With
Set obj = Nothing
End Function
When I run debug.Print TwipsFromFont("Hello World!","Arial Black",20) in Access I get back 2670. When I run debug.Print TwipsFromFontXLS() in Excel I get back 585.
In Access, if I set TwipsFomFont = 9999, then debug.Print TwipsFromFontXLS() will return 9999.
Any thoughts on where my disconnect is?
For those that are interested, the issue turned out to be how Application.Run passed arguments. I was explicitly identifying my arguments, and this apparently created an issue. Below is code that appears to work when I call it in Excel. It isn't particularly fast, but at this point it works.
In Access:
Public Function TwipsFromFont(ByVal sCaption As String, ByVal sFontName As String, ByVal lSize As Long, Optional ByVal lWeight As Long = 400, Optional bItalic As Boolean = False, Optional bUnderline As Boolean = False, Optional lCch As Long = 0, Optional lMaxWidthCch As Long = 0) As Double
'inspired by http://www.team-moeller.de/?Tipps_und_Tricks:Wizhook-Objekt:TwipsFromFont
'required to call WizHook functions
WizHook.Key = 51488399
'width (ldx) and height (ldy) variables will be changed ByRef in the TwipsFromFont function
Dim ldx As Long
Dim ldy As Long
'call undocumented function
Call WizHook.TwipsFromFont(sFontName, lSize, lWeight, bItalic, bUnderline, lCch, sCaption, lMaxWidthCch, ldx, ldy)
'return printed text width in twips (1440 twips = 1 inch, 72 twips = 1 point, 20 points = 1 inch)
TwipsFromFont = CDbl(ldx)
End Function
In Excel:
Public Function TwipsFromFontXLS(ByVal sCaption As String, ByVal sFontName As String, ByVal lSize As Long, Optional ByVal lWeight As Long = 400, Optional bItalic As Boolean = False, Optional bUnderline As Boolean = False, Optional lCch As Long = 0, Optional lMaxWidthCch As Long = 0) As Double
'calls the WizHook.TwipsFromFont function from MS Access to calculate text width in twips
'create the application object
Dim obj As Object
Set obj = CreateObject("Access.Application")
With obj
'call the appropriate Access database
.OpenCurrentDatabase "C:\MyPath\Jeremy.accdb"
'pass the arguments to the Access function
'sCaption = the string to measure; sFontName = the Font; lSize = text size in points; lWeight = boldness, 400 is regular, 700 is bold, bItalic = italic style, bUnderline = underline style, lCch = number of characters with average width, lMaxwidth = number of characters with maximum width
TwipsFromFontXLS = .Run("TwipsFromFont", sCaption, sFontName, lSize, lWeight, bItalic, bUnderline, lCch, lMaxwidth)
'close the connection to the Access database
.Quit
End With
End Function
As remarked in Application.Run method:
You cannot use named arguments with this method. Arguments must be
passed by position.
So simply remove sCaption, sFontName, and lSize and Excel call should return exact same as Access call, namely 2670. Explicitly defining all non-optional arguments is not needed.
Public Function TwipsFromFontXLS() As Double
Dim obj As Object
Set obj = CreateObject("Access.Application")
With obj
.OpenCurrentDatabase "C:\MyPath\Jeremy.accdb"
TwipsFromFontXLS = .Run("TwipsFromFont", "Hello World!", "Arial Black", 20)
.Quit
End With
Set obj = Nothing
End Function
In fact, had OP including Option Explicit at top of module, these named arguments should have raised a runtime even compiled error as being undefined!

VBA and GetRawInputDeviceList

I am working in Access 2013 and try to get GetRawInputDeviceList, GetRawInputDeviceInfo, RegisterRawInputDevices and GetRawInputData equivalents for VBA with no success. I have also searched in vain for a procedure, function or module to get a list of connected HID devices to a computer to pick out a barcode scanner. This is the beginning of the third week so I am on my knees begging for assistance. Do any of you all have a module you're willing to share, a link to a website where this is dealt with? Any help is greatly appreciated.
Using the GetRawInputDeviceList API from VBA would be pretty tricky because of the pRawInputDeviceList parameter. Unless you're willing to jump through a ton of hoops to manage your own memory and manually handle the resulting array of RAWINPUTDEVICELIST in raw memory, you'll be better off coming at this from another direction.
Most barcode scanners I've dealt with present themselves to Windows as a keyboard. One possible solution would be to use a WMI query to enumerate attached Win32_Keyboard devices:
Private Sub ShowKeyboardInfo()
Dim WmiServer As Object
Dim ResultSet As Object
Dim Keyboard As Object
Dim Query As String
Query = "SELECT * From Win32_Keyboard"
Set WmiServer = GetObject("winmgmts:root/CIMV2")
Set ResultSet = WmiServer.ExecQuery(Query)
For Each Keyboard In ResultSet
Debug.Print Keyboard.Name & vbTab & _
Keyboard.Description & vbTab & _
Keyboard.DeviceID & vbTab & _
Keyboard.Status
Next Keyboard
End Sub
Note: If it doesn't turn up there, you can enumerate all of the USB devices by querying CIM_USBDevice: Query = "SELECT * From Win32_Keyboard"
EDIT: Per the comments, the above code won't return the handle needed to register to receive raw input events. This should get you started though - the RegisterRawInputDevices and GetRawInputData aspects are beyond the scope of what will easily go in an answer. Take a hack at it, and if you run into any problems post your code in another question.
Declarations:
Private Type RawInputDeviceList
hDevice As Long
dwType As Long
End Type
Private Type RidKeyboardInfo
cbSize As Long
dwType As Long
dwKeyboardMode As Long
dwNumberOfFunctionKeys As Long
dwNumberOfIndicators As Long
dwNumberOfKeysTotal As Long
End Type
Private Enum DeviceType
TypeMouse = 0
TypeKeyboard = 1
TypeHID = 2
End Enum
Private Enum DeviceCommand
DeviceName = &H20000007
DeviceInfo = &H2000000B
PreParseData = &H20000005
End Enum
Private Declare Function GetRawInputDeviceList Lib "user32" ( _
ByVal pRawInputDeviceList As Long, _
ByRef puiNumDevices As Long, _
ByVal cbSize As Long) As Long
Private Declare Function GetRawInputDeviceInfo Lib "user32" Alias "GetRawInputDeviceInfoW" ( _
ByVal hDevice As Long, _
ByVal uiCommand As Long, _
ByVal pData As Long, _
ByRef pcbSize As Long) As Long
Private Declare Function GetLastError Lib "kernel32" () As Long
Sample of retrieving device names with GetRawInputDeviceInfo:
Private Sub SampleCode()
Dim devices() As RawInputDeviceList
devices = GetRawInputDevices
Dim i As Long
For i = 0 To UBound(devices)
'Inspect the type - only looking for a keyboard.
If devices(i).dwType = TypeKeyboard Then
Dim buffer As String
Dim size As Long
'First call with a null pointer returns the string length in size.
If GetRawInputDeviceInfo(devices(i).hDevice, DeviceName, 0&, size) = -1 Then
Debug.Print "GetRawInputDeviceInfo error " & GetLastError()
Else
'Size the string buffer.
buffer = String(size, Chr$(0))
'The second call copies the name into the passed buffer.
If GetRawInputDeviceInfo(devices(i).hDevice, DeviceName, StrPtr(buffer), size) = -1 Then
Debug.Print "GetRawInputDeviceInfo error " & GetLastError()
Else
Debug.Print buffer
End If
End If
End If
Next i
End Sub
Private Function GetRawInputDevices() As RawInputDeviceList()
Dim devs As Long
Dim output() As RawInputDeviceList
'First call with a null pointer returns the number of devices in devs
If GetRawInputDeviceList(0&, devs, LenB(output(0))) = -1 Then
Debug.Print "GetRawInputDeviceList error " & GetLastError()
Else
'Size the output array.
ReDim output(devs - 1)
'Second call actually fills the array.
If GetRawInputDeviceList(VarPtr(output(0)), devs, LenB(output(0))) = -1 Then
Debug.Print "GetRawInputDeviceList error " & GetLastError()
Else
GetRawInputDevices = output
End If
End If
End Function
Sorry about the side scrolling.