dllCall to call getkeyboardlayout

Started by archimede, June 10, 2022, 04:07:06 PM

Previous topic - Next topic

archimede

Hallo.
I am trying to get keyboard name.
I use this, but it seem no work:

#DefineFunction iStructAlignedWidth ( iStructWidth, iWILByteness )
   Return iStructWidth + ( iWILByteness - iStructWidth mod iWILByteness ) mod iWILByteness
#EndFunction

WILBYTENESS = ( 2 ** ( WinMetrics ( -2 ) + 3 )) / 8
DWORDDATA = 4                                                        ; Num. Bytes DWORD
WPARAMDATA32 = 4                                                     ; Num. Bytes WPARAM in WinBatch 32 bit
WPARAMDATA64 = 8                                                     ; Num. Bytes WPARAM in WinBatch 64 bit
HANDLE32 = 4                                                         ; Num. Bytes HANDLE in WinBatch 32 bit
HANDLE64 = 8                                                         ; Num. Bytes HANDLE in WinBatch 64 bit
If WILBYTENESS == 4
   HANDLE = HANDLE32
   WPARAMDATA = WPARAMDATA32

Else
   HANDLE = HANDLE64
   WPARAMDATA = WPARAMDATA64

EndIf

iRAWINPUTHEADERwidth = DWORDDATA + DWORDDATA + HANDLE + WPARAMDATA
iRAWINPUTHEADERwidth = iStructAlignedWidth ( iRAWINPUTHEADERwidth, WILBYTENESS )

hpRawInputDevice = BinaryAlloc ( iRAWINPUTHEADERwidth )
hpcbSize = BinaryAlloc ( UINTDATA )
BinaryPoke4 ( hpcbSize, 0, iRAWINPUTHEADERwidth )
xuiCommand = xHex ( "20000007" )
;xuiCommand = 20000007
iHIDDetected = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", lpbinary:hpRawInputDevice, word:xuiCommand, lpnull, lpbinary:hpcbSize )

It return -1
I think to use well the pointers...
I'm no sure about WPARAMDATA
Can you help me to understand the problem?
Thank you very much.

ChuckC

This parameter is being passed incorrectly:  lpbinary:hpRawInputDevice

This buffer isn't set up properly and isn't populated with a valid value:  hpRawInputDevice.  This is a handle to a device, and the value will have been obtained by iterating thru the output buffer that you got from calling GetRawInputDeviceList().

This parameter is being passed incorrectly:  word:xuiCommand.  The value is a UINT [4 byte value], not a WORD [2 byte value].  Reread the documentation for the function.

This buffer isn't set up properly and isn't populated with a valid value:  hpcbSize.  Again, you are going to call GetRawInputDeviceInfo[A|W] twice... once with pData being a null pointer and cbSize being zero.  Provided that the first call succeeds, cbSize will be populated with the number of bytes [not characters] that pData must have allocated to hold the string.  For the second call, you allocate a buffer for pData that contains at least the minimum number of bytes and then make the function call.  If it succeeds, then you'll perform some kind of BinarPeek*() function call in WinBatch to read a string from the buffer.  Make sure that if you call GetRawInputDeviceInfoA() that you peek an ANSI string from the pData buffer, and if you call GetRawInputDeviceInfoW(), then peek a Unicode [wide character] string from the pData buffer.


The output buffer, "pData", isn't a structure in this case.  It's nothing more than a buffer used to receive a string that is the device name.

Reread the function documentation.  You aren't understanding the meaning of the parameters and how to properly set them up with initial values and pass them to the function.


Also, you are ignoring what I told you about getting started with C/C++ development using these function.  Directly within WinBatch, you will eventually find yourself able to enumerate the raw input devices, which you have apparently done successfully, followed eventually by obtaining information [e.g. the device name] for each raw input device that is of interest.  After that you are going to run head first into a brick wall and you won't get around/over/under/thru it without resorting to writing C/C++ code, most likely in a WinBatch Extender library.  That will happen when you attempt to register for the receipt of raw input messages and find that the only way to do so is to write C/C++ code that creates an invisible/hidden window with its own private incoming message loop to receive those messages.  Do not dismiss this as a "maybe" kind of thing... it's an absolute necessity.

archimede

