dllCall to call getkeyboardlayout

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

Previous topic - Next topic

archimede

I am learning links you wrote me.
About your first question:
I would like pass the first argument like NULL, then BinaryAlloc (0) I think can define a null value: how I must define a NULL value to pass like argument to an API function?

ChuckC

Read the help topic for the DllCall() function in the Windows Interface Language reference documentation.  You will find an explicit statement made there regarding how to pass a "pointer to null", sometimes referred to as "long pointer to null" or "lpnull".

And, again, the function you chose isn't a place to start learning how to use DllCall().  Think of it this way... Compare your effort to that required to build a house.  The function you are starting with is akin to putting plywood down on the substructure of the roofing trusses before putting on the shingles.  The problem is, you haven't built the portion of the house that the roof is supported by.  Until you build the foundation and the ground floor of the house, it is pointless to attempt to put on portions of the roof.

Despite the fact that I keep pointing you at a very specific set of functions to utilize, you insist on going off in random directions and completely disregarding the instruction you've been provided with.


archimede

Hallo.
I am following your suggestion.
I am studying all links you posted me.
I decided to change the Dll function to call to try: now I use GetRawInputDeviceList(...) with the simplest call (with the NULL pointer for the first argument).
I made this test:
hBuffer = BinaryAlloc ( 2 )
sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, long:hBuffer, word:0 )
BinaryFree ( hBuffer )
The first argument lpnull is necessary to obtain the number of connected keyboards on the pointed var in the second argument
The second argument must be a pointer to a UINT, 2 bytes; then I use BinaryAlloc ( 2 ) to allocate memory for the pointer t a UINT value; then I assign that value to the second argument, lpbinary type because it is allocated by BinaryAlloc(...)
The third argument is 0 because it define The size of a RAWINPUTDEVICELIST structure, in bytes; that dimension is 0 because the pointer to it (first argument) is lpnull.
At the end the function call returns -1, error: can you help me to find the error?
Thank you very much

archimede

Sorry.
The second line is:
sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, lpbinary:hBuffer, word:0 )
with lpbinary type for the second agument

ChuckC

Here's another link to multiple C++ examples making use of GetRawInputDeviceList():

https://cpp.hotexamples.com/examples/-/-/GetRawInputDeviceList/cpp-getrawinputdevicelist-function-examples.html


Passing lpnull for the 1st parameter, pRawInputDeviceList, is correct when calling the function to find out how many raw input devices exist and for the purpose of determining how large of a [binary] buffer to allocate for the second call to the function to actually retrieve the list of raw input devices.

Look at the function documentation again and ask yourself how many bytes are used to represent a UINT.

Look at the function documentation again and ask yourself how many bytes represent a single instance of the RAWINPUTDEVICELIST structure.  Look at the example code in the function documentation and figure out what the "sizeof()" operator does.  Find the definition of RAWINPUTDEVICELIST and add up how many bytes it takes to represent all the fields in the structure.

stanl

Chuck has been very patient but you seem to waffle between listing the keyboard devices and detecting keys and assigning to a window. I think WMI can list your devices. I posted a reference to an app [WMIGen] that might assist you with your list if you need it and then you can concentrate on the keys.

ChuckC

WMI certainly can assist with identifying device names.  One of the problems, though, is that the handling of raw input has no connection to the actual device names... it's all achieved via handles to devices.  Part of what I'm trying to get firmly understood by anybody interested in the raw input issue is that the following must happen:

1)  All raw input devices must be enumerated, and what is returned by the API function is device handles and other meta data associated with each device.

2)  The enumerated results must then be analyzed to identify which handles are associated with keyboard devices vs. mouse devices vs. other types of HID devices that are neither keyboard nor mouse.

3)  The keyboard devices must then have their handles passed to an API function that registers the process to receive raw input messages from those devices.

4)  The program's message loop then needs to specifically detect when a WM_INPUT message is received that contains a special flag indicating that raw input messages follow it in the message queue.

