在Python中接收WM_COPYDATA

5 投票
1 回答
5110 浏览
提问于 2025-04-16 13:22

我正在尝试用Python读取一些应用程序(我正在尝试Spotify)发送给Windows Live Messenger的WM_COPYDATA消息,以更新“我正在听...”这句话。

根据我找到的信息,WM_COPYDATA消息是通过一个叫COPYDATASTRUCT的结构体发送的,结构大致如下:

  • dwData在我们的情况下是0x547,这样可以访问“正在收听”功能。
  • cbData是接收到的字符串的长度。
  • lpData是指向字符串本身的指针,可能包含Unicode字符。

这个字符串应该有以下格式:\0Music\0status\0format\0song\0artist\0album\0,具体说明可以参考ListeningNowTracker

WM_COPYDATA事件中,我们收到的lParam是一个包含COPYDATASTRUCT的指针。

我开始尝试使用pywin32的函数,记得之前的经验中它们不支持Unicode字符,所以我转向了ctypes。尽管这对我来说在Python中几乎是个新领域,我尝试了POINTER(),结果得到的都是我不认识的对象或者访问错误。

我认为代码应该创建一个COPYDATASTRUCT

class CopyDataStruct(Structure):
    _fields_ = [('dwData', c_int),
                ('cbData', c_int),
                ('lpData', c_void_p)]

然后让lParam指向这个结构体,从lpData获取字符串指针,最后用ctypes.string_at(lpData,cbData)获取字符串。

有什么建议吗?

更新 1

WM_COPYDATA事件是通过一个用win32gui构建的隐藏窗口接收的,专门用于这个目的。copydata事件连接到一个叫OnCopyData的函数,函数的头部如下:
def OnCopyData(self, hwnd, msg, wparam, lparam):
这个函数返回的值与Spy++消息日志中的值是正确的。

更新 2

这应该接近我想要的,但出现了NULL指针错误。

class CopyDataStruct(ctypes.Structure):
    _fields_ = [('dwData', c_int),
                ('cbData', c_int),
                ('lpData', c_wchar_p)]

PCOPYDATASTRUCT = ctypes.POINTER(CopyDataStruct)
pCDS = ctypes.cast(lparam,  PCOPYDATASTRUCT)
print ctypes.wstring_at(pCDS.contents.lpData)

1 个回答

7

我写了一个简单的win32gui应用程序:

import win32con, win32api, win32gui, ctypes, ctypes.wintypes

class COPYDATASTRUCT(ctypes.Structure):
    _fields_ = [
        ('dwData', ctypes.wintypes.LPARAM),
        ('cbData', ctypes.wintypes.DWORD),
        ('lpData', ctypes.c_void_p)
    ]
PCOPYDATASTRUCT = ctypes.POINTER(COPYDATASTRUCT)

class Listener:

    def __init__(self):
        message_map = {
            win32con.WM_COPYDATA: self.OnCopyData
        }
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = message_map
        wc.lpszClassName = 'MyWindowClass'
        hinst = wc.hInstance = win32api.GetModuleHandle(None)
        classAtom = win32gui.RegisterClass(wc)
        self.hwnd = win32gui.CreateWindow (
            classAtom,
            "win32gui test",
            0,
            0, 
            0,
            win32con.CW_USEDEFAULT, 
            win32con.CW_USEDEFAULT,
            0, 
            0,
            hinst, 
            None
        )
        print self.hwnd

    def OnCopyData(self, hwnd, msg, wparam, lparam):
        print hwnd
        print msg
        print wparam
        print lparam
        pCDS = ctypes.cast(lparam, PCOPYDATASTRUCT)
        print pCDS.contents.dwData
        print pCDS.contents.cbData
        print ctypes.wstring_at(pCDS.contents.lpData)
        return 1

l = Listener()
win32gui.PumpMessages()

然后我从另一个用Delphi写的应用程序发送了一个WM_COPYDATA消息给这个窗口:

Text := 'greetings!';
CopyData.cbData := (Length(Text)+1)*StringElementSize(Text);
CopyData.lpData := PWideChar(Text);
SendMessage(hwnd, WM_COPYDATA, Handle, NativeInt(@CopyData));

输出结果是:

461584
461584
74
658190
2620592
42
22
greetings!

所以看起来这个功能正常,基本上就是你写的那样。

我能想到的唯一问题是,Spotify的COPYDATASTRUCT里的文本没有以空字符结束。你可以通过读取数据来轻松检查这一点。记得使用cbData这个成员。

撰写回答