Hallo.
You have reason, I no red well documentation.
I re-read it documentation many times, and your suggestions many times.
First thing: on WinBatch documentation named "How To Call Windows API Functions" is reported UINT C++ type must translated to WORD WinBatch type ( I red it before, after that I red your suggestion and I followed it ).
I made it:
hDevice = BinaryAlloc ( HANDLE )                                                                        ; HANDLE  == 4
BinaryPoke4 ( hDevice, 0, BinaryPeek4 ( hpRawInputDeviceList, 32 ))               ; hpRawInputDeviceList is the buffer generated by GetRawInputDeviceList(...), the offset is calculated before to point an ihDevice of a keyboard
iTest = BinaryPeek4 ( hDevice, 0 )                                                                       ; this test show me the valure is correctly red from the buffer list and correctly write on the hDevice buffer
hcbSize = BinaryAlloc ( UINTDATA )                                                                      ; UINTDATA  == 4
BinaryPoke4 ( hcbSize, 0, 0 )                                                                               ; 0 because pData is a null pointer
iTest = BinaryPeek4 ( hcbSize, 0 )                                                                       ; This show me the data is OK
xuiCommand = Int ( xHex ( "20000007" ))                                                          ; I hope, but I'm no sure, the data is OK: uiCommand 0x20000007 I think must be translated to INT value
ipDataBytes = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", lpbinary:hDevice, long:xuiCommand, lpnull, lpbinary:hcbSize )

The return value is -1: can you help me why?

ChuckC

Tony will have to provide some input about the WinBatch docs related to calling Win32 API functions and whether there may be an error in there.  In all cases, UINT is the same as DWORD on both 32-bit & 64-bit Windows.

You don't say if you're using 32-bit WinBatch or 64-bit WinBatch.  Pick one and consistently use it for your testing.  Do not switch unless there is some overriding highly logical reason to do so.  I would recommend always using 64-bit WinBatch.

On 64-bit Windows, a HANDLE is basically the same as a pointer, and is always 8 bytes, not 4 bytes.

Unless you were using 32-bit WinBatch the following is invalid:

hDevice = BinaryAlloc ( HANDLE )
BinaryPoke4 ( hDevice, 0, BinaryPeek4 ( hpRawInputDeviceList, 32 ))

The same applies to all instances of BinaryPeek4() and BinaryPoke4() as it relates to HANDLE values in a 64-bit program.


archimede

Hallo.
I think I found the problem.
Now function returns 0 and pcbSize contain 122.
I think the first argument is no an handhe but an integer that contain an handle; then I wrote this:
hDeviceNum = ItemExtract ( 1, sKbdDetectedItem, @TAB )                                    ; ItemList with many handles previously detected by GetRawInputDeviceList(...)
ipDataBytes = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", long:hDeviceNum, long:xuiCommand, lpnull, lpbinary:hcbSize )

if it is right I go ahead...

archimede

Normally I am on WIL 32 bit because the debugger works on 32 bit.
When I compile the program works on 64 bit.
Normally I am testing, but after test it must work on 32 and 64 bit.

archimede

The program knows everytime if working WIL is 32 or 64 bit because the first program line is:
WILBYTENESS = ( 2 ** ( WinMetrics ( -2 ) + 3 )) / 8
that generates 4 for WIL 32 bit and 8 for WIL 64 bit

ChuckC

long:hDeviceNum will work OK for 32-bit WinBatch.  It will very likely fail on 64-bit WinBatch since HANDLE would be 8 bytes, not 4.  Make sure that whatever populates sKbdDetectedItem with HANDLE values is using the correct BinaryPeek4() / BinaryPeek8() function.  There may be cases were incorrectly peeking only the lower 4 bytes of an 8 byte HANDLE from a buffer value will work on 64-bit WinBatch *IF* the upper 4 bytes just happen to be zero.

cbSize having a value of 122 sounds like it might be a reasonable value.  use one of the Peek*() functions to read an ANSI string from the binary buffer and then examine the string value.


archimede

Hallo.
The Device interface name I found is:
\\?\HID#VID_
or very similar for other keyboards.
I made:
iInfoLen = BinaryPeek4 ( hcbSize, 0 )
hData = BinaryAlloc ( iInfoLen )
iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", long:hDeviceNum, long:xuiCommand, lpbinary:hData, lpbinary:hcbSize )
sTest = BinaryPeekStr ( hData, 0, iInfoLen )
The Device interface name is contained in sTest variable.
Do you think it is exactly?

archimede

long:hDeviceNum will work OK for 32-bit WinBatch
If I have 64-bit WinBatch how must I define and declare variables/memory?
I tried
hDeviceNum = BinaryAlloc ( HANDLE )
and declare
iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", lpbinary:hDeviceNum, long:xuiCommand, lpnull, lpbinary:hcbSize )
but it no works...

ChuckC

That device name looks like it's been truncated a bit.  If the required buffer was 122 bytes long, then the string most likely should have been 121 or 122 characters long depending on whether a trailing NULL byte is present to terminate the string.

The second call to GetRawInputDeviceInfoA() results in the content of the buffer hcbSize being updated, so the value of iInfoLen needs to be updated by calling BinaryPeek4() again, and the new value is then used to in the call to BinaryPeekStr().

ChuckC

Quote from: archimede on June 21, 2022, 09:09:18 AM
long:hDeviceNum will work OK for 32-bit WinBatch
If I have 64-bit WinBatch how must I define and declare variables/memory?
I tried
hDeviceNum = BinaryAlloc ( HANDLE )
and declare
iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", lpbinary:hDeviceNum, long:xuiCommand, lpnull, lpbinary:hcbSize )
but it no works...


no no no... take what you were doing for 32-bit:

Quote
iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", long:hDeviceNum, long:xuiCommand, lpbinary:hData, lpbinary:hcbSize )

and adjust the data sizes for 64-bit Windows:

hDeviceNum is a HANDLE value as defined for 64-bit WinBatch/Windows, meaning it is 8 bytes, not 4, so ensure that you obtained it previously from a structure field in a binary buffer using BinaryPeek8().

then use "int_ptr:hDeviceNum" instead of "long:hDeviceNum".

archimede

ok I will try it.
Now I obtained a very strange result:
After the call
iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceInfoA", long:hDeviceNum, long:xuiCommand, lpbinary:hData, lpbinary:hcbSize )
if I put
sTest = BinaryPeekStr ( hData, 0, iInfoLen )
the sTest == ""
if I put
sTest = BinaryPeek4 ( hData, 0 )
sTest = BinaryPeekStr ( hData, 0, iInfoLen )
the sTest == "\\?\"
if I put
sTest = BinaryPeek4 ( hData, 0 )
sTest = BinaryPeek4 ( hData, 4 )
sTest = BinaryPeekStr ( hData, 0, iInfoLen )
the sTest == "\\?\HID#"
if I put
sTest = BinaryPeek4 ( hData, 0 )
sTest = BinaryPeek4 ( hData, 4 )
sTest = BinaryPeek4 ( hData, 8 )
sTest = BinaryPeekStr ( hData, 0, iInfoLen )
the sTest == "\\?\HID#VID_"
Do you know how is it possible?

ChuckC

Looking thru the function documentation, it's the return value of the function that specifies how many *characters* [not bytes] were returned in the buffer.  This can differ from the value placed in cbSize that represented the minimum number of characters the buffer must hold, which would typically be the length of the string plus one additional null terminator character.

Looking at your code, it's the value in iReturnVal, not iInfoLen, which should be used with BinaryPeekStr().


On my system, there are 3 keyboard devices available for raw input, and the names returned for them are as follows:

"\\?\HID#VID_046D&PID_C52B&MI_00#8&25c01294&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"
"\\?\ACPI#DLLK098F#4&21948c4c&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"
"\\?\HID#ConvertedDevice&Col01#5&28b906d8&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"


archimede

The function return value for me is half than length specified by the argument call.
My surprise is BinaryPeekStr(...) return "", but if BEFORE that I use BinaryPeek(...), the function BinaryPeekStr(...) works...
I controlled it well.
Can you tell me why?

ChuckC

You haven't posted enough information about the specific input parameter values, output parameter values and function return values, nor about your specific calls to the Binary*() functions to have any idea what you've done wrong.  I am, however, certain that the problem is happening some place between the keyboard and the chair.

td

Chuck, you have spent more time (and shown more patience) on this forum member's problem than is reasonable to expect.  It is probably time to end the topic.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

ChuckC

Noted.  I had a blue & gold macaw for 14 years... attempts at modifying her behavior taught me the lesson of futility :o  Perhaps the quote from "Joshua" in "War Games" applies... "A strange game.  The only winning move is not to play..."


archimede

