Windows Common Item对话框:ctypes + COM访问冲突

rei*_*eiv 7 python windows com ctypes access-violation

我正在尝试使用该ctypes模块来调用Windows的Common Item Dialog API.下面显示的代码大致基于MSDN文档中列出的步骤.它唯一的依赖是comtypes.GUID模块.

import ctypes
from ctypes import byref, POINTER, c_int, c_long
from ctypes.wintypes import HWND, HRESULT
from comtypes import GUID

CLSID_FileOpenDialog = '{DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7}'
IID_IFileDialog = '{42F85136-DB7E-439C-85F1-E4075D135FC8}'
#IID_IFileOpenDialog = '{D57C7288-D4AD-4768-BE02-9D969532D960}'
CLSCTX_SERVER = 5
COINIT_APARTMENTTHREADED = 2
FOS_PICKFOLDERS = 32
FOS_FORCEFILESYSTEM = 64

ole32 = ctypes.windll.ole32
CoCreateInstance = ole32.CoCreateInstance
CoInitializeEx = ole32.CoInitializeEx

CoInitializeEx(None, COINIT_APARTMENTTHREADED)

ptr = c_int()
error = CoCreateInstance(
    byref(GUID(CLSID_FileOpenDialog)), None, CLSCTX_SERVER,
    byref(GUID(IID_IFileDialog)), byref(ptr))
assert error == 0

ptr = ptr.value
c_long_p = ctypes.POINTER(ctypes.c_int)
print('Pointer to COM object: %s' % ptr)
vtable = ctypes.cast(ptr, c_long_p).contents.value
print('Pointer to vtable: %s' % vtable)

func_proto = ctypes.WINFUNCTYPE(HRESULT, HWND)

# Calculating function pointer offset: 3rd entry in vtable; 32-bit => 4 bytes
show_p = ctypes.cast(vtable + 3*4, c_long_p).contents.value
print('Pointer to show(): %s' % show_p)
show = func_proto(show_p)
show(0)
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作,直到第一次调用show(0):

 WindowsError: exception: access violation reading 0xXXXXXXXX
Run Code Online (Sandbox Code Playgroud)

(输出可能会有所不同.)为了进行比较,我在AutoHotkey_L中执行了相同的步骤,它可以直接访问COM.

CLSID := "{DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7}"
IID := "{42F85136-DB7E-439C-85F1-E4075D135FC8}"

ptr := ComObjCreate(CLSID, IID)
vtable := NumGet(ptr + 0, 0, "Ptr")
    show := NumGet(vtbl + 0, 3 * A_PtrSize, "Ptr")

MsgBox ptr: %ptr% vtable: %vtable% show: %A_PtrSize%

DllCall(show, "Ptr", ptr, "Ptr", 44)
Run Code Online (Sandbox Code Playgroud)

生成的宏会弹出一个"打开文件"对话框,如预期的那样.在两种情况下,vtable指针偏移都是相同的,但只有Python版本才会引发访问冲突.

任何人都可以对此有所了解吗?

[我很抱歉没有在适当的时候添加更多的超链接,但作为一个新用户,我一次只限于两个.]

背景:我正在整理一个轻量级模块,它提供了一个本机保存/打开文件对话框,可以在Python脚本中使用.到目前为止,我一直无法在纯Python中找到实现.那些存在依赖于UI工具包,如Tkinter或wxPython.

the*_*ler 6

这是解决方案:

COM方法需要一个额外的参数:'this'指针.当你从C++调用方法时,它是隐式的,在C(和ctypes中)你必须自己提供它.

改变线

func_proto = ctypes.WINFUNCTYPE(HRESULT, HWND)
Run Code Online (Sandbox Code Playgroud)

func_proto = ctypes.WINFUNCTYPE(HRESULT, c_long, HWND)
Run Code Online (Sandbox Code Playgroud)

这条线

show(0)
Run Code Online (Sandbox Code Playgroud)

show(ptr, 0)
Run Code Online (Sandbox Code Playgroud)

你的代码会起作用.

  • 如果你想试验:_ctypes模块中有一个无文档的call_commethod()函数,它可以使你的代码更加简单.我没有时间尝试一下; 您可以看到的唯一非常旧的示例代码(此代码比comtypes更旧):http://ctypes.cvs.sourceforge.net/viewvc/ctypes/ctypes/samples/Windows/COM.py?hideattic= 0&revision = 1.6&view = markup使用指向COM对象的指针,vtable中的com方法的索引(在您的情况下为3)和参数元组调用它.(可选)提供包含参数类型的元组. (2认同)