Skip to content

Commit

Permalink
Implement SAFEARRAY pointers and SAFEARRAYs as method parameters of a…
Browse files Browse the repository at this point in the history
… Dispatch Interface (enthought#569)

* Implement safearray pointers as method parameters of a Dispatch Interface
to get server side modifications of safearrays marshaled back to the client.

* Rebase on main.

Added an interface to the C++ COM server for testing pointers to safearrays
as method parameres of a Dispatch Interface.

The implementation of the interface cannot share the IDispatch implementation
with other interfaces and requires a separate component.
(see https://learn.microsoft.com/en-us/cpp/atl/multiple-dual-interfaces)

Therefore the naming scheme of the existing component that implements the
test interface for record pointers was also changed.

* Added unittest for safearrays as Dispatch Interface method parameters.

* Fixed formating issues.

Fixed changes that reverted PR#561 (fix import part in automation).

* Apply suggestions from code review

Co-authored-by: Jun Komoda <[email protected]>

* Apply suggestions from code review

---------

Co-authored-by: Jun Komoda <[email protected]>
  • Loading branch information
geppi and junkmd authored Jun 25, 2024
1 parent a6aa153 commit 11d7651
Show file tree
Hide file tree
Showing 10 changed files with 583 additions and 13 deletions.
14 changes: 14 additions & 0 deletions comtypes/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ def _set_value(self, value):
ri.AddRef()
self._.pRecInfo = ri
self._.pvRecord = cast(value, c_void_p)
elif isinstance(ref, _Pointer) and isinstance(
ref.contents, _safearray.tagSAFEARRAY
):
self.vt = VT_ARRAY | ref._vartype_ | VT_BYREF
self._.pparray = cast(value, POINTER(POINTER(_safearray.tagSAFEARRAY)))
else:
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
elif isinstance(value, _Pointer):
Expand All @@ -422,6 +427,15 @@ def _set_value(self, value):
ri.AddRef()
self._.pRecInfo = ri
self._.pvRecord = cast(value, c_void_p)
elif isinstance(ref, _safearray.tagSAFEARRAY):
obj = _midlSAFEARRAY(value._itemtype_).create(value.unpack())
memmove(byref(self._), byref(obj), sizeof(obj))
self.vt = VT_ARRAY | obj._vartype_
elif isinstance(ref, _Pointer) and isinstance(
ref.contents, _safearray.tagSAFEARRAY
):
self.vt = VT_ARRAY | ref._vartype_ | VT_BYREF
self._.pparray = cast(value, POINTER(POINTER(_safearray.tagSAFEARRAY)))
else:
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
else:
Expand Down
89 changes: 89 additions & 0 deletions comtypes/test/test_dispifc_safearrays.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# coding: utf-8

from ctypes import byref, c_double, pointer
import unittest

import comtypes
import comtypes.safearray
from comtypes import CLSCTX_LOCAL_SERVER
from comtypes.client import CreateObject, GetModule

ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}"

try:
GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0))
import comtypes.gen.ComtypesCppTestSrvLib as ComtypesCppTestSrvLib

IMPORT_FAILED = False
except (ImportError, OSError):
IMPORT_FAILED = True


@unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.")
class Test_DispMethods(unittest.TestCase):
"""Test dispmethods with safearray and safearray pointer parameters."""

UNPACKED_ZERO_VALS = tuple(0.0 for _ in range(10))
UNPACKED_CONSECUTIVE_VALS = tuple(float(i) for i in range(10))

def _create_dispifc(self) -> "ComtypesCppTestSrvLib.IDispSafearrayParamTest":
# Explicitely ask for the dispinterface of the component.
return CreateObject(
"Comtypes.DispSafearrayParamTest",
clsctx=CLSCTX_LOCAL_SERVER,
interface=ComtypesCppTestSrvLib.IDispSafearrayParamTest,
)

def _create_zero_array(self):
return comtypes.safearray._midlSAFEARRAY(c_double).create(
[c_double() for _ in range(10)]
)

def _create_consecutive_array(self):
return comtypes.safearray._midlSAFEARRAY(c_double).create(
[c_double(i) for i in range(10)]
)

def test_inout_byref(self):
dispifc = self._create_dispifc()
# Passing a safearray by reference to a method that has declared the parameter
# as [in, out] we expect modifications of the safearray on the server side to
# also change the safearray on the client side.
test_array = self._create_zero_array()
self.assertEqual(test_array.unpack(), self.UNPACKED_ZERO_VALS)
dispifc.InitArray(byref(test_array))
self.assertEqual(test_array.unpack(), self.UNPACKED_CONSECUTIVE_VALS)

def test_inout_pointer(self):
dispifc = self._create_dispifc()
# Passing a safearray pointer to a method that has declared the parameter
# as [in, out] we expect modifications of the safearray on the server side to
# also change the safearray on the client side.
test_array = self._create_zero_array()
self.assertEqual(test_array.unpack(), self.UNPACKED_ZERO_VALS)
dispifc.InitArray(pointer(test_array))
self.assertEqual(test_array.unpack(), self.UNPACKED_CONSECUTIVE_VALS)

def test_in_safearray(self):
# Passing a safearray to a method that has declared the parameter just as [in]
# we expect modifications of the safearray on the server side NOT to change
# the safearray on the client side.
# We also need to test if the safearray gets properly passed to the method on
# the server side. For this, the 'VerifyArray' method returns 'True' if
# the safearray items have values equal to the initialization values
# provided by 'InitArray'.
for sa, expected, unpacked_content in [
(self._create_consecutive_array(), True, self.UNPACKED_CONSECUTIVE_VALS),
# Also perform the inverted test. For this, create a safearray with zeros.
(self._create_zero_array(), False, self.UNPACKED_ZERO_VALS),
]:
with self.subTest(expected=expected, unpacked_content=unpacked_content):
# Perform the check on initialization values.
self.assertEqual(self._create_dispifc().VerifyArray(sa), expected)
# Check if the safearray is unchanged although the method
# modifies the safearray on the server side.
self.assertEqual(sa.unpack(), unpacked_content)


if __name__ == "__main__":
unittest.main()
16 changes: 15 additions & 1 deletion source/CppTestSrv/CFACTORY.CPP
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,21 @@ HRESULT CFactory::UnregisterAll()
{
UnregisterServer(*(g_FactoryDataArray[i].m_pCLSID),
g_FactoryDataArray[i].m_szVerIndProgID,
g_FactoryDataArray[i].m_szProgID) ;
g_FactoryDataArray[i].m_szProgID,
g_FactoryDataArray[i].m_pLIBID) ;
// We can only unregister a TypeLib once.
// So if we just unregistered a TypeLib set all pointers to the same TypeLib to NULL.
if (g_FactoryDataArray[i].m_pLIBID != NULL)
{
const GUID* libid = g_FactoryDataArray[i].m_pLIBID ;
for(int j = 0 ; j < g_cFactoryDataEntries ; j++)
{
if (g_FactoryDataArray[j].m_pLIBID != NULL && g_FactoryDataArray[j].m_pLIBID == libid)
{
g_FactoryDataArray[j].m_pLIBID = NULL ;
}
}
}
}
return S_OK ;
}
Expand Down
Loading

0 comments on commit 11d7651

Please sign in to comment.