5)  Another API function must then be called to receive the raw in put messages from the message queue.

6)  Multiple API functions then need to be called to decode the raw input messages to identify the key that was pressed which device handle it is associated with based on the particular keyboard device that generated the raw input.

7)  Finally, the keystroke & source keyboard device handle can be used to determine which target application a simulated keystroke will be sent to.


That is all non-trivial but doable, at least in a C/C++ application.

Perhaps Tony could fill in some detail about step #4.  If WinBatch lacks a method for allowing a script to have a callback invoked for specific messages being received by GetMessage(), then this entire exercise is pointless and irrelevant in terms of a solution that is based 100% on WinBatch.

td

It is possible to subclass a WinBatch window or alternatively, set up a window procedure hook (not sure if Windows will still let you set a hook because of all the security "enhancements".) Both techniques would require using the DllCallback function. However, the chances of getting that done correctly and reliably are in the winning-the-lottery area of probability for the average WinBatch user. That is why I mentioned using an extender many posts ago.

Chuck has done all the heavy lifting by laying out the equivalent of a design and implementation specifications. It is just a matter of someone wanting this bit of functionality enough to wiring it all together.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

ChuckC

I agree, an extender is likely to be the best option to do the really dirty work in terms of capturing the raw input provided that C/C++ code in the extender can inform WinBatch that it wants to participate in the message processing.  From there, the extender could simply buffer the parsed raw input and return that via extender function calls made by the script via polling or possibly via some kind of event trigger where the script waits [with a timeout] on the event looking for non-timeout results indicating that extender has data for it.

It looks like registration for receiving raw input may also allow a process to get those messages without being in the foreground and having a window displayed that has focus.  Of course that would need to be tested, but if it works that way, it would allow for some interesting flexibility.

The last that i'm going to post [or re-post] on the subject are the following links which detail the core concepts related to receiving & processing raw input along with various code examples demonstrating the techniques:

https://docs.microsoft.com/en-us/windows/win32/inputdev/about-raw-input

https://docs.microsoft.com/en-us/windows/win32/inputdev/using-raw-input

https://cpp.hotexamples.com/examples/-/-/GetRawInputDeviceList/cpp-getrawinputdevicelist-function-examples.html

It doesn't get any more clear than what those pages present in the way of technical reference information, tutorials and examples.  No amount of spoon-feeding will do more than what those links provide, and if anybody won't or can't successfully utilize that information, then the task of receiving & processing raw keyboard input is simply going to beyond their skill.


stanl

Quote from: td on June 17, 2022, 08:00:13 AM
It is just a matter of someone wanting this bit of functionality enough to wiring it all together.


Which begs the question of the practical application for the Op's request.

       
  • 1 user, multiple keyboards - each assigned to a window
  • multiple users - each with keyboard, i.e. a video game
  • multiple users - each with keyboard, i.e. users sending input to multiple stock exchanges
all of which I suppose can be done, but seem really outside the scope of wanting to control in WB. Or maybe the goal is just to discover a WB way to handle raw input on multiple keyboards... then configure practical application -  cart/horse

archimede

My case is the first:
many keyboards everyone connected to a different window.
Every keyboard is a remote control visible like a keyboard in the system.
Every time I press a key on a remote I want to send the equivalent character to a specific window.
Another way (I think) is to convert windows 7 to concurrent mutiuser, but I think it is very hard...

ChuckC

Sending simulated keystrokes does not work in a "locked" or "disconnected" Terminal Services session.  That precludes the use of the SendKey*() / SendMenu*() / Mouse*() functions under those conditions.

archimede

