Page 1 of 3

MS SendInput

Posted: Mon Jul 03, 2017 10:25 am
by DGDanforth
WinApi does not export the Windows function "SendInput" present in USER32.dll so I created
a module that accesses that.

When I make a call to it I get the message (from show last error) of
"The parameter is incorrect."

Well there are 3 parameters being passed.

Code: Select all

IF MyUser32.SendInput(1, S.ADR(input), cbSize) = 0 THEN
			MyWin.ShowLastError; Out.Ln;
			input.Dump;
			HALT(0);
		END;
The first parameter specifies the number of records in the array "input" each of which is of size

Code: Select all

cbSize:= SIZE(KEYBDINPUT);
What I am trying to do is pass a sequence of keystrokes to a preexisting game whose handle I am able to get.
The fact that the call to SendInput does not bomb says I really am calling the procedure.

Now comes my questions. the C code looks like

Code: Select all

  kb.wVk  = vk;  
  Input.type  = INPUT_KEYBOARD;

  Input.ki  = kb;
  ::SendInput(1,&Input,sizeof(Input));
I am assuming the SYSTEM.ADR(input) does the same thing as &input. Is that correct?
Also SIZE(input) and sizeof(input) should yield the same number (in this case 16) bytes.
Before the call input.type = 1 (the value of INPUT_KEYBOARD), After the call stuff is
messed up
input.type = 6480556 (*initially 1*)
KEYBDINPUT.wVk = 452 (*initially ORD("R")*)
KEYBDINPUT.wScan = 13964 (*initially 0*)
KEYBDINPUT.dwFlags = 0 (*initially 0*)
KEYBDINPUT.time = 0 (*initially 0*)
KEYBDINPUT.dwExtraInfo = 0 (*initially 0*)
Is this an issue about [ccall]? What else could be going on?
-Doug Danforth

Re: MS SendInput

Posted: Mon Jul 03, 2017 10:40 am
by Josef Templ
Doug, can you post a complete code example including the MyXxx modules, everything.
Otherwise it is not possible to see what is going on.
My wild guess would be that the size parameter is wrong.

- Josef

Re: MS SendInput

Posted: Tue Jul 04, 2017 3:31 am
by DGDanforth
Josef,
This is probably more than you really want but for completeness ...
I've tried with and without [ccall] and get the same error. Which should it be?

Code: Select all

MODULE MyUser32  ["USER32.dll"];

IMPORT
	S:=SYSTEM;

PROCEDURE [ccall] SendInput* ["USER32.dll", ""] (nInputs, pInputs, cbSize: INTEGER): INTEGER;
	(*SendInput*)

END MyUser32.

Code: Select all

		Key = 	SHORTCHAR;

Code: Select all

		SCHARS =	ARRAY OF SHORTCHAR;

Code: Select all

		hWnd:	INTEGER;

Code: Select all

	PROCEDURE GetGameHandle (title: SCHARS);
	VAR className, windowName: WinApi.PtrSTR;
	BEGIN
		className := S.VAL(WinApi.PtrSTR, 0);
		windowName := S.VAL(WinApi.PtrSTR, S.ADR(title[0]));
		hWnd := WinApi.FindWindow(className, windowName);
		ASSERT(hWnd # 0);
	END GetGameHandle;
	

Code: Select all

	PROCEDURE Attach*;
	CONST false = 0;
	CONST true = 1;
	VAR fromPid, toPid, from, to, oldWnd: INTEGER; 
	BEGIN
		GetGameHandle(title);
		fromPid := WinApi.GetCurrentProcessId();
		toPid := 1;
		from := WinApi.GetCurrentThreadId();
		to := WinApi.GetWindowThreadProcessId(hWnd, toPid);
		(*
		Out.String("fromPid = "); Out.Int(fromPid); Out.Ln;
		Out.String("toPid = "); Out.Int(toPid); Out.Ln;
		*)
		ASSERT(WinApi.AttachThreadInput(from, to, true) # 0) ;
		ASSERT(WinApi.SetForegroundWindow(hWnd) # 0) ;
		ASSERT(WinApi.SetFocus(hWnd) # 0) ;
	END Attach;
	

Code: Select all

	PROCEDURE Detach*;
	CONST false = 0;
	CONST true = 1;
	VAR fromPid, toPid, from, to: INTEGER; 
	BEGIN
		fromPid := WinApi.GetCurrentProcessId();
		toPid := 1;
		from := WinApi.GetCurrentThreadId();
		to := WinApi.GetWindowThreadProcessId(hWnd, toPid);
		ASSERT(WinApi.AttachThreadInput(from, to, false) # 0) ;
		(*
		Out.String("fromPid = "); Out.Int(fromPid); Out.Ln;
		Out.String("toPid = "); Out.Int(toPid); Out.Ln;
		*)
	END Detach;
	

Code: Select all

		KEYBDINPUT =	RECORD
				wVk:	SHORTINT;
				wScan:	SHORTINT;
				dwFlags:	INTEGER;
				time:	INTEGER;
				dwExtraInfo:	INTEGER;
			END;

Code: Select all

	PROCEDURE (VAR input: KEYBDINPUT) SetKeyDown (key: Key), NEW;
	BEGIN
		input.wVk := SHORT(ORD(key));
		input.wScan := 0;
		input.dwFlags :=0;
		input.time := 0;
		input.dwExtraInfo := 0
	END SetKeyDown;
	

Code: Select all

		INPUT =	RECORD
				type:	INTEGER;
       				ki:	KEYBDINPUT
			END;

Code: Select all

	PROCEDURE (VAR input: INPUT) Zero, NEW;
	BEGIN
		input.type := 0;
		input.ki.Zero
	END Zero;
	

Code: Select all

	PROCEDURE (VAR input: KEYBDINPUT) Zero, NEW;
	BEGIN
		input.wVk := 0;
		input.wScan := 0;
		input.dwFlags := 0;
		input.time := 0;
		input.dwExtraInfo := 0
	END Zero;
	

Code: Select all

	PROCEDURE Test*;
	VAR flag: BOOLEAN; input: INPUT; cbSize: INTEGER;
	BEGIN
		input.Zero;
		Attach;
		input.type := INPUT_KEYBOARD;
		cbSize:= SIZE(KEYBDINPUT);
		input.ki.SetKeyDown("R");
		IF User32.SendInput(1, S.ADR(input), cbSize) = 0 THEN
			MyWin.ShowLastError; Out.Ln;
			input.Dump;
			HALT(0);
		END;
		Detach
	END Test;
	

Re: MS SendInput

Posted: Tue Jul 04, 2017 4:46 am
by DGDanforth
Small step.
I previously did not have my dump procedure when I tested the two forms of SendInput (with and without [ccall]).
I have tried those two forms again with dump and I see that with [ccall] I get garbage whereas without [ccall] the
returned parameters of KEYBDINPUT are clean. Hence no [ccall] seems to be correct.

Also I have tried using scan codes rather than virtual-key codes but I still get the same error message.

Code: Select all

	PROCEDURE (VAR input: KEYBDINPUT) SetKeyDown (key: Key), NEW;
	VAR Vk: INTEGER;
	BEGIN
		Vk := ORD(key);
		input.wVk := 0;
		input.wScan := SHORT(WinApi.MapVirtualKey(Vk, 0 ));
		input.dwFlags :=0;
		input.time := 0;
		input.dwExtraInfo := 0
	END SetKeyDown;
	
-Doug

Re: MS SendInput

Posted: Tue Jul 04, 2017 5:34 am
by DGDanforth
Perhaps this has something to do with c unions, of which I am not familiar.
I have assumed that the different record types simply share the same memory space.
The input.type = 1 then knows the space is a keyboard record and hence its length
should be 16. If that logic is wrong then what must I do?

Re: MS SendInput

Posted: Tue Jul 04, 2017 5:39 am
by DGDanforth
Also I found this on the web
Answer
Re: SendInput Parameter error Pin mvp Luc Pattyn 13-May-09 3:34

Go to ParentHi,

AFAIK there is a problem in your MOUSEINPUT struct: the extraInfo field is a pointer (IntPtr), not an int, and it takes 8B instead of 4B on X64.
BTW: there are a lot of such errors on the web, even on www.pinvoke.net
Answer
Re: SendInput Parameter error Pin mvp Luc Pattyn 13-May-09 3:34
General
Re: SendInput Parameter error Pin member IceWater42 18-May-09 19:15

Go to ParentThanks Luc. There is no question you are correct.

I was aware of the 8b vs 4b difference. I guess what i don't understand is how 64-bit machines can run 32-bit DLLs. Why is it that the 32-bit DLL is not expecting 32-bit pointers?

Sorry for delay in getting back here.

Re: MS SendInput

Posted: Tue Jul 04, 2017 8:11 am
by Josef Templ
Since WinApi does not specify ccall anywhere, why should it be specified for SendInput?
All Windows dll calls and callbacks use stdcall, as far as I remember.
This is the default in BB dll modules.

WinApi already defines similar INPUT records (INPUT_RECORD, PtrINPUT_RECORD).
This can be used as a template for defining a union.
Care must be taken regarding the usage of BYTE, SHORTINT, and INTEGER.
This is also crucial.
In addition, all union variants must be specified in order to get the correct maximum size and alignment
for the union. A union simply overlays all variants and the size
and alignment follow from the maximum of all variants.
If MOUSEINPUT requires more memory (and I think it does) it will be ignored otherwise,
leading to a wrong size. In addition, the event type must also be counted for the size, I think.
I would use SIZE(INPUT) not SIZE(KEYBDINPUT).

It may be a good idea to include SendInput in the 1.7.1 WinApi.

- Josef

Re: MS SendInput

Posted: Wed Jul 05, 2017 5:23 am
by Josef Templ
Here is a possible coding of SendInput as an extension of WinApi:

Code: Select all

CONST
		INPUT_MOUSE* = 0;
		INPUT_KEYBOARD* = 1;
		INPUT_HARDWARE* = 2;
		KEYEVENTF_UNICODE* = {2};
		KEYEVENTF_SCANCODE* = {3};
		MOUSEEVENTF_XDOWN* = {7};
		MOUSEEVENTF_XUP* = {8};
		MOUSEEVENTF_WHEEL* = {11};
		MOUSEEVENTF_HWHEEL* = {12};
		MOUSEEVENTF_MOVE_NOCOALESCE* = {13};
		MOUSEEVENTF_VIRTUALDESK* = {14};

TYPE
		PtrMOUSEINPUT* = POINTER TO MOUSEINPUT;
		MOUSEINPUT* = RECORD [untagged]
			dx*: INTEGER;
			dy*: INTEGER;
			mouseData*: INTEGER;
			dwFlags*: SET;
			time*: INTEGER;
			dwExtraInfo*: INTEGER;
		END;
		PtrKEYBDINPUT* = POINTER TO KEYBDINPUT;
		KEYBDINPUT* = RECORD [untagged]
			wVk*: SHORTINT;
			wScan*: SHORTINT;
			dwFlags*: SET;
			time*: INTEGER;
			dwExtraInfo*: INTEGER;
		END;
		PtrHARDWAREINPUT* = POINTER TO HARDWAREINPUT;
		HARDWAREINPUT* = RECORD [untagged]
			uMsg*: INTEGER ;
			wParamL*: SHORTINT;
			wParamH*: SHORTINT;
		END;
		PtrINPUT* = POINTER TO INPUT;
		INPUT* = RECORD [untagged]
			type*: INTEGER;
			u*: RECORD [union]
				mi*: MOUSEINPUT;
				ki*: KEYBDINPUT;
				hi*: HARDWAREINPUT;
			END
		END;

	PROCEDURE SendInput* ["USER32.dll", ""] (nInputs: INTEGER; pInputs: POINTER TO ARRAY [untagged] OF INPUT; cbSize: INTEGER): INTEGER;
	(*END SendInput;*)
For testing set the cursor to the traget position and execute TestSendInput.Do:

Code: Select all

MODULE TestSendInput;

IMPORT WinApi;

PROCEDURE Do*;
	VAR inp: ARRAY 1 OF WinApi.INPUT; res: INTEGER;
BEGIN
	inp[0].type := WinApi.INPUT_KEYBOARD;
	inp[0].u.ki.wVk := ORD("9");
	inp[0].u.ki.wScan := 0;
	inp[0].u.ki.dwFlags := {}; (*WinApi.KEYEVENTF_KEYUP*)
	inp[0].u.ki.time := 0;
	inp[0].u.ki.dwExtraInfo := 0;
	res := WinApi.SendInput(1, inp, SIZE(WinApi.INPUT));
	ASSERT(res > 0)
END Do;

END TestSendInput.
- Josef

Re: MS SendInput

Posted: Wed Jul 05, 2017 12:47 pm
by DGDanforth
Josef,
Thank you very much!
I see there were quite a number of things I overlooked and of which
I was unaware. I now need to study and test your code.
-Doug

Re: MS SendInput

Posted: Wed Jul 05, 2017 12:51 pm
by DGDanforth
Josef Templ wrote:Since WinApi does not specify ccall anywhere, why should it be specified for SendInput?
All Windows dll calls and callbacks use stdcall, as far as I remember.
...
It may be a good idea to include SendInput in the 1.7.1 WinApi.

- Josef
From the sourcecode of WinApi.odc

Code: Select all

	PROCEDURE [ccall] wsprintfA* ["USER32.dll", ""] (p0: PtrSTR; p1: PtrSTR): INTEGER;
	(*END wsprintfA;*)
Some procedures do and others don't use [ccall]

I agree that SendInput should be part of 1.7.1 WinApi
-Doug