Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

DllCall() compatibility issue with 64 bit AHK_L (Win7 x64)


  • Please log in to reply
12 replies to this topic
SL55-AMG
  • Guests
  • Last active:
  • Joined: --
Hello, I'm trying to convert some of the existing 32 bit scripts and libraries to 64 bit. However, it seems that DllCall doesn't work in AHK_L 64 bit when structs need to be passed directly as an argument (not via a pointer).

Example 1: a POINT struct
(adapted from mouse scroll wheel script)

MouseGetPos, x, y
hwnd:=DllCall("WindowFromPoint", int, x, int, y)	; works in 32 bit but not in 64 bit

According to MSDN, WindowFromPoint() takes a POINT struct, defined as two 32 bit values (LONG). Since "int" is 32 bits in both x86 and x64, it would seem the argument sizes shouldn't need to be changed for x64. However, in AHK_L x64 (latest version 1.1.05.06), only the x argument has any effect, and as a result it returns hwnds only for windows along the top of the screen.


Exhibt 2: a VARIANT struct
(adapted from the original ACC.AHK lib)

DllCall("LoadLibrary",str,"oleacc", ptr)

VarSetCapacity(Point, 8, 0)
DllCall("GetCursorPos", ptr, &Point)

DllCall("oleacc\AccessibleObjectFromPoint", "int64", NumGet(Point, 0, "int64"), ptrp, pAccessible, ptr, &varChild)	

; get vtable for IAccessible
vtAccessible :=  NumGet(pAccessible+0, "ptr")

; call get_accName() through the vtable
hr := DllCall(NumGet(vtAccessible+0, 10*A_PtrSize, "ptr"), ptr, pAccessible,"int64", 3, "int64", 0,"int64", 0 ptrp, pvariant) 
; variant's type is VT_I4 = 3
; variant's value is CHILDID_SELF = 0

; get_accName returns the following hresult error with 64 bit ahk
hr_facility := (0x07FF0000 & hr) >>16	; shows facility = 7 "win32"
hr_code := 0x0000ffff & hr		; code 1780 "RPC_X_NULL_REF_POINTER"

More info:
In Example 1, a workaround for the POINT struct was possible by passing a single "int64" parameter instead. However, this couldn't be done for Example 2, because a VARIANT struct (24 bytes in x64) is larger than the largest argument size available for DllCall().

In Example 2: The original ACC.AHK library, which was written for x86, faked the VARIANT struct by passing two "int64"s in the function call. The same technique doesn't seem to work under AHK_L 64 bit -- as shown above, a third "int64" was added to adjust the size of the variant parameter up to 24 bytes. Further notes:
a. the other vtable functions which don't take a VARIANT as a direct parameter work fine.
b. invoking that same function via the IDispatch works fine, because IDispatch takes variants as pointers. [/list]
I'm not sure if this might have something to do with the different calling convention used in x64 (eg stdcall in x86 versus fastcall in x64). There were a few other areas in the Windows API I came across where structs are passed directly as arguments. Is there any way to specify or verify the calling convention used by DllCall()? Or is there any way to affect how parameters get pushed onto the call stack or registers, for example whether small variables get zero-extended, etc? It would be nice to get all those great scripts upgraded to 64 bits.

Thanks a bunch

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010
Exhibit A, you are not defining the return value; it should be UPtr. You are passing two parameters; you should only be passing one: a POIT structure. I believe that the other exhibits in this post likewise don't show any bugs in AHK and this topic should be moved to Ask for Help.

SL55-AMG
  • Guests
  • Last active:
  • Joined: --
Specifying the return type doesn't make a difference, eg:

hwnd:=DllCall("WindowFromPoint", "int", x, "int", y, "ptr")

It has always returned a valid hwnd, just the wrong one. Eg WinGetClass always shows "MSTaskListWClass" in both cases since I have the taskbar at the top of the screen.

Regarding passing one parameter for a struct:
(a) there isn't any available type as "struct" for DllCall(). E.g. to say only one parameter should be passed for an entire struct means that it would be impossible to ever call a winapi function that takes a struct larger than the size of an "int64"?

All the existing x86 scripts/libraries handled this by splitting the struct up into multiple parameters. The Exhibit 1 code was taken directly from the mouse scroll wheel sample http://www.autohotke...ic6772-105.html. But this method just doesn't seem to work under ahk x64.

(B) Supposedly according to MSDN the x64 calling convention does not attempt to break up base types among call registers (according to link below). This is a different treatment from x86. Presumably the win32 compiler would push each base type parameter from the struct to the call register/call stack separately. And in x64, the size of the register is 64 bits, so a large struct would definitely not be passed as one parameter.

http://msdn.microsof...y/ms235286.aspx

  • Guests
  • Last active:
  • Joined: --
All parameters are 64-bit on x64. Structs larger than 64 bits are passed by address (which is 64-bit).

It has always returned a valid hwnd,

If the HWND value fits in an INT, you won't notice the problem. But a HWND isn't guaranteed to fit in an INT.

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010

Specifying the return type doesn't make a difference, eg:

