Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

DllCall: "UChar*", buffer vs. "UInt", &


  • Please log in to reply
7 replies to this topic
Jamie
  • Members
  • 129 posts
  • Last active: Dec 02 2012 04:59 AM
  • Joined: 26 Mar 2010
The documentation makes it seem as if calling a dll function with "UChar*" will pass the address of a variable, much like passing a "UInt" with the address-of operator (&) but these are NOT the same. One works and the other throws access violations.

What is the difference? Why would someone use the "UChar*" form? It seems to be broken.

wolf_II
  • Members
  • 343 posts
  • Last active: Jul 23 2010 05:19 PM
  • Joined: 18 Oct 2007
"UChar*" will push ONE byte on the stack.
"UInt" will push FOUR bytes on the stack.

I think. ?
Wolf

Schön wär's, wenn's schön wär!

Jamie
  • Members
  • 129 posts
  • Last active: Dec 02 2012 04:59 AM
  • Joined: 26 Mar 2010
Pointers take four bytes, at least on a 32 bit system. So they should be equivalent as far as what ends up on the stack.

YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
You are probably doing something wrong. I just compiled a DLL with a couple of functions — one reads from, the other writes to a variable passed as "UChar *". No errors, the value is read and written correctly.

"UChar *" in AHK is a pointer to a single 8-bit number, not a pointer to an array of 8-bit numbers. Maybe you are trying to pass a string as "UChar *"? I saw that if the variable had been initialized with a string, the function received a pointer to zero and not to the string's first character. The string is probably discarded and the variable reinitialized as a number before passing its pointer to the function.

Jamie
  • Members
  • 129 posts
  • Last active: Dec 02 2012 04:59 AM
  • Joined: 26 Mar 2010
So here's a function which simply returns its argument:
typedef unsigned int uint;
uint __stdcall ptrtest(uint *p) {
	return (uint)p;
}
Which condenses into this mcode: 8b442404c20400

When invoked as MCode using ("UInt", &var), the return value matches &var. But when invoked as ("UInt*", var), the return value does not match &var. So I am not seeing how it could work to read or write values.

