I'm currently getting into the ctypes
module and I'm trying to call the user32 function GetWindowText
with a HWND
handle I already received by using FindWindow
. This time though i wanted to process a step further and use a function prototype instead of calling the function with ctypes.windll.user32.GetWindowText
. Although I´m having problems declaring the lpString
arguement as output parameter.
My first attempt looked this way:
GetWindowText = cfunc("GetWindowTextA",windll.user32,c_int,
("hWnd",HWND,1),
("lpString",LPCSTR,2),
("nMaxCount",c_int,1)
)
(cfunc
is a little wrapper that I found here)
This prototype yields the following exception as soon as it is called:
chars,name = user32.GetWindowText(handle,255)
TypeError: c_char_p 'out' parameter must be passed as default value
I thought that any output variables must be a POINTER(...)
type, so I changed my definition to:
GetWindowText = cfunc("GetWindowTextA",windll.user32,c_int,
("hWnd",HWND,1),
("lpString",POINTER(c_char),2),
("nMaxCount",c_int,1)
)
But this yields an exception too:
chars,name = user32.GetWindowText(handle,255)
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong type
I hope somebody knows how to call the GetWindowText
function correctly using ctypes
prototyping.
Edit:
Through further research I could get it to work, at least somehow. The first issue I fixed was the usage of cfunc()
which had wrong calling specifiers. I defined a exact copy of that function and named it winfunc()
and replaced return CFUNCTYPE(result, *atypes)((name, dll), tuple(aflags))
with return WINFUNCTYPE(result, *atypes)((name, dll), tuple(aflags))
.
Then I inspected prototyping further. As it seems if you pass somewhat like ("someParameter",POINTER(aType),2)
to WINFUNCTYPE
it will create a aType
object on call and passes a pointer to that object to the function. In the returned tuple you can then access the aType
object. This brings up another problem. A cstring is a array of chars; so one needs to tell ctypes to create a c_char array
. This means that something like:
GetWindowText = winfunc("GetWindowTextA",windll.user32,c_int,
("hWnd",HWND,1),
("lpString",POINTER(c_char*255),2),
("nMaxCount",c_int,1)
)
works just fine. But unfortunately, ctypes will now pass a pointer to a cstring which is ALWAYS 255 chars long ignoring the size specified by nMaxCount
.
In my opinion, I think theres no way one could get that function to work with a dynamically sized cstring defined as output parameter. The only possibility seems to be simply going without the output parameter feature and defining a LPCSTR
as input parameter. The callee then needs to create a buffer by his own with ctypes.create_string_buffer()
and pass it to the function (just as in C).
You have to create a string buffer for out parameters. You can wrap the function to make it somewhat transparent:
# python3
from ctypes import *
_GetWindowText = WinDLL('user32').GetWindowTextW
_GetWindowText.argtypes = [c_void_p,c_wchar_p,c_int]
_GetWindowText.restype = c_int
def GetWindowText(h):
b = create_unicode_buffer(255)
_GetWindowText(h,b,255)
return b.value
FindWindow = WinDLL('user32').FindWindowW
FindWindow.argtypes = [c_wchar_p,c_wchar_p]
FindWindow.restype = c_void_p
h = FindWindow(None,'Untitled - Notepad')
print(GetWindowText(h))
Or in this case you can just use pywin32:
import win32gui
h = win32gui.FindWindow(None,'Untitled - Notepad')
print(win32gui.GetWindowText(h))