Skip to content

Commit

Permalink
Implement record pointers as method parameters of a Dispatch Interface (
Browse files Browse the repository at this point in the history
#535)

* Implement record pointers as method parameters of a Dispatch Interface

to get server side modifications of a record marshaled back to the client.

* Adding the source files for a simple out of process COM-server under
'source/OutProcSrv'. The COM-server implements a dual interface and
the corresponding dispinterface with currently just a single method
'InitRecord' for testing record pointer parameters.
The added unittest 'test_dispifc_recordptr.py' passes a record by
reference and also as a record pointer to the 'InitRecord' method
of the dispinterface and checks the initialized records.

* Modified the testing workflow to compile the out of process
COM-server and register it.

* Added test for passing a record as a pure [in] parameter.

* Apply suggestions from code review

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

* Apply suggestions from code review

* Apply suggestions from code review

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

* Commit for renaming.

Changed the names of:
- Interfaces
- Component
- ProgID
- Type Library

Renamed the source files of the C++ server component to reflect the component name.
Changed the name of the C++ COM server sources subdirectory.

* Renamed the structure used for record parameter testing.

* Fixed typo that rendered a subtest useless.

* Apply suggestions from code review

---------

Co-authored-by: Jun Komoda <[email protected]>
  • Loading branch information
geppi and junkmd authored May 31, 2024
1 parent cc071fe commit 2963afd
Show file tree
Hide file tree
Showing 17 changed files with 2,157 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/autotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ jobs:
python setup.py install
pip uninstall comtypes -y
python test_pip_install.py
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build and register the OutProc COM server
run: |
cd source/CppTestSrv
nmake /f Makefile
./server.exe /RegServer
- name: unittest comtypes
run: |
python -m unittest discover -v -s ./comtypes/test -t comtypes\test
- name: Unregister the OutProc COM server
run: |
cd source/CppTestSrv
./server.exe /UnregServer
28 changes: 26 additions & 2 deletions comtypes/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,36 @@ def _set_value(self, value):
ref = value._obj
self._.c_void_p = addressof(ref)
self.__keepref = value
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"):
guids = ref._recordinfo_
from comtypes.typeinfo import GetRecordInfoFromGuids

ri = GetRecordInfoFromGuids(*guids)
self.vt = VT_RECORD | VT_BYREF
# Assigning a COM pointer to a structure field does NOT
# call AddRef(), have to call it manually:
ri.AddRef()
self._.pRecInfo = ri
self._.pvRecord = cast(value, c_void_p)
else:
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
elif isinstance(value, _Pointer):
ref = value.contents
self._.c_void_p = addressof(ref)
self.__keepref = value
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"):
guids = ref._recordinfo_
from comtypes.typeinfo import GetRecordInfoFromGuids

ri = GetRecordInfoFromGuids(*guids)
self.vt = VT_RECORD | VT_BYREF
# Assigning a COM pointer to a structure field does NOT
# call AddRef(), have to call it manually:
ri.AddRef()
self._.pRecInfo = ri
self._.pvRecord = cast(value, c_void_p)
else:
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
else:
raise TypeError("Cannot put %r in VARIANT" % value)
# buffer -> SAFEARRAY of VT_UI1 ?
Expand Down
90 changes: 90 additions & 0 deletions comtypes/test/test_dispifc_records.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# coding: utf-8

import unittest

from comtypes import CLSCTX_LOCAL_SERVER
from comtypes.client import CreateObject, GetModule
from ctypes import byref, pointer

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 record and record pointer parameters."""

EXPECTED_INITED_QUESTIONS = "The meaning of life, the universe and everything?"

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

def test_inout_byref(self):
dispifc = self._create_dispifc()
# Passing a record by reference to a method that has declared the parameter
# as [in, out] we expect modifications of the record on the server side to
# also change the record on the client side.
test_record = ComtypesCppTestSrvLib.StructRecordParamTest()
self.assertEqual(test_record.question, None)
self.assertEqual(test_record.answer, 0)
self.assertEqual(test_record.needs_clarification, False)
dispifc.InitRecord(byref(test_record))
self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS)
self.assertEqual(test_record.answer, 42)
self.assertEqual(test_record.needs_clarification, True)

def test_inout_pointer(self):
dispifc = self._create_dispifc()
# Passing a record pointer to a method that has declared the parameter
# as [in, out] we expect modifications of the record on the server side to
# also change the record on the client side.
test_record = ComtypesCppTestSrvLib.StructRecordParamTest()
self.assertEqual(test_record.question, None)
self.assertEqual(test_record.answer, 0)
self.assertEqual(test_record.needs_clarification, False)
dispifc.InitRecord(pointer(test_record))
self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS)
self.assertEqual(test_record.answer, 42)
self.assertEqual(test_record.needs_clarification, True)

def test_in_record(self):
# Passing a record to a method that has declared the parameter just as [in]
# we expect modifications of the record on the server side NOT to change
# the record on the client side.
# We also need to test if the record gets properly passed to the method on
# the server side. For this, the 'VerifyRecord' method returns 'True' if
# all record fields have values equivalent to the initialization values
# provided by 'InitRecord'.
inited_record = ComtypesCppTestSrvLib.StructRecordParamTest()
inited_record.question = self.EXPECTED_INITED_QUESTIONS
inited_record.answer = 42
inited_record.needs_clarification = True
for rec, expected, (q, a, nc) in [
(inited_record, True, (self.EXPECTED_INITED_QUESTIONS, 42, True)),
# Also perform the inverted test. For this, create a blank record.
(ComtypesCppTestSrvLib.StructRecordParamTest(), False, (None, 0, False)),
]:
with self.subTest(expected=expected, q=q, a=a, nc=nc):
# Perform the check on initialization values.
self.assertEqual(self._create_dispifc().VerifyRecord(rec), expected)
self.assertEqual(rec.question, q)
# Check if the 'answer' field is unchanged although the method
# modifies this field on the server side.
self.assertEqual(rec.answer, a)
self.assertEqual(rec.needs_clarification, nc)


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 2963afd

Please sign in to comment.