I tried this:
hBuffer = BinaryAlloc ( 2 )
sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, lpbinary:hBuffer, word:8 )
iTest = BinaryPeek2 ( hBuffer, 0 )
BinaryFree ( hBuffer )
It seem work because the return value sTest is 0 (instead -1); the iTest value is 10; if I unconnect a keyboard and retry, the iTest value is 7, then it seem work
The last argument I calculate 8 because the RAWINPUTDEVICELIST structure contain 2 elements:
- HANDLE hDevice (4 bytes)
- DWORD  dwType ( 4 bytes)
Is it right?
Do you know if there are a simplest way to calculate it?

ChuckC

DWORD and UINT are both 4 byte values.

HANDLE varies based on the architecture of the process that is executing.  For a 32-bit process, it is 4 bytes, while for a 64-bit process it is 8 bytes.  Depending on whether you are running a 32-bit or 64-bit build of WinBatch, your script code will have to adjust accordingly.

td

From the help file: DllCall supports a "long_ptr" integer value guaranteed to be the bit size of a memory address. That is, a 32-bit integer value when used with 32-bit WinBatch and a 64-bit value when used with 64-bit WinBatch.

This is the best choice when passing a "HANDLE" and several other data types that vary with the process's bitness.

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

ChuckC

Good point.  For passing a handle value as an individual parameter, that would be preferred way to do it.

When it comes to computing the site of a structure that contains a field of type HANDLE, would it be best just to test the "bitness" of WinBatch and add 4 or 8 to the structure size that's being computed?  I wasn't sure where "long_ptr" modifier for DllCall() could be shoe-horned into the arithmetic for structure size calculation.



archimede

I made this test program that seem work on WIL 32 bit and WIL 64 bit:
WILBITNESS = 2 ** ( WinMetrics ( -2 ) + 3 )                          ; Num. Bit architettura questo programma
POINTERINT = 2                                                       ; Num. Bytes INT pointer
POINTERUINT = 2                                                      ; Num. Bytes UNSIGNED INT pointer
HANDLE32 = 4                                                         ; Num. Bytes HANDLE in WinBatch 32 bit
HANDLE64 = 8                                                         ; Num. Bytes HANDLE in WinBatch 64 bit
DWORD32 = 4                                                          ; Num. Bytes DWORD in WinBatch 32 bit
DWORD64 = 8                                                          ; Num. Bytes DWORD in WinBatch 64 bit
If WILBITNESS == 32                                                  ; Se questo programma è in architettura 32 bit

   HANDLE = HANDLE32                                                ; La lunghezza di un HANDLE è quella definita da HANDLE32
   DWORD = DWORD32                                                  ; La lunghezza di DWORD è quella definita da DWORD32

Else                                                                 ; Altrimenti significa che l'architettura di questo programma è
                                                                     ; a 64 bit, quindi:
   HANDLE = HANDLE64                                                ; La lunghezza di un HANDLE è quella definita da HANDLE64
   DWORD = DWORD64                                                  ; La lunghezza di DWORD è quella definita da DWORD64

EndIf                                                                ; If WILBITNESS == 32
hpuiNumDevicesBuffer = BinaryAlloc ( POINTERUINT )
iStructLen = HANDLE + DWORD
sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, lpbinary:hpuiNumDevicesBuffer, word:iStructLen )
iTest = BinaryPeek2 ( hpuiNumDevicesBuffer, 0 )
BinaryFree ( hpuiNumDevicesBuffer )
Message ( "Test architettura", "Numero tastiere: " : iTest : " Architettura " : WILBITNESS : " bit" : " Lunghezza handle: " : HANDLE )

ChuckC

The size of an integer doesn't change with the difference between 32-bit and 64-bit architecture.

The size of a pointer to *anything* changes with the difference between 32-bit and 64-bit architecture.  Pointers are 4 bytes on 32-bit and 8 bytes on 64-bit.

an INT, UINT and a DWORD are *always* 4 bytes.

a WORD is always 2 bytes.


archimede

Hallo.
You tell DWORD is ALWAYS 4 bytes.
When I compile 64 bit, if I put in the program
DWORD = DWORD64
the program works
if I put
DWORD = DWORD32
the program no works... why?

archimede

I tryed this calls:
hpuiNumDevice = BinaryAlloc ( POINTERUINT )
iStructLen = HANDLE + DWORD

sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, lpbinary:hpuiNumDevice, word:iStructLen )
iStructWidth = iStructLen * iStructCount
hpRawInputDeviceList = BinaryAlloc ( iStructWidth )
sTest = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpbinary:hpRawInputDeviceList, lpbinary:hpuiNumDevice, word:iStructWidth )

BinaryFree ( hpRawInputDeviceList )
BinaryFree ( hpuiNumDevice )

The first DllCall works, the second DllCall no works ( return -1 ): can you help me to understand why?
Thank you very much

ChuckC

Use the exact data types and their architecture-specific sizes *EXACTLY* as they are defined in the function's signature as it is declared in the documentation.  If you deviate from doing so in any way, the function will fail.

This code is wrong:

iStructWidth = iStructLen * iStructCount
hpRawInputDeviceList = BinaryAlloc ( iStructWidth )

"iStructCount" is undefined and any calculations based on it will produce wrong results.


You are still not correctly understanding what you are doing with allocating a buffer and then passing the pointer to the buffer as a parameter to the function.  The function is going to write some data into that buffer.  There are *2* parameters that you are going to pass that are pointers to buffers.  One is the buffer that's going to receive one or more RAWINPUTDEVICELIST structures.  At the start you don't know how many of those structures you are going to receive, so you have to call the function twice.  The first call where you pass "lpnull" for the 1st parameter tells the function that you want it to tell you how many structures it will return.  The number of structures gets written so a UINT that is passed by address [e.g. Pointer to UINT] in the 2nd parameter.  Since you can't pass arbitrary numeric variables by a pointer in WinBatch, you have to allocate a binary buffer that is the size of a UINT [e.g. 4 bytes, same as a DWORD] and pass the buffer with "lpbinary:".  After the first call, provided that the function's return value indicates success, you read the 4-byte unsigned integer [UINT] value *from* that buffer and use it to figure out how much memory to allocate for the binary buffer that you will pass as the 1st parameter the second time you call the function.

UINT GetRawInputDeviceList(
  [out, optional] PRAWINPUTDEVICELIST pRawInputDeviceList,  // lpnull on 1st call, lpbinary:<buffer-variable-name> on 2nd call
  [in, out]       PUINT               puiNumDevices,  // lpbinary:<buffer-for-uint-variable-name>
  [in]            UINT                cbSize  //  just a normal WinBatch numeric variable with an integer value in it.
);


Make note of the return value of the function as defined in the online docs:

Return value
Type: UINT

If the function is successful, the return value is the number of devices stored in the buffer pointed to by pRawInputDeviceList.

On any other error, the function returns (UINT) -1 and GetLastError returns the error indication.


The definition of RAWINPUTDEVICELIST from the online docs:

typedef struct tagRAWINPUTDEVICELIST {
  HANDLE hDevice;  //  4 bytes on 32-bit, 8 bytes on 64-bit
  DWORD  dwType;  //  always 4 bytes
} RAWINPUTDEVICELIST, *PRAWINPUTDEVICELIST;

sizeof(RAWINPUTDEVICELIST) is 8 on 32-bit and 12 on 64-bit.

"*RAWINPUTDEVICELIST" means "Pointer to RAWINPUTDEVICELIST", the size of which varies only with the 32-bit/64-bit architecture, not with the size of RAWINPUTDEVICELIST itself.



The example code from the online docs:

UINT nDevices;
PRAWINPUTDEVICELIST pRawInputDeviceList = NULL;
while (true) {
    if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { Error();}
    if (nDevices == 0) { break; }
    if ((pRawInputDeviceList = malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL) {Error();}
    nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST));
    if (nDevices == (UINT)-1) {
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { Error(); }
        // Devices were added.
        free(pRawInputDeviceList);
        continue;
    }
    break;
}
// do the job...
// after the job, free the RAWINPUTDEVICELIST
free(pRawInputDeviceList);


