Re: it cannot find a User32 function

eryk sun <[email protected]> Thu, 4 May 2017 02:53:22 +0000
Newsgroups gmane.comp.python.ctypes
Message-ID <CACL+1asw3WmBV-8GoJd5tdDbwsMWgBUaaY2WpGse0ZV373Pn4w@mail.gmail.com>
On Wed, May 3, 2017 at 11:19 PM, Michael C
<[email protected]> wrote:
> Hi all, I am trying to move the location and change the size of a window,
> but the User32.FindWindow function cannot be found!
>
> AttributeError: function 'FindWindow' not found

Here's the prototype from MSDN [1]:

    HWND WINAPI FindWindow(
      _In_opt_ LPCTSTR lpClassName,
      _In_opt_ LPCTSTR lpWindowName
    );

[1]: https://msdn.microsoft.com/en-us/library/ms633499

The string type is "LPCTSTR". That's a [L]ong [P]ointer (just a
pointer nowadays; there's no segmented addressing) to a [C]onstant
[T]CHAR [STR]ing. A TCHAR is a [T]ext CHAR ("T" could also mean
"typed) that depends on defining the UNICODE macro in C. If the macro
is defined, a TCHAR is a two-byte WCHAR, i.e. a [W]ide CHAR, and
otherwise it's a single-byte CHAR. The macro also determines the
definition of the "T" types. If defined, an LPCTSTR is in turn defined
as an LPCWSTR, and otherwise it's an LPCSTR.

Historically this split is because single-byte and double-byte code
pages were the way text was localized in the 80s and 90s in MS-DOS and
versions of Windows that extend DOS, i.e. Windows 3.x and 9x. The
newer NT system (developed in 1988-93 and released in 1993 as Windows
NT 3.1) is based on Unicode internally and uses 16-bit WCHARs.

When the NT designers updated the existing Win16 API to Win32, they
needed to support porting legacy Windows applications that use code
pages, so they developed this typing system based on the UNICODE
macro. Also, when the Win32 API itself was ported to DOS-based Windows
95, there was only rudimentary Unicode support, so for a long time
even new applications were designed around code pages. Starting with
Windows XP, all versions of Windows use the NT kernel, and by Windows
Vista new functions appear that only support Unicode (e.g.
SetFileInformationByHandle).

What the FindWindow prototype is not showing is that when Windows is
built it has to support programs compiled for both CHAR and WCHAR. For
most functions Windows NT uses WCHAR, so the CHAR implementation
decodes the string arguments and calls a common internal function. The
code page it decodes from is usually the system-locale ANSI codepage
(e.g. 1252 in the U.S. and Western Europe), with a couple of
exceptions. The file-system 'ANSI' API can be set to the system-locale
OEM codepage (e.g. 850 in Western Europe) via SetFileApisToOEM. Also,
the console 'ANSI' API defaults to OEM and can be set to any
single-byte or multi-byte code page separately for input and screen
buffers via SetConsoleCP and SetConsoleOutputCP.

Whenever possible you should prefer calling the [W]ide-character
function to avoid writing locale-dependent code.

That said, here are the actual FindWindow prototypes from the WinUser.h header:

    WINUSERAPI
    HWND
    WINAPI
    FindWindowA(
        _In_opt_ LPCSTR lpClassName,
        _In_opt_ LPCSTR lpWindowName);

    WINUSERAPI
    HWND
    WINAPI
    FindWindowW(
        _In_opt_ LPCWSTR lpClassName,
        _In_opt_ LPCWSTR lpWindowName);

    #ifdef UNICODE
    #define FindWindow  FindWindowW
    #else
    #define FindWindow  FindWindowA
    #endif // !UNICODE

Since ctypes works at the ABI (application binary interface) level
rather than API (application programmng interface), you need to use
the function names a module actually links with, i.e. either
FindWindowA for the [A]NSI implementation or FindWindowW for the
[W]ide-character implementation.

Let's take a look at your code.

> import ctypes
> User32 = ctypes.WinDLL('User32', use_last_error=True)
> window_name = 'Star Wars'
> handle = User32.FindWindow(None, window_name )

If you're using Python 3, then 'Star Wars' is a Unicode string, and
you should call FindWindowW. If you're using Python 2, then it's a
byte string, and you should call FindWindowA. But I don't recommend
the latter. You should be working with Unicode, i.e. u'Star Wars'. For
example:

    import ctypes
    from ctypes import wintypes

    user32 = ctypes.WinDLL('user32', use_last_error=True)

    user32.FindWindowW.restype = wintypes.HWND
    user32.FindWindowW.argtypes = (wintypes.LPCWSTR,
        wintypes.LPCWSTR)

    if __name__ == '__main__':
        import os
        kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

        window_name = u'Spam Wars'
        os.system('title {}'.format(window_name))

        handle = user32.FindWindowW(None, window_name)
        if handle is None:
            handle = user32.FindWindowW(None,
                u'Administrator:  {}'.format(window_name))

        assert handle == kernel32.GetConsoleWindow()

Another thing to consider is that if you've defined the FindWindowW
prototype, ctypes in Python 2 will automatically convert a byte-string
argument to Unicode. It uses the encoding and errors that are set by
ctypes.set_conversion_mode(encoding, errors), which defaults to
('mbcs', 'ignore'). Python's 'mbcs' encoding is the system-locale ANSI
code page. This automatic conversion is not implemented in Python 3
ctypes, in which case you should definitely be using Unicode.

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot