Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement record pointers as method parameters of a Dispatch Interface #535

Merged
merged 12 commits into from
May 31, 2024
Merged
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(unittest.TestCase):
junkmd marked this conversation as resolved.
Show resolved Hide resolved
"""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,
)

geppi marked this conversation as resolved.
Show resolved Hide resolved
def test_byref(self):
junkmd marked this conversation as resolved.
Show resolved Hide resolved
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)

geppi marked this conversation as resolved.
Show resolved Hide resolved
def test_pointer(self):
junkmd marked this conversation as resolved.
Show resolved Hide resolved
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)

geppi marked this conversation as resolved.
Show resolved Hide resolved
def test_record(self):
junkmd marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading