import unittest

from ctypes import PyDLL, py_object, c_void_p, byref, POINTER
from ctypes.wintypes import BOOL

from comtypes import IUnknown
from comtypes.client import CreateObject
from comtypes.automation import IDispatch

import pythoncom
import win32com.client

# We use the PyCom_PyObjectFromIUnknown function in pythoncom25.dll to
# convert a comtypes COM pointer into a pythoncom COM pointer.
# Fortunately this function is exported by the dll...
#
# This is the C prototype; we must pass 'True' as third argument:
#
# PyObject *PyCom_PyObjectFromIUnknown(IUnknown *punk, REFIID riid, BOOL bAddRef)

_PyCom_PyObjectFromIUnknown = PyDLL(pythoncom.__file__).PyCom_PyObjectFromIUnknown
_PyCom_PyObjectFromIUnknown.restype = py_object
_PyCom_PyObjectFromIUnknown.argtypes = (POINTER(IUnknown), c_void_p, BOOL)

def comtypes2pywin(ptr, interface=None):
    """Convert a comtypes pointer 'ptr' into a pythoncom
    PyI<interface> object.

    'interface' specifies the interface we want; it must be a comtypes
    interface class.  The interface must be implemented by the object;
    and the interface must be known to pythoncom.
    
    If 'interface' is specified, comtypes.IUnknown is used.
    """
    if interface is None:
        interface = IUnknown
    return _PyCom_PyObjectFromIUnknown(ptr, byref(interface._iid_), True)

################################################################

def comtypes_get_refcount(ptr):
    """Helper function for testing: return the COM reference count of
    a comtypes COM object"""
    ptr.AddRef()
    return ptr.Release()

from comtypes import COMObject

class MyComObject(COMObject):
    """A completely trivial COM object implementing IDispatch. Calling
    any methods will return the error code E_NOTIMPL (except the
    IUnknown methods; they are implemented in the base class."""
    _com_interfaces_ = [IDispatch]

################################################################

class Test(unittest.TestCase):
    def tearDown(self):
        if hasattr(self, "ie"):
            self.ie.Quit()
            del self.ie

    def test_mycomobject(self):
        o = MyComObject()
        p = comtypes2pywin(o, IDispatch)
        disp = win32com.client.Dispatch(p)
        self.failUnlessEqual(repr(disp), "<COMObject <unknown>>")

    def test_ie(self):
        # Convert a comtypes COM interface pointer into a win32com COM
        # pointer.
        ie = self.ie = CreateObject("InternetExplorer.Application")
        # The COM refcount of the created object is 1:
        self.failUnlessEqual(comtypes_get_refcount(ie), 1)
        # IE starts invisible:
        self.failUnlessEqual(ie.Visible, False)

        # Create a pythoncom PyIDispatch object from it:
        p = comtypes2pywin(ie, interface=IDispatch)
        self.failUnlessEqual(comtypes_get_refcount(ie), 2)

        # Make it usable...
        disp = win32com.client.Dispatch(p)
        self.failUnlessEqual(comtypes_get_refcount(ie), 2)
        self.failUnlessEqual(disp.Visible, False)

        # Cleanup and make sure that the COM refcounts are correct
        del p, disp
        self.failUnlessEqual(comtypes_get_refcount(ie), 1)

if __name__ == "__main__":
    unittest.main()