Hallo.
I'm sorry.
Please no ban me :-(
I'm sorry if I made no good thing.
Please tell me what you no like, before ban me.

archimede

For ChuckC:
I made the working utility to detect all Human Interface Devices.
If you are interested You can write me on this e-mail (because, elsewhere, I hope to be banned):
archimede00@bluewin.ch

For td:
With the ChuckC help I made a working HID detect.
Can I continue to go ahead with the help to detect keys on on every keyboard?
Need I tu buy newly WinBatch?
Thank you very much and sorry if I made some bad thing.

ChuckC

The problem is that I've explained that what you are trying to achieve is absolutely going to require that you write some C/C++ code for a critical part of the functionality that is beyond the capabilities of WinBatch, and you have stated that you will not or cannot learn to write code in C/C++.  That pretty much makes any and all time spent on this issue a waste of time.

To be more specific:

WinBatch will allow you to enumerate HID devices and obtain their names and their handle values.

All of that is useless if you can't register to receive raw input messages and then actually receive them.  That is the part where WinBatch is going to get into trouble.  Attempting to cobble together code relying on DllCall() will result in something almost unreadable, very difficult to understand and nearly impossible to troubleshoot if/when it doesn't work.  There's a high probability that the creation of a hidden message-only window and its associated message processing loop to receive & process raw input messages simply cannot be achieved with DllCall().

To make an analogy, you are trying to build an igloo out of mashed potatoes... it's a messy process, the results won't be what you wanted, it's going to take a long time and you probably won't be able to get it done at all.  Tony, myself and others have advised you that you've got a flawed approach to solving the problem and have tried to explain what you need to do and why you need to do it, but you keep on ignoring that advice.

It is, however, possible to write such code in C/C++ that is compact, clean, easily readable and performs well in a reliable way.  If you make efficient use of Google, it's fairly easy to find functional examples of handling raw input in C/C++ code.  The most direct route to success that any of the advanced users of WinBatch on this forum would take is to make that code work in a WinBatch extender and then write simpler WinBatch script code to consume the functionality provided by the extender.  The extender takes care of the hard parts that WinBatch isn't suited for and then makes the results available for use in a script.  From there, the script can use the functionality that WinBatch excels at like automating the control of other applications on the desktop.

Here's a link to a discussion that covers the core piece of functionality that you need:

https://stackoverflow.com/questions/62088236/how-do-i-register-an-invisible-window-class-in-a-win32-console-application

If understanding that discussion is beyond your ability, then you either need to do some independent study and learn to understand it, or you simply need to give up on this task.  I've read it a couple of times, and it provides an example that can readily be adapted to what you want to do.  All you'd need to do is make it work in a console application and then adapt that code to work inside a WinBatch extender.

None of this assessment is personal in nature.  Nobody wants to ban you or not answer questions you have about WinBatch related to other scripting projects.  It's simply a matter of looking at the stated goal, the choice of technology and the level of knowledge, experience and skill required to achieve the goal.  If you aren't willing to do the things that were recommended then there's no incentive to continue to provide any additional advice or assistance, and that leads to this raw input discussion becoming a dead topic that nobody feels inclined to continue to discuss.


archimede

Thank you very much.

I think you have reason.
Sorry if I answer heare, but I no found any way to make a personal message.
I see the WinBatch language is not very good to launch API functions; this is the first time I need to solve a similar problem, and I think I will no have  any other similar problem.
I can no learn a language everytime I have a problem no suited to the language I know; then I must know a language very versatile.
To learn a language is no simple: it is necessary many engagement and many time: I used many engagement and many time to learn WinBatch and other ( AutoHotKey, that seem similar to WinBatch, but it allow the automation of existing program that WinBatch can no make ).
With your help I made a WinBatch utility to list all HID devices, divided by type, everyone with the handle.
Now I need only to detect what key is down on every specific HID ( keyboard ).
I think you have absolutely reason, but I think, with some small forcing, is possible to obtain the result: really, I can no learn a language to every different type of problem... :-(
Obviously, I red your link and, for what I understand, I think it is very interesting; but, after that, there is the problem to interface that solution ( on a different language ) with other WinBatch programs...
For example, with WinBatch I made a command prompt program executor function very much powerful than all Run (...) WinBatch functions, and I used it into many WinBatch programs I made...
If you like, I can send it here.
I need a similar thing, but for HID detect by API functions, to use it into one or much WinBatch programs.
I know it is messed up, no ideal; but from the function extern, it will works and it can be called from other WinBatch functions...
The list of all HID with name, type and handle is very good to now; it is 50% of the work made.

Thank you very much for patience.

ChuckC

Quote from: archimede on June 25, 2022, 06:51:20 AM

...

1)  To learn a language is no simple: it is necessary many engagement and many time

...

2)  Now I need only to detect what key is down on every specific HID ( keyboard ).

...

3)  I think you have absolutely reason, but I think, with some small forcing, is possible to obtain the result: really, I can no learn a language to every different type of problem... :-(



For #1:  Yes, you are correct, it takes time and effort to learn new things.

For #2:  Yes, you have stated that as your goal from the very beginning.  The word "only" is only 4 letters in length.  The C/C++ code you are going to write is going to be several 1000's of lines in length.  Just because the word used to describe the last little bit of functionality is a small word has absolutely no relationship to the size of the effort involved in implementing the desired functionality.

For #3:  No amount of effort involved in "some small forcing, is possible to obtain result" is going to make it work like you want it to no matter how much you want it to.  You still refuse to accept the reality of the situation.  Detecting the key presses and determining which keyboard device they were generated by requires writing some C/C++ code.


archimede

:-)
It seem we are reciprocally understanging...
I searched the API function that seem to solve the problem:
GetRawInputData( ... )
If it solve the problem, at now it seem the only missing call... or not?

ChuckC

No, GetRawInputData() is only one of the API functions that needs to be called.

What you are failing to understand is when it needs to be called, and what input parameters must be passed to it.  Equally important is where/how those input parameters are obtained.

You don't just directly poll the known keyboard devices asking if they have input for you.  Windows itself has direct control over the keyboard hardware and user-mode program code has zero direct access to the keyboard hardware.  Instead, the device driver for the keyboard detects the key presses and then sends a message higher up in Windows to be handled elsewhere.  Programs interested in receiving raw input have to register for raw input and then listen for raw input messages sent to them from Windows itself.

The part you are still failing to understand is the message processing loop that is associated with a window and how that works.  When registering for raw input messages, the function that you call requires a window handle as an input parameter.  You are responsible for writing that program code that implements that window, including the program code for the message processing loop.  That is the part that is going to require C/C++ code.  I already posted a link to a discussion showing an example of how to do that.  It's the discussion about creating a hidden window from a console application.  I'm not going to explain that any further.  If you can't understand it, then what you want to do is simply beyond your skill.


archimede

:-)
I no studied Windows theory :-(
I am an electronic engineer, no an informatic engineer.
You are very interesting for me; your help, to you, was be very useful.
In the RAWINPUTHEADER structure there is a param named
WPARAM wParam
what is that?
It seem important to set 0 or 1 but why? What is the meaning of that?
How I can to know what is the last keyboard that worked on the system?
:-)

JTaylor

You could engage in the time-honored and often-used tradition of paying someone to do what you cannot and/or do not wish to learn to do.

Just a thought.

Jim

archimede

JTaylor.
I hope my English is good enough to undertand what you wrote me...
I think you would have right if I would make a commercial thing...
:-)

archimede

Hallo.
I attach here the HID detect utility.
It is experimental but is completely working.
I'm sorry for Italian comments; if you like some explanation, ask me.
It works well on 32 and 64 bit WIL.

archimede

Hallo.
I see the function
WaitForKeyEx (...)
is very near what I need.

First all:
FOR MY OPINION that is no good described, because it no explain that function is "no sichronized", it detect the keyboard key at hardware level, NO THE INPUT KEY.

After that:
at this level I think is no impossible to detect the name ok the keyboard with the pressed key... or not?

ChuckC

It is not possible to do what you want to do with the functionality that's built into WinBatch.

This has been explained to you more times that I care to count.

If you are unwilling/unable to write an extender in C/C++, and are not willing to pay somebody else to develop it for you, then you'll have to give up on you project as it will be unachievable as it has become an exercise in futility.

Please, stop asking the same question over and over again expecting to get a different answer.

JTaylor

Should you decide to take the plunge and solve your problem, here is a Visual Studio solution to get started.   I think it has everything you need to do what you want.   Just need to make use of what is provided to accomplish your goal.

I have no answers to questions so please do not expect any.   While I tweaked things a bit to provide a display when pressing a key, the work is someone else's. 

I know what it is like to accomplish something like this without knowing C/C++ so thought I would help a bit.   All the Extenders I wrote were mainly because of persistence and not from knowing C.  I took a class back in the 80's and that was my only knowledge when I began.   If you need/want this bad enough you will do what is needed to make it happen.   Just like everything else in life :)

Jim