pythonwinapiprototypectypesuser32

Python ctypes: Prototype with LPCSTR [out] parameter


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).


Solution

  • 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))