You are passing two parameters; you should only be passing one: a POIT structure.



fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
You create structs by using NumPut/NumGet and the offset memory address. Alternatively, feel free to use a struct library, there are some on this forum.

just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

MSDN:
The __fastcall model uses registers for the first four arguments and the stack frame to pass the other parameters.
...
The x64 Application Binary Interface (ABI) is a 4 register fast-call calling convention, with stack-backing for those registers. There is a strict one-to-one correspondence between arguments in a function, and the registers for those arguments. Any argument that doesn’t fit in 8 bytes, or is not 1, 2, 4, or 8 bytes, must be passed by reference. There is no attempt to spread a single argument across multiple registers.

Help File:
DllCall:
ErrorLevel:
An (the letter A followed by an integer n): The function was called but was passed too many or too few arguments. "n" is the number of bytes by which the argument list was incorrect. If n is positive, too many arguments (or arguments that were too large) were passed, or the call requires CDecl. If n is negative, too few arguments were passed. This situation should be corrected to ensure reliable operation of the function. The presence of this error may also indicate that an exception occurred, in which case the function returns a blank value. Note that due to the x64 calling convention, 64-bit builds never set ErrorLevel to An.

IMHO this means:

WindowFromPoint has exactly one 8-byte argument. So it isn't a workaround to pass one "Int64", it's rather a workaround to pass two "Int" arguments to get 8 bytes in 32-bit builts. This "concatenation workaround" won't work in 64-bit builts due to the __fastcall model, because (at least) the first four arguments are passed separate in registers.

DllCall doesn't know the number and types of arguments the function requires, it just passes what is given. So it isn't a bug, when you use a wrong type.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
just me is correct; what you describe is not a bug. Please confirm you have seen this post so that a moderator can move the thread from Bug Reports to Ask for Help.

The following shows the correct usage of WindowFromPoint (assuming x and y are always within the bounds of a 32-bit integer).
Loop {
    CoordMode Mouse
    MouseGetPos x, y
    hwnd := DllCall("WindowFromPoint", "int64", x | y << 32, "ptr")
    WinGetTitle title, ahk_id %hwnd%
    WinGetClass class, ahk_id %hwnd%
    ToolTip %title% ahk_class %class%
    Sleep 10
}
For structures larger than 64-bit, you are forced to use different code for 32-bit and 64-bit builds. However, I have a tentative plan to add support for structures of any arbitrary size. If you have any suggestions (such as what the syntax should be), please start a new thread in the Wish List forum.

This "concatenation workaround" won't work in 64-bit builts due to the __fastcall model, because (at least) the first four arguments are passed separate in registers.

DllCall passes all parameters on the stack. For x64, the first four parameters are additionally stored in the following registers as per the x64 calling convention: rcx, rdx, r8, r9, xmm0, xmm1, xmm2 and xmm3. (The last four are for floating-point values, but all eight are set for simplicity.)

Is there any way to specify or verify the calling convention used by DllCall()?

For x64, there is only one calling convention. Otherwise, you can specify "Cdecl" in DllCall's final parameter to use that calling convention or omit it to use stdcall.

Or is there any way to affect how parameters get pushed onto the call stack or registers, for example whether small variables get zero-extended, etc?

Parameters are pushed onto the stack according to the calling convention and parameter types that you specify.

Since AutoHotkey uses int64 natively, input values are not extended and are only truncated as much as necessary to fit into the parameter list. In other words, all integer types behave the same, with the exception of (U)Int64 on 32-bit builds. (There is no need to truncate to anything smaller than a pointer; if a function accepts a Char, that implies it will use only the low 8 bits of that parameter.)

On the other hand, output values are zero-extended or sign-extended as appropriate for the type which you specify.

SL55-AMG
  • Guests
  • Last active:
  • Joined: --
Thanks so much for the info.

Many of the COM functions (non-IDispatch) take a VARIANT struct directly on the call stack, which is indeed larger than an "int64". For example, from the SDK header file for IAccessible:

HRESULT STDMETHODCALLTYPE get_accName( 
            /* [optional][in] */ VARIANT varChild,
            /* [retval][out] */ __RPC__deref_out_opt BSTR *pszName) = 0;
In x86 AHK_L, people have gotten around this by breaking up the VARIANT struct into two separate Int64's, calling the function like so:

DllCall(NumGet(vtAccessible+0, 10*A_PtrSize, "ptr"), ptr, pAccessible, int64, 3, int64, 0, ptrp, pvariant)

However, this technique doesn't seem to work for x64 AHK_L. As I described initially, it results in hresult code of "RPC_X_NULL_REF_POINTER" (this description seems to suggests that COM is marshalling these call parameters across thread apartments or process boundaries). I'm not sure how the Microsoft compiler would actually break up the VARIANT and place it on the call stack/registers, but I did try breaking it up according to the struct definition for a Variant, but that didn't seem to work either.

So for the moment, are we saying that x64 AHK_L doesn't support any COM functions that take a VARIANT struct directly on the call stack? (Or at least not support the workaround that allowed x86 AHK_L to do so?)

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

However, this technique doesn't seem to work for x64 AHK_L.

just me already gave you the answer. Hint: VARIANT is not 1, 2, 4 or 8 bytes.

[Note]

SL55-AMG
  • Guests
  • Last active:
  • Joined: --
Wow you guys are genious! Sorry I missed the clue. It seems sending replacing the VARIANT with a pointer to a VARIANT works, even though this contradicts how the OleAcc.h header defined the function's prototype!

So this was how ACC.AHK library for x86 called get_accName, placing the VARIANT directly on the call stack as per the function prototype in the sdk header file:

DllCall(NumGet(NumGet(1*pacc)+40), "Uint", pacc, [color=darkred]"int64", 3, "int64", idChild[/color], "UintP", pName)
To convert this to x64, changing it to instead pass a pointer to the VARIANT work:

VarSetCapacity(variantChild, 8+2*A_PtrSize, 0)
NumPut(3, 0, "short") 	; VT_I4
DllCall(NumGet(NumGet(pacc+0, "ptr"), 10*A_PtrSize, "ptr"), ptr, pacc, [color=darkred]ptr, &variantChild[/color], ptrp, pName)

I concur there is no bug. Regarding the previous example about the POINT struct, I was wondering whether maybe the documentation for "int" could be updated. It current says

"A 32-bit integer (the most common integer type), whose range is -2147483648 (-0x80000000) to 2147483647 (0x7FFFFFFF). An Int is sometimes called a "Long".

This definition is consistent with the Win32 api where an "int" is always 32 bits. But with x64 AHK_L, the int is actually 64 bits as you mentioned and it seems a NumGet() can fill it beyond 32 bits:

Loop
{
VarSetCapacity(point, 8)
DllCall("GetCursorPos", ptr, &point)
SetFormat, Integer, h
xy := NumGet(point, 0)
SetFormat, Integer, h
hwnd:=DllCall("WindowFromPoint", "int", xy)
WinGetClass, class, ahk_id %hwnd%
Tooltip %hwnd% %class%
}

Alternatively, maybe there is some way to restrict the "int" to only 32 bit values to keep things consistent with windows api?

Thanks

just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

But with x64 AHK_L, the int is actually 64 bits as you mentioned

No, an "Int" has 32 bits in both builds.

Alternatively, maybe there is some way to restrict the "int" to only 32 bit values to keep things consistent with windows api?

There is, of course:
x := Numget(Point, 0, "[color=red]Int[/color]")
y := Numget(Point, 4, "[color=red]Int[/color]")
(The default type argument of NumGet() is "UPtr" which is 8 bytes on 64-bit builds)

chaidy
  • Members
  • 57 posts
  • Last active: Oct 21 2015 05:53 PM
  • Joined: 20 Apr 2010
suggest CSCode class for SCODE bitfield structure
http://msdn.microsof...y/bb415337.aspx
[attachment=51:SCODE structure.jpg]
; CStruct.ahk - http://www.autohotkey.com/community/viewtopic.php?f=2&t=84054
#include CStruct.ahk
#SingleInstance Force

^\::ExitApp

F12::
	DllCall("LoadLibrary",Str,"oleacc", Ptr)
	
	Point := new CPoint
	DllCall("GetCursorPos", Ptr, Point[""])
	
	VarSetCapacity(varChild, 8)
	DllCall("oleacc\AccessibleObjectFromPoint", "int64", Point.int64, ptrp, pAccessible, ptr, &varChild)   
	
	; get vtable for IAccessible
	vtAccessible :=  NumGet(pAccessible+0, "ptr")
	
	; call get_accName() through the vtable
	scode := new CSCode
	scode.ulong := DllCall(NumGet(vtAccessible+0, 10*A_PtrSize, "ptr"), ptr, pAccessible,"int64", 3, "int64", 0,"int64", 0, ptrp, pvariant) 
	; variant's type is VT_I4 = 3
	; variant's value is CHILDID_SELF = 0
	
	; get_accName returns the following hresult error with 64 bit ahk
	; facility = 7  : "win32"
	; code 1780 : "RPC_X_NULL_REF_POINTER"
	MsgBox % "facility = " scode.facility  "`ncode = " scode.code
return


;=======================================================================
; http://msdn.microsoft.com/en-us/library/bb415337.aspx
; The SCODE data-type is a 32-bit value that specifies a particular warning or error code.
class CSCode extends CStruct_Base
{
   __New()
   {
      this.AddStructVar("code"    , "ULONG", "bit:16")
      this.AddStructVar("facility", "ULONG", "bit:12")
      this.AddStructVar("r"       , "ULONG", "bit:1")
      this.AddStructVar("c"       , "ULONG", "bit:1")
      this.AddStructVar("sev"     , "ULONG", "bit:2")
      this.SetStructCapacity()
   }
   __Get(name)
   {
      if name=ulong
         return NumGet(base.GetData("ptr"), "ULONG")
   }
   __Set(name, v)
   {
      if name=ulong
         return v  ,  NumPut(v+0, base.GetData("ptr"), "ULONG")
   }
}