Search code examples
pythonwindowswinapipywin32sendmessage

Encode text into WPARAM for win32api.SendMessage()


I'm trying to call win32api.SendMessage() via my Python application (i.e. the sending app).

The receiving app's API states that the format of the message is: ::SendMessage(<app_name>, <msg_name>, (WPARAM) <value>)

However, value is actually a string of between 3 to 4 characters (no spaces).

My Question

What is the correct way of using win32api.SendMessage, especially with regard to value?

Can I simply put the string, as in: win32api.SendMessage(<app_name>, <msg_name>, "ABC")?

Or do I need to convert the string to WPARAM type (and if so, how do I do that)?

I've been developing in Linux Python and have very little experience with Windows and C++. Would appreciate any assistance.

Thanks in advance!

P.s. In response to comments, the receiving app is actually AmiBroker and the actual message format given in the API documentation is: ::SendMessage( g_hAmiBrokerWnd, WM_USER_STREAMING_UPDATE, (WPARAM) Ticker, (LPARAM) &recentInfoStructureForGivenTicker ); The 'string' I mentioned earlier is 'Ticker', which the author says is a string (char*). I didn't include that initially as I thought the actual message format is not important.

RESEARCH: I read from this that WPARAM is essentially an integer type, and this led me to win32api. Among the many articles I read; none of them helped to answer my questions above (or at least so I think).


Solution

  • [Github]: mhammond/pywin32 - Python for Windows (pywin32) Extensions is a Python wrapper over WINAPIs, and therefore is designed to be Python friendly.

    [ActiveState.Docs]: win32api.SendMessage (best doc I could find), is a wrapper over [MS.Docs]: SendMessage function.

    The lParam (last) argument is a LONG_PTR, which means that it holds a memory address that can point to anything. Usually this is the one used to pass data like strings.

    Since I don't know what message you want to send, I spent some time til I found [MS.Docs]: EM_REPLACESEL message.

    code0.py:

    #!/usr/bin/env python3
    
    import sys
    import win32api
    import win32gui
    import win32con
    
    
    is_py2 = sys.version_info.major < 3
    
    if is_py2:
        _input = input
        input = raw_input
    
    
    def main():
        np_wnd = win32gui.FindWindow(None, "Untitled - Notepad")
        if not np_wnd:
            print("Cound not get Notepad window")
            return
        np_edit_wnd = win32gui.GetWindow(np_wnd, win32con.GW_CHILD)
        if not np_edit_wnd:
            print("Cound not get Notepad child window")
            return
        heading = "After pressing ENTER, "
        #'''
        input("{:s}all text in Notepad will be selected ... ".format(heading))
        # HERE's when the 1st screenshot was taken
        win32api.SendMessage(np_edit_wnd, win32con.EM_SETSEL, 0, -1)
        replaced_text0 = "Replaced\nmultiline text."
        input("{:s}Notepad text will be set (via EM_REPLACESEL) to: \n\"\"\"\n{:s}\n\"\"\" ... ".format(heading, replaced_text0))
        win32api.SendMessage(np_edit_wnd, win32con.EM_REPLACESEL, 0, replaced_text0)  # Regular string
        # HERE's when the 2nd screenshot was taken. It was at the end of the program (at that time), but some stuff was added
        replaced_text1 = "Other\nreplaced\n\nnmultiline text."
        input("\n{:s}Notepad text will be set (via WM_SETTEXT) to: \n\"\"\"\n{:s}\n\"\"\" ... ".format(heading, replaced_text1))
        win32api.SendMessage(np_edit_wnd, win32con.WM_SETTEXT, 0, replaced_text1)
        if not is_py2:
            return
        #'''
        print("\nFor Python 2, also get the text back from Notepad")
        buf_size = 255
        buf = win32gui.PyMakeBuffer(buf_size)
        text_len = win32api.SendMessage(np_edit_wnd, win32con.WM_GETTEXT, buf_size, buf)
        print("    Original text length: {:d}\n    Retrieved text length: {:d}\n    Text: \"\"\"\n{:s}\n    \"\"\"".format(len(replaced_text1), text_len, buf[:text_len]))
    
    
    if __name__ == "__main__":
        print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
        main()
        print("\nDone.")
    

    Outcome:

    • Initial state:

      Img0

    • Final state:

      Img1

    Output:

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056331657]> "e:\Work\Dev\VEnvs\py_064_02.07.15_test0\Scripts\python.exe" code0.py
    Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32
    
    After pressing ENTER, all text in Notepad will be selected ...
    After pressing ENTER, Notepad text will be set (via EM_REPLACESEL) to:
    """
    Replaced
    multiline text.
    """ ...
    
    After pressing ENTER, Notepad text will be set (via WM_SETTEXT) to:
    """
    Other
    replaced
    
    nmultiline text.
    """ ...
    
    For Python 2, also get the text from Notepad
        Original text length: 32
        Retrieved text length: 32
        Text: """
    Other
    replaced
    
    nmultiline text.
        """
    
    Done.
    

    As seen, it works with a normal Python string.

    Note: My Win user has "super" administrative privileges. For a normal user, some things might not work as expected.

    You could also take a look at [SO]: Keyboard event not sent to window with pywin32 (@CristiFati's answer) for handling WM_CHAR like messages, and more important: how to handle child windows.

    @EDIT0:

    Added:

    • WM_SETTEXT
    • WM_GETTEXT (Python 2 only) - to show how to get strings back from SendMessage

    But since WM_USER_STREAMING_UPDATE is beyond WM_USER (btw, I didn't see any documentation for it), things might / will not work (per @IInspectable's comment, and also SendMessage's documentation), so additional work (data marshaling) would be required.

    @EDIT1:

    I've already noticed that you're trying to work with AmiBroker (by Googleing WM_USER_STREAMING_UPDATE).
    However, I couldn't find any (official) documentation for that message, that would reveal what the WPARAM and LPARAM arguments are supposed to contain (like: [MS.Docs]: WM_SETTEXT message).
    Are you trying to write a plugin (meaning you're in the same process with AmiBroker), or you're simply trying to send messages to it (like I did in my example: Python -> Notepad)?