For now, ignore the fact that the function calls are made in a loop.  The usage of a looping construct is there to deal with the possibility that a device is added or removed between the 1st & 2nd function calls and allows the code to re-try without blowing up or returning inaccurate information.

The first call to the function is:

GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)

With DllCall(), you'll pass "lpnull" for the 1st parameter.

The 2nd parameter will be passed with "lpbinary:" and the buffer will be 4 bytes in size.

The 3rd parameter will be the integer value 8 or 12 depending on the architecture, and it can be a literal numeric value or a variable set to that value.

The return value of the function will be -1 if it fails on the 1st call; otherwise it is successful.

You will need to learn to use the BinaryPeek*() functions to read a 4 byte unsigned integer from the buffer that you passed in as the 2nd parameter.  That will get you the "nDevices" [e.g. "number of devices"] value that you multiply by the size of RAWINPUTDEVICELIST to determine what size binary buffer to allocate and pass as the 1st parameter the 2nd time you call the function.

The 2nd time the function is called, it looks like this [taken from example code above]:

nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST));

In this case, when you do the same with DllCall(), "pRawInputDeviceList" will be replaced with your 1st binary buffer passed via "lpbinary:<buffer-name>".  The 2nd & 3rd parameters remain the same.

Note that the return value of the function can have 2 meanings this time.  If it returns -1, then the function failed.  Otherwise, the return value is the # of devices returned in the buffer that was passed at the 1st parameter.


archimede

Hallo.
First all thank you very much for your help.
Second thing: I no understand a thing: how I must calculate the struct dimension?
This struct
typedef struct tagRAWINPUTDEVICELIST {
  HANDLE hDevice;
  DWORD  dwType;
} RAWINPUTDEVICELIST, *PRAWINPUTDEVICELIST;
it seem to contain 2 data:
- HANDLE hDevice
- DWORD dwType
but reading what you tell me it seem no like this...
This is my limit to no know c++.

Another thing: if WinBatch is "near" to C++, why no generate a conversion WinBatch -> C++ to compilate it ( really, no semicompilation ): I understant it would be an hard work, but I think it would be very very useful...
:-)

archimede

Sorry I was no enough clear.
The API function GetRawInputDeviceList ( ... )
requires, on the third argument, "The size of a RAWINPUTDEVICELIST structure, in bytes".
I think the struct dimension is the sum of all data dimension contents.
Then the struct
typedef struct tagRAWINPUTDEVICELIST {
  HANDLE hDevice;
  DWORD  dwType;
} RAWINPUTDEVICELIST, *PRAWINPUTDEVICELIST;
seem to contain 2 data:
- HANDLE hDevice
- DWORD dwType
total 12 bytes, like you tell.
with WinBatch 64 bitness,  if I put, on the third argument of GetRawInputDeviceList ( ... ), 12 it generates error; if I put 16 it works: why?
with WinBatch 32 bitness,  if I put, on the third argument of GetRawInputDeviceList ( ... ), 8 it works: why?

ChuckC

Yes, you finally figured out the process of computing the size of a structure.  It is nothing more than the sum of the memory sizes of all the fields in the structure.  Doing this in C++ is exactly the same as in C.

Regarding 16 bytes vs. 12 bytes for the size of the RAWINPUTDEVICELIST in a 64-bit program, you have likely encountered one of the "quirks" of how code is actually generated to run on 64-bit Windows vs. 32-bit Windows.  Specifically, note the types of the fields in the structure and the *order* in which they appear.  They are, respectively, a HANDLE field followed by a DWORD field.  On 64-bit architecture, the concept of "memory alignment" becomes more important since the "native word size" is 8 bytes rather than the 4 byte native word size for 32-bit Windows.  In practice, this means that the compiler will insert padding bytes into a structure to ensure that it has an overall size that is a multiple of 8 bytes.

The *best* way to figure this with 100% certainty is to actually install Visual Studio [Visual C/C++] and the Windows Platform SDK and then examine the header files where the functions and structures are defined.  There will very likely be some annotation in comments or usage of "#prgama" pre-processor directives that specify things like "packing size" or "alignment".  Once you learn to read that and understand it, you'll be doing less guess work.


td

Using the DllStructAlloc and related functions to allocate and access c-style structures takes care of memory alignment automagically. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

archimede

Hallo.
For td:
I tried this:
hStructHandle = DllStructAlloc ( "handle:hDevice dword:dwType" )
iStructWidth = IntControl ( 98, hStructHandle, 1, 0, 0 )
Message ( "Struct dimension", iStructWidth )
DllStructFree ( hStructHandle )
Exit
if executed on WinBatch 32 bit it give the message "8"
if executed on WinBatch 64 bit it give the message "12"
then it seem no magically aligned...

archimede

Hallo.
I found this function to calculate the right aligned struct dimension:
WILBYTENESS = ( 2 ** ( WinMetrics ( -2 ) + 3 )) / 8
DWORD = 4
If WILBYTENESS == 4

   HANDLE = HANDLE32

Else
   HANDLE = HANDLE64

EndIf
iStructWidth = HANDLE + DWORD
iStructWidth += ( WILBYTENESS - iStructWidth mod WILBYTENESS ) mod WILBYTENESS  ; Struct dimension with aligned data

It seem work.
I have some questions:
- in the 64 bitness WIL structure, every data must be aligned or only the structure must be aligned?
- in the 64 bitness WIL structure, the 2 bytes data must be aligned ( then every data is 8 bytes or multiple width )?
- in the 32 bitness WIL structure, the 2 bytes data must be aligned like 4 bytes data on the 64 bitness structure?

ChuckC

Where do you keep coming up with a data type that's 2 bytes in size?  There is no situation, ever, any where, where a HANDLE or a DWORD value would be 2 bytes in size.

"DWORD" stands for "Double WORD", where "WORD" is a 16-bit [2 byte] unsigned integer, and DWORD is twice as large as a WORD.


Again, regarding the actual size of RAWINPUTDEVICELIST that is required on 64-bit Windows, the absolute authoritative source would be to compile and execute some C++ code that has the following code:

auto x = sizeof(RAWINPUTDEVICELIST);

and then examine the value of "x" in the debugger or print it out to the console.


archimede

"a WORD is always 2 bytes"
you told before...
I think some structures can contain it: in that case how is the alignment? in the 32 bitness WIL structure is necessary, too?
Thank you very much

ChuckC

On Windows, regardless of whether or operating system or program is 32-bit or 64-bit, the following always apply:

The type macro "WORD" is always defined as a 2-byte [16-bit] integer.

The type macro "DWORD" is always defined as a 4-byte [32-bit] integer.


In terms of memory alignment, that will be based on the native "word size" of the CPU architecture and the size of the data fields in the structure.  Yes, just as a single DWORD field at the end of the RAWINPUTDEVICELIST requires an additional 4 bytes of padding space on 64-bit architecture, a similar thing happens on a 32-bit architecture where a single WORD at the end of a structure that has an overall apparent size that does not end on a 4-byte boundary and would require an additional 2 bytes of padding space.


From one of the C/C++ header files in the Windows Platform SDK:

#undef FAR
#undef  NEAR
#define FAR                 far
#define NEAR                near
#ifndef CONST
#define CONST               const
#endif

typedef unsigned long       DWORD;
typedef int                 BOOL;
typedef unsigned char       BYTE;
typedef unsigned short      WORD;
typedef float               FLOAT;
typedef FLOAT               *PFLOAT;
typedef BOOL near           *PBOOL;
typedef BOOL far            *LPBOOL;
typedef BYTE near           *PBYTE;
typedef BYTE far            *LPBYTE;
typedef int near            *PINT;
typedef int far             *LPINT;
typedef WORD near           *PWORD;
typedef WORD far            *LPWORD;
typedef long far            *LPLONG;
typedef DWORD near          *PDWORD;
typedef DWORD far           *LPDWORD;
typedef void far            *LPVOID;
typedef CONST void far      *LPCVOID;

typedef int                 INT;
typedef unsigned int        UINT;
typedef unsigned int        *PUINT;


Ignore "near" and "far"... they are macros defined to be nothing and represent ancient terms that were applicable way back on 16-bit Windows 3.1.


Yes, structures may be defined to have fields of type WORD.  However, in this case, you don't have a structure to work with that contains a field of type WORD.  Stop conflating and confusing things by introducing garbage that isn't pertinent or applicable to the API function & associated parameter data types.


Again, do the following:

Download and install Visual Studio 2022 Community Edition.  It's *FREE* and won't cost you anything but the disk space required on your computer and the time it takes to install it.

Create a new C++ Console application from the corresponding built-in project template.

Start experimenting with C++ code examples related to your task and make them work enough that you understand them well enough to convert them to WinBatch code using DllCall() and related functions.

Visual Studio has a feature called "Intellisense" that allows you to right-click on keyword like "DWORD" and have it automatically navigate to whatever source code module or header file contains the declaration/definition for the keyword.  This capability is invaluable when you are learning to use a portion of the Win32 API that you are not familiar with.  It will show you all kinds of important details that you need to know.  It removes the guess work from the process, and from what I've observed, you are making a lot of bad assumptions and making a lot of bad guesses as to what things mean.  I'm trying to show you how to find FACTS that will be useful to you.


td

Quote from: ChuckC on June 17, 2022, 02:39:55 PM
Good point.  For passing a handle value as an individual parameter, that would be preferred way to do it.

When it comes to computing the site of a structure that contains a field of type HANDLE, would it be best just to test the "bitness" of WinBatch and add 4 or 8 to the structure size that's being computed?  I wasn't sure where "long_ptr" modifier for DllCall() could be shoe-horned into the arithmetic for structure size calculation.

The DllStructAlloc function and friends are "bitness aware" so you can use types like HANDLE and long_ptr in structures without having to worry about the bitness of the process running the script.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

ChuckC

Quote from: td on June 19, 2022, 03:34:51 PM
Quote from: ChuckC on June 17, 2022, 02:39:55 PM
Good point.  For passing a handle value as an individual parameter, that would be preferred way to do it.

When it comes to computing the site of a structure that contains a field of type HANDLE, would it be best just to test the "bitness" of WinBatch and add 4 or 8 to the structure size that's being computed?  I wasn't sure where "long_ptr" modifier for DllCall() could be shoe-horned into the arithmetic for structure size calculation.

The DllStructAlloc function and friends are "bitness aware" so you can use types like HANDLE and long_ptr in structures without having to worry about the bitness of the process running the script.


Nifty!  Thanks!

archimede

Hallo ChuckC.
Thank you very much for your help.
Sorry for my bad English.
I am an hardware developer about domotic.
I build a new PC, starting the box ( I build the box by hand, cutting the plate... ), modifying the power supply, studying the air way, and may other things ); I made a simil-driver to interface the PC with an OLD 56K modem to automatically make phone number by only a selection on Outlook: when I open the phone communication the modem make the phone number and the call begins; I made a radio-alarm with week scheduling and auto-regulation of the volume ( from 0% to 100% in many minuts ) and many other ( really, many other, for example interfacing my stereo with PC ).
I use WinBatch ( and AutoHotkey ) because is very versatile: it allows to build a simil-driver ( low level ) and radio-alarm ( with interface, high level ); it allow sequentially program and event program; it allow to build interface without complicate knowledge; then for me would be too much hard starting to know C++ programming ( would be very interesting, but when I study it I can no build any other thing, and I have many other things to build...: for example I need to control some external digital card fot hardware control... ).
For example I was a C programmer, and I like C very much ( at DOS time... ) but to build the same prgrams by C or C++ requires many knowledge that I can no have, building all other... I can no made all, even I try it...
Then if I can no install C++ SDK I would be many happy...
I am trying to detect many keybords ( all remote ) to allow, at the same time, for a person to view some image ( by remote connection ) and to another person to hear radio Internet and to another person to hear stereo ( I made only an example to understand, I hope it is clear... )
Now I go ahead.
:-)

ChuckC

As long you keep focusing on saying that you can't do something you'll never be able to do it.

C++ is a superset of C.  In terms of basic Win32 API usage, although you are using the Visual C++ compiler in Visual Studio, by and large, you are really only using C language features.

I can tell you with 100% certainty at this point that you will not be able to read input from multiple keyboards and determine which keyboard produced the input without delving into some C/C++ coding efforts.  You will either that you are willing to do so and will make your very best effort at doing so, of you will fail.

Tony's previous comment about getting the latest WIL SDK and learning to build an extender with C/C++ was spot-on accurate in terms of what would be the shortest path to writing code that does what you want it to do.


archimede

Hallo.
It seem work well:

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

WILBYTENESS = ( 2 ** ( WinMetrics ( -2 ) + 3 )) / 8

POINTER32 = 4
POINTER64 = 8
INTDATA = 4
UINTDATA = 4
HANDLE32 = 4
HANDLE64 = 8
DWORD = 4

If WILBYTENESS == 4
   HANDLE = POINTER32
Else
   HANDLE = POINTER64
EndIf
sHIDDetectedItem = ""
sKbdDetectedItem = ""
sMouDetectedItem = ""

hpuiNumDevice = BinaryAlloc ( UINTDATA )
iStructWidth = HANDLE + DWORD
iStructWidth = iStructAlignedWidth ( iStructWidth, WILBYTENESS ) 

iReturnVal = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpnull, lpbinary:hpuiNumDevice, word:iStructWidth )
iHIDCount = BinaryPeek4 ( hpuiNumDevice, 0 ) 

hpRawInputDeviceList = BinaryAlloc ( iStructWidth * iHIDCount )      ; Allocazione memoria per tutte le strutture generate dalla
iHIDDetected = DllCall ( DirWindows ( 1 ) : "user32.dll", long:"GetRawInputDeviceList", lpbinary:hpRawInputDeviceList, lpbinary:hpuiNumDevice, word:iStructWidth )

For iCnt = 0 To iHIDDetected - 1

   hDeviceOffset = iCnt * iStructWidth                              ; Offset hDevice
   dwTypeOffset = hDeviceOffset + HANDLE                            ; Offset dwType

   If WILBYTENESS == 4
      ihDevice = BinaryPeek4 ( hpRawInputDeviceList, hDeviceOffset )
   Else
      ihDevice = BinaryPeek8 ( hpRawInputDeviceList, hDeviceOffset )
   EndIf
   idwType = BinaryPeek4 ( hpRawInputDeviceList, dwTypeOffset )     ; dwType nella struttura N. iCnt

   Switch idwType

      Case 2                                                       ;  HID no mouse no keyboard

         sHIDDetectedItem = ItemInsert ( ihDevice, -1, sHIDDetectedItem, @TAB )

         Break

      Case 1                                                       ; HID keyboard

         sKbdDetectedItem = ItemInsert ( ihDevice, -1, sKbdDetectedItem, @TAB )

         Break

      Case 0                                                       ; HID mouse

         sMouDetectedItem = ItemInsert ( ihDevice, -1, sMouDetectedItem, @TAB )

         Break

   EndSwitch

Next

BinaryFree ( hpRawInputDeviceList )
BinaryFree ( hpuiNumDevice )

Now I try to detect every keyboard name.