Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Common Item Dialog: ctypes + COM access violation

I am trying to use the ctypes module to make calls to Windows' Common Item Dialog API. The code shown below is roughly based on the steps outlined in the MSDN documentation. Its only dependency is the comtypes.GUID module.

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)

Everything works as intended until the first call to show(0):

 WindowsError: exception: access violation reading 0xXXXXXXXX

(Output may vary.) For comparison, I have carried out the same steps in AutoHotkey_L, which has direct access to 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)

The resulting macro pops up an Open File dialog, as expected. The vtable pointer offsets are the same in both cases, but only the Python version throws up an access violation.

Can anyone shed some light on this?

[I apologize for not adding more hyperlinks where appropriate, but as a new user I am limited to two at a time.]

Background: I am putting together a lightweight module which provides a native save/open file dialog for use in Python scripts. So far I have been unable to find an implementation in pure Python. Those that exist rely on UI toolkits such as Tkinter or wxPython.

like image 202
reiv Avatar asked Sep 28 '12 01:09

reiv


1 Answers

Here is the solution:

COM methods take an additional parameter: The 'this' pointer. It is implicit when you call the method from C++, in C (and in ctypes) you must supply it yourself.

Change the line

func_proto = ctypes.WINFUNCTYPE(HRESULT, HWND)

into

func_proto = ctypes.WINFUNCTYPE(HRESULT, c_long, HWND)

and this line

show(0)

into

show(ptr, 0)

and your code will work.

like image 172
theller Avatar answered Oct 02 '22 12:10

theller