使用 ctypes 调用 EnumRunning 方法时出现 ValueError 异常 "Procedure probably called with not enough arguments (4 bytes missing)"

ValueError exception "Procedure probably called with not enough arguments (4 bytes missing)" when calling the EnumRunning method using ctypes

在 Python 我正在尝试调用 Windows API EnumRunning method of the IRunningObjectTable interface that I get from the GetRunningObjectTable function using the comtypes and ctypes modules (I want to avoid using other non-standard modules such as pythoncom).

我主要是从C:\Program Files\Windows Kits\Include.0.17763.0\um中的moniker.py and persist.py and the objbase.h header file页面中检索和修改代码。

当我尝试调用 EnumRunning 方法时,引发了 ValueError 异常:

Procedure probably called with not enough arguments (4 bytes missing)

异常消息来自文件 _ctypes.pyd,因为如果我在文件中修改它,它确实会发生变化,但我无法阅读其中包含的已编译 Python 代码来尝试理解原因正在筹集中。

ValueError 异常的文档是:

Raised when an operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.

所以问题可能出在我从 GetRunningObjectTable 函数获得的 IRunningObjectTable 接口的值上。虽然函数确实 return HRESULT S_OK value 所以我不确定。

我已经搜索了异常消息,但我得到的信息是关于调用约定的,并且缺少一些参数,我已经检查过但似乎并非如此。

如果我尝试用另一个参数调用它,我会得到 TypeError 异常:

this function takes 1 argument (2 given)

只有一个参数,所以顺序是正确的,尝试使用 cdlloledll 而不是 windll 也没有用。

代码如下:

from comtypes import GUID, IUnknown, STDMETHOD
from ctypes import byref, c_void_p, HRESULT, POINTER, Structure, windll
from ctypes.wintypes import BOOL, DWORD, FILETIME, LARGE_INTEGER, LPCVOID, LPOLESTR, ULARGE_INTEGER, ULONG

CLSID = GUID
IID = GUID
REFIID = POINTER(IID)

class STATSTG(Structure):
  _fields_ = [
    ('pwcsName', LPOLESTR),
    ('type', DWORD),
    ('cbSize', ULARGE_INTEGER),
    ('mtime', FILETIME),
    ('ctime', FILETIME),
    ('atime', FILETIME),
    ('grfMode', DWORD),
    ('grfLocksSupported', DWORD),
    ('clsid', CLSID),
    ('grfStateBits', DWORD),
    ('reserved', DWORD)]

class BIND_OPTS(Structure):
  _fields_ = [
    ('cbStruct', DWORD),
    ('grfFlags', DWORD),
    ('grfMode', DWORD),
    ('dwTickCountDeadline', DWORD)]

class IEnumString(IUnknown):
  _iid_ = GUID('{00000101-0000-0000-C000-000000000046}')

class ISequentialStream(IUnknown):
  _iid_ = GUID('{0c733a30-2a1c-11ce-ade5-00aa0044773d}')
  _methods_ = IUnknown._methods_ + [
    STDMETHOD(HRESULT, 'Read', [c_void_p, ULONG, POINTER(ULONG)]),
    STDMETHOD(HRESULT, 'Write', [LPCVOID, ULONG, POINTER(ULONG)])]

class IStream(ISequentialStream):
  _iid_ = GUID('{0000000C-0000-0000-C000-000000000046}')
  _methods_ = IUnknown._methods_

class IEnumMoniker(IUnknown):
  _iid_ = GUID('{00000102-0000-0000-C000-000000000046}')

class IBindCtx(IUnknown):
  _iid_ = GUID('{0000000E-0000-0000-C000-000000000046}')

class IRunningObjectTable(IUnknown):
  _iid_ = GUID('{00000010-0000-0000-C000-000000000046}')

class IPersist(IUnknown):
  _iid_ = GUID('{0000010C-0000-0000-C000-000000000046}')
  _methods_ = IUnknown._methods_ + [
    STDMETHOD(HRESULT, 'GetClassID', [POINTER(GUID)])]

class IPersistStream(IPersist):
  _iid_ = GUID('{00000109-0000-0000-C000-000000000046}')
  _methods_ = IPersist._methods_ + [
    STDMETHOD(HRESULT, 'IsDirty'),
    STDMETHOD(HRESULT, 'Load', [POINTER(IStream)]),
    STDMETHOD(HRESULT, 'Save', [POINTER(IStream), BOOL]),
    STDMETHOD(HRESULT, 'GetSizeMax', [POINTER(ULARGE_INTEGER)])]

class IMoniker(IPersistStream):
  _iid_ = GUID('{0000000F-0000-0000-C000-000000000046}')

IEnumString._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'Next', [ULONG, POINTER(LPOLESTR), POINTER(ULONG)]),
  STDMETHOD(HRESULT, 'Skip', [ULONG]),
  STDMETHOD(HRESULT, 'Reset'),
  STDMETHOD(HRESULT, 'Clone', [POINTER(POINTER(IEnumString))])]

IStream._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'Seek', [LARGE_INTEGER, DWORD, POINTER(ULARGE_INTEGER)]),
  STDMETHOD(HRESULT, 'SetSize', [ULARGE_INTEGER]),
  STDMETHOD(HRESULT, 'CopyTo', [ULARGE_INTEGER, ULARGE_INTEGER, POINTER(ULARGE_INTEGER)]),
  STDMETHOD(HRESULT, 'Commit', [DWORD]),
  STDMETHOD(HRESULT, 'Revert'),
  STDMETHOD(HRESULT, 'LockRegion', [ULARGE_INTEGER, ULARGE_INTEGER, DWORD]),
  STDMETHOD(HRESULT, 'UnlockRegion', [ULARGE_INTEGER, ULARGE_INTEGER, DWORD]),
  STDMETHOD(HRESULT, 'Stat', [POINTER(STATSTG), DWORD]),
  STDMETHOD(HRESULT, 'Clone', [POINTER(POINTER(IStream))])]

IEnumMoniker._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'Next', [ULONG, POINTER(POINTER(IMoniker)), POINTER(ULONG)]),
  STDMETHOD(HRESULT, 'Skip', [ULONG]),
  STDMETHOD(HRESULT, 'Reset'),
  STDMETHOD(HRESULT, 'Clone', [POINTER(POINTER(IEnumMoniker))])]

IBindCtx._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'RegisterObjectBound', [POINTER(IUnknown)]),
  STDMETHOD(HRESULT, 'RevokeObjectBound', [POINTER(IUnknown)]),
  STDMETHOD(HRESULT, 'ReleaseBoundObjects'),
  STDMETHOD(HRESULT, 'SetBindOptions', [POINTER(BIND_OPTS)]),
  STDMETHOD(HRESULT, 'GetBindOptions', [POINTER(BIND_OPTS)]),
  STDMETHOD(HRESULT, 'GetRunningObjectTable', [POINTER(POINTER(IRunningObjectTable))]),
  STDMETHOD(HRESULT, 'RegisterObjectParam', [LPOLESTR, POINTER(IUnknown)]),
  STDMETHOD(HRESULT, 'GetObjectParam', [LPOLESTR, POINTER(POINTER(IUnknown))]),
  STDMETHOD(HRESULT, 'EnumObjectParam', [POINTER(POINTER(IEnumString))]),
  STDMETHOD(HRESULT, 'RevokeObjectParam', [LPOLESTR])]

IRunningObjectTable._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'Register', [DWORD, POINTER(IUnknown), POINTER(IMoniker), POINTER(DWORD)]),
  STDMETHOD(HRESULT, 'Revoke', [DWORD]),
  STDMETHOD(HRESULT, 'IsRunning', [POINTER(IMoniker)]),
  STDMETHOD(HRESULT, 'GetObject', [POINTER(IMoniker), POINTER(POINTER(IUnknown))]),
  STDMETHOD(HRESULT, 'NoteChangeTime', [DWORD, POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'GetTimeOfLastChange', [POINTER(IMoniker), POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'EnumRunning', [POINTER(POINTER(IEnumMoniker))])]

IMoniker._methods_ = IPersistStream._methods_ + [
  STDMETHOD(HRESULT, 'BindToObject', [POINTER(IBindCtx), POINTER(IMoniker), REFIID, POINTER(c_void_p)]),
  STDMETHOD(HRESULT, 'BindToStorage', [POINTER(IBindCtx), POINTER(IMoniker), REFIID, POINTER(c_void_p)]),
  STDMETHOD(HRESULT, 'Reduce', [POINTER(IBindCtx), DWORD, POINTER(POINTER(IMoniker)), POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'ComposeWith', [POINTER(IMoniker), BOOL, POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'Enum', [BOOL, POINTER(POINTER(IEnumMoniker))]),
  STDMETHOD(HRESULT, 'IsEqual', [POINTER(IMoniker)]),
  STDMETHOD(HRESULT, 'Hash', [POINTER(DWORD)]),
  STDMETHOD(HRESULT, 'IsRunning', [POINTER(IBindCtx), POINTER(IMoniker), POINTER(IMoniker)]),
  STDMETHOD(HRESULT, 'GetTimeOfLastChange', [POINTER(IBindCtx), POINTER(IMoniker), POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'Inverse', [POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'CommonPrefixWith', [POINTER(IMoniker), POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'RelativePathTo', [POINTER(IMoniker), POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'GetDisplayName', [POINTER(IBindCtx), POINTER(IMoniker), POINTER(LPOLESTR)]),
  STDMETHOD(HRESULT, 'ParseDisplayName', [POINTER(IBindCtx), POINTER(IMoniker), LPOLESTR, POINTER(ULONG), POINTER(POINTER(IMoniker))]),
  STDMETHOD(HRESULT, 'IsSystemMoniker', [POINTER(DWORD)])]

S_OK = 0

RunningObjectTable = POINTER(IRunningObjectTable)()

GetRunningObjectTable = windll.ole32.GetRunningObjectTable
GetRunningObjectTable.argtypes = [DWORD, POINTER(POINTER(IRunningObjectTable))]
GetRunningObjectTable.restype = HRESULT

if GetRunningObjectTable(0, byref(RunningObjectTable)) != S_OK:
  raise Exception('GetRunningObjectTable failed.')

EnumMoniker = POINTER(IEnumMoniker)()

try:
  RunningObjectTable.EnumRunning(byref(EnumMoniker))
except ValueError as exception:
  print(exception)

input()

我找到了 NV Access 的 NVDA(非可视化桌面访问)GitHub 存储库,其中的文件 objidl.py 包含了它们的实现,并且在一点一点地修改我的实现方式之后,我发现问题是我将 IUnknown._methods_ 添加到每个接口的方法中。

所以不要像这样声明方法:

IRunningObjectTable._methods_ = IUnknown._methods_ + [
  STDMETHOD(HRESULT, 'Register', [DWORD, POINTER(IUnknown), POINTER(IMoniker), POINTER(DWORD)]),
  STDMETHOD(HRESULT, 'Revoke', [DWORD]),
  STDMETHOD(HRESULT, 'IsRunning', [POINTER(IMoniker)]),
  STDMETHOD(HRESULT, 'GetObject', [POINTER(IMoniker), POINTER(POINTER(IUnknown))]),
  STDMETHOD(HRESULT, 'NoteChangeTime', [DWORD, POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'GetTimeOfLastChange', [POINTER(IMoniker), POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'EnumRunning', [POINTER(POINTER(IEnumMoniker))])]

我应该这样声明它们:

IRunningObjectTable._methods_ = [
  STDMETHOD(HRESULT, 'Register', [DWORD, POINTER(IUnknown), POINTER(IMoniker), POINTER(DWORD)]),
  STDMETHOD(HRESULT, 'Revoke', [DWORD]),
  STDMETHOD(HRESULT, 'IsRunning', [POINTER(IMoniker)]),
  STDMETHOD(HRESULT, 'GetObject', [POINTER(IMoniker), POINTER(POINTER(IUnknown))]),
  STDMETHOD(HRESULT, 'NoteChangeTime', [DWORD, POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'GetTimeOfLastChange', [POINTER(IMoniker), POINTER(FILETIME)]),
  STDMETHOD(HRESULT, 'EnumRunning', [POINTER(POINTER(IEnumMoniker))])]

(我编辑了答案,因为起初我认为解决问题的方法是使用 COMMETHOD 函数而不是 STDMETHOD 函数,但现在发现情况并非如此全部)