MCode(ptrtest, "8b442404c20400") ; stdcall function just returns its argument
SetFormat, IntegerFast, H
VarSetCapacity(buf1, 4, 1)
rc := DllCall(&ptrtest, "UInt*", buf1)
abuf1 := &buf1
MsgBox, return code: %rc%  &buf1: %abuf1%  errorlevel: %errorlevel%
; says return code: 0x88e7ec  &buf1: 0xec0fc4  errorlevel: 0
SetFormat, IntegerFast, D
In what appears to be a different, probably unrelated issue, when I use the ("UInt", &var) method, I can read from the address, but not write to it. (When using ("UInt*", var) I can't read either). Reading will get the correct value (0x01010101 as expected from VarSetCapacity) but writing produces a return code indicating an access violation.

YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
As far as I know, plain variables declared as "Var = 111" are stored internally as strings, even if they represent numerical values. When you explicitly request a pointer to a number ("Int *", Var), that number is probably allocated in a different place in binary form and you get a pointer to that location. After the function returns, the binary result of its work is converted back to string form and stored back in the original place. But when you request the address of the variable ("UInt", &Var), AHK just gives you what you want, not doing any conversions, probably because in this case it doesn't know what you are going to do with the variable. Of course, those are just my guesses, but they could explain the difference you observed.

Variables created via VarSetCapacity hold their values already in binary form, so you normally would not use "Int *" with them. Here the correct approach is &Var.

Compare these two examples, in both of them the function swaps the values of the variables. For that, of course, it should be able to read from and write to them.
Var1 = 111
Var2 = 222

MsgBox, Var1: %Var1%`nVar2: %Var2%

MCode(ptrtest,"5589E58B45088B550CFF30FF328F008F025DC20800")

DllCall(&ptrtest, "Int *", Var1, "Int *", Var2)

MsgBox, Var1: %Var1%`nVar2: %Var2%


;=================== MCode ============================================

MCode(ByRef code, hex) { ; allocate memory and write Machine Code there 
   VarSetCapacity(code,StrLen(hex)//2) 
   Loop % StrLen(hex)//2 
      NumPut("0x" . SubStr(hex,2*A_Index-1,2), code, A_Index-1, "Char") 
}

SetFormat, Integer, Hex
VarSetCapacity(Var1, 4, 1)
VarSetCapacity(Var2, 4, 2)

MsgBox, % "Var1: " NumGet(Var1) "`n Var2: " NumGet(Var2)

MCode(ptrtest,"5589E58B45088B550CFF30FF328F008F025DC20800")

DllCall(&ptrtest, "UInt", &Var1, "UInt", &Var2)

MsgBox, % "Var1: " NumGet(Var1) "`n Var2: " NumGet(Var2)


;=================== MCode ============================================

MCode(ByRef code, hex) { ; allocate memory and write Machine Code there 
   VarSetCapacity(code,StrLen(hex)//2) 
   Loop % StrLen(hex)//2 
      NumPut("0x" . SubStr(hex,2*A_Index-1,2), code, A_Index-1, "Char") 
}

And finally, the source of my machine code function. Sorry, I don't have a C compiler at the moment, it's in assembler:
ptrTest1: FRAME ptr1, ptr2

    mov eax,[ptr1]
    mov edx,[ptr2]
    push [eax]
    push [edx]
    pop [eax]
    pop [edx]
    ret

ENDF


Jamie
  • Members
  • 129 posts
  • Last active: Dec 02 2012 04:59 AM
  • Joined: 26 Mar 2010

As far as I know, plain variables declared as "Var = 111" are stored internally as strings, even if they represent numerical values. When you explicitly request a pointer to a number ("Int *", Var), that number is probably allocated in a different place in binary form and you get a pointer to that location. After the function returns, the binary result of its work is converted back to string form and stored back in the original place.

Aha, this would make sense! Thank you, I hadn't thought about that.

As for the exceptions being generated, I'm still digging into that. Your code works for me, and when I write the equivalent function in C it also works. But if I change it to assign an immediate constant, it returns an exception.
typedef unsigned int uint;
uint __stdcall ptrtest(uint *p, uint *q) {
	uint t = *p;
	t = 123;
	*p = *q;
	*q = t;
	return t;
}
_p$ = 8							; size = 4
_q$ = 12						; size = 4
[email protected]@[email protected] PROC				; ptrtest, COMDAT
  00000	8b 44 24 08        mov	 eax, DWORD PTR _q$[esp-4]
  00004	8b 08              mov	 ecx, DWORD PTR [eax]
  00006	8b 54 24 04        mov	 edx, DWORD PTR _p$[esp-4]
  0000a	89 0a              mov	 DWORD PTR [edx], ecx
  0000c	c7 00 7b 00 00 00  mov	 DWORD PTR [eax], 123	; 0000007bH
  00012	b8 7b 00 00 00     mov	 eax, 123		; 0000007bH
  00017	c2 08 00           ret	 8
[email protected]@[email protected] ENDP				; ptrtest
MCode(ptrtest, "8b4424088b088b542404890a0000cc7007b000000b87b000000c20800")
It's frustrating because it should be simple. And doing arithmetic operations such as t=*p+*q gives correct results with no exceptions. I checked the pointer alignment and they're all 4-byte aligned. It's puzzling to me. It seems the immediate assignment is causing the problem somehow.

Jamie
  • Members
  • 129 posts
  • Last active: Dec 02 2012 04:59 AM
  • Joined: 26 Mar 2010
AW CRAP

Okay, i found it, the code i was using to copy from assembly code to produce the mcode was injecting some wrong hex bytes into it.

The red highlighted hex digits are not supposed to appear in the machine code anywhere but showed up mysteriously.

8b4424088b088b542404890a0000cc7007b000000b87b000000c20800

When I remove them, it all is good! Sorry for the mistaken problem.