From 8fbc267158a823e54c9b0f54dd2c702fe9941644 Mon Sep 17 00:00:00 2001 From: jonschz Date: Sat, 29 Jun 2024 08:04:18 +0200 Subject: [PATCH 1/8] Implement static import for `ISequentialStream` (#474) --- comtypes/client/_generate.py | 1 + comtypes/objidl.py | 59 +++++++++++++++++++++++++++++++++++ comtypes/test/test_istream.py | 39 +++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 comtypes/objidl.py create mode 100644 comtypes/test/test_istream.py diff --git a/comtypes/client/_generate.py b/comtypes/client/_generate.py index f2e65941..f825a71b 100644 --- a/comtypes/client/_generate.py +++ b/comtypes/client/_generate.py @@ -272,6 +272,7 @@ def _get_known_namespaces() -> Tuple[ "comtypes.persist", "comtypes.typeinfo", "comtypes.automation", + "comtypes.objidl", "comtypes", "ctypes.wintypes", "ctypes", diff --git a/comtypes/objidl.py b/comtypes/objidl.py new file mode 100644 index 00000000..bbad9737 --- /dev/null +++ b/comtypes/objidl.py @@ -0,0 +1,59 @@ +from ctypes import Array, c_ubyte, c_ulong, HRESULT, POINTER, pointer +from typing import Tuple, TYPE_CHECKING + +from comtypes import COMMETHOD, GUID, IUnknown + + +class ISequentialStream(IUnknown): + """Defines methods for the stream objects in sequence.""" + + _iid_ = GUID("{0C733A30-2A1C-11CE-ADE5-00AA0044773D}") + _idlflags_ = [] + + _methods_ = [ + COMMETHOD( + [], + HRESULT, + "RemoteRead", + # This call only works if `pv` is pre-allocated with `cb` bytes, + # which cannot be done by the high level function generated by metaclasses. + # Therefore, we override the high level function to implement this behaviour + # and then delegate the call the raw COM method. + (["out"], POINTER(c_ubyte), "pv"), + (["in"], c_ulong, "cb"), + (["out"], POINTER(c_ulong), "pcbRead"), + ), + COMMETHOD( + [], + HRESULT, + "RemoteWrite", + (["in"], POINTER(c_ubyte), "pv"), + (["in"], c_ulong, "cb"), + (["out"], POINTER(c_ulong), "pcbWritten"), + ), + ] + + def RemoteRead(self, cb: int) -> Tuple["Array[c_ubyte]", int]: + """Reads a specified number of bytes from the stream object into memory + starting at the current seek pointer. + """ + # Behaves as if `pv` is pre-allocated with `cb` bytes by the high level func. + pv = (c_ubyte * cb)() + pcb_read = pointer(c_ulong(0)) + self.__com_RemoteRead(pv, c_ulong(cb), pcb_read) # type: ignore + return pv, pcb_read.contents.value + + if TYPE_CHECKING: + + def RemoteWrite(self, pv: "Array[c_ubyte]", cb: int) -> int: + """Writes a specified number of bytes into the stream object starting at + the current seek pointer. + """ + ... + + +# fmt: off +__known_symbols__ = [ + 'ISequentialStream', +] +# fmt: on diff --git a/comtypes/test/test_istream.py b/comtypes/test/test_istream.py new file mode 100644 index 00000000..4ccebe43 --- /dev/null +++ b/comtypes/test/test_istream.py @@ -0,0 +1,39 @@ +import unittest as ut + +from ctypes import POINTER, byref, c_bool, c_ubyte +import comtypes +import comtypes.client + +comtypes.client.GetModule("portabledeviceapi.dll") +from comtypes.gen.PortableDeviceApiLib import IStream + + +class Test_IStream(ut.TestCase): + def test_istream(self): + # Create an IStream + stream: IStream = POINTER(IStream)() # type: ignore + comtypes._ole32.CreateStreamOnHGlobal(None, c_bool(True), byref(stream)) + + test_data = "Some data".encode("utf-8") + pv = (c_ubyte * len(test_data)).from_buffer(bytearray(test_data)) + + written = stream.RemoteWrite(pv, len(test_data)) + self.assertEqual(written, len(test_data)) + # make sure the data actually gets written before trying to read back + stream.Commit(0) + + # Move the stream back to the beginning + STREAM_SEEK_SET = 0 + stream.RemoteSeek(0, STREAM_SEEK_SET) + + read_buffer_size = 1024 + + read_buffer, data_read = stream.RemoteRead(read_buffer_size) + + # Verification + self.assertEqual(data_read, len(test_data)) + self.assertEqual(bytearray(read_buffer)[0:data_read], test_data) + + +if __name__ == "__main__": + ut.main() From 6e25245ca16491b07ae8cc66ac1a79c2e5cc9ef2 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 30 Jun 2024 10:18:56 +0900 Subject: [PATCH 2/8] add `Test_KnownSymbols.test_symbols_in_comtypes_objidl` --- comtypes/test/test_client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/comtypes/test/test_client.py b/comtypes/test/test_client.py index 7ffa06fd..6a83533f 100644 --- a/comtypes/test/test_client.py +++ b/comtypes/test/test_client.py @@ -117,6 +117,11 @@ def test_symbols_in_comtypes(self): self._doit(comtypes) + def test_symbols_in_comtypes_objidl(self): + import comtypes.objidl + + self._doit(comtypes.objidl) + def test_symbols_in_comtypes_automation(self): import comtypes.automation From fb9ccdf2bf4c3ca69c487ba2cd1fc0fec94eb01f Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 30 Jun 2024 10:21:26 +0900 Subject: [PATCH 3/8] add `Test_GetModule.test_portabledeviceapi` --- comtypes/test/test_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/comtypes/test/test_client.py b/comtypes/test/test_client.py index 6a83533f..ddba1527 100644 --- a/comtypes/test/test_client.py +++ b/comtypes/test/test_client.py @@ -71,6 +71,12 @@ def test_mscorlib(self): # the `_Pointer` interface, rather than importing `_Pointer` from `ctypes`. self.assertTrue(issubclass(mod._Pointer, comtypes.IUnknown)) + def test_portabledeviceapi(self): + mod = comtypes.client.GetModule("portabledeviceapi.dll") + from comtypes.objidl import ISequentialStream + + self.assertTrue(issubclass(mod.IStream, ISequentialStream)) + def test_no_replacing_Patch_namespace(self): # NOTE: An object named `Patch` is defined in some dll. # Depending on how the namespace is defined in the static module, From 9309b51e0c7e9962a30fa5a5c0981951253498ad Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:06:52 +0900 Subject: [PATCH 4/8] split `Test_IStream` --- comtypes/test/test_istream.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/comtypes/test/test_istream.py b/comtypes/test/test_istream.py index 4ccebe43..bde2f90a 100644 --- a/comtypes/test/test_istream.py +++ b/comtypes/test/test_istream.py @@ -8,27 +8,41 @@ from comtypes.gen.PortableDeviceApiLib import IStream -class Test_IStream(ut.TestCase): - def test_istream(self): - # Create an IStream - stream: IStream = POINTER(IStream)() # type: ignore - comtypes._ole32.CreateStreamOnHGlobal(None, c_bool(True), byref(stream)) +def _create_stream() -> IStream: + # Create an IStream + stream = POINTER(IStream)() # type: ignore + comtypes._ole32.CreateStreamOnHGlobal(None, c_bool(True), byref(stream)) + return stream # type: ignore + +class Test_RemoteWrite(ut.TestCase): + def test_RemoteWrite(self): + stream = _create_stream() test_data = "Some data".encode("utf-8") pv = (c_ubyte * len(test_data)).from_buffer(bytearray(test_data)) written = stream.RemoteWrite(pv, len(test_data)) + + # Verification self.assertEqual(written, len(test_data)) - # make sure the data actually gets written before trying to read back - stream.Commit(0) + +class Test_RemoteRead(ut.TestCase): + def test_RemoteRead(self): + stream = _create_stream() + test_data = "Some data".encode("utf-8") + pv = (c_ubyte * len(test_data)).from_buffer(bytearray(test_data)) + stream.RemoteWrite(pv, len(test_data)) + + # Make sure the data actually gets written before trying to read back + stream.Commit(0) # Move the stream back to the beginning STREAM_SEEK_SET = 0 stream.RemoteSeek(0, STREAM_SEEK_SET) - read_buffer_size = 1024 + buffer_size = 1024 - read_buffer, data_read = stream.RemoteRead(read_buffer_size) + read_buffer, data_read = stream.RemoteRead(buffer_size) # Verification self.assertEqual(data_read, len(test_data)) From 551842da8b843580ae232b8404553e0b87eac20d Mon Sep 17 00:00:00 2001 From: jonschz Date: Fri, 5 Jul 2024 07:02:31 +0200 Subject: [PATCH 5/8] Rename objidl.py to stream.py --- comtypes/client/_generate.py | 2 +- comtypes/{objidl.py => stream.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename comtypes/{objidl.py => stream.py} (100%) diff --git a/comtypes/client/_generate.py b/comtypes/client/_generate.py index f825a71b..24ff7960 100644 --- a/comtypes/client/_generate.py +++ b/comtypes/client/_generate.py @@ -272,7 +272,7 @@ def _get_known_namespaces() -> Tuple[ "comtypes.persist", "comtypes.typeinfo", "comtypes.automation", - "comtypes.objidl", + "comtypes.stream", "comtypes", "ctypes.wintypes", "ctypes", diff --git a/comtypes/objidl.py b/comtypes/stream.py similarity index 100% rename from comtypes/objidl.py rename to comtypes/stream.py From 15bce0512c837c08529a0b600651c0440955c755 Mon Sep 17 00:00:00 2001 From: jonschz Date: Fri, 5 Jul 2024 07:27:37 +0200 Subject: [PATCH 6/8] Add RemoteRead explanation to stream.py --- comtypes/stream.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/comtypes/stream.py b/comtypes/stream.py index bbad9737..213186c6 100644 --- a/comtypes/stream.py +++ b/comtypes/stream.py @@ -11,6 +11,11 @@ class ISequentialStream(IUnknown): _idlflags_ = [] _methods_ = [ + # Note that these functions are called `Read` and `Write` in Microsoft's documentation, + # see https://learn.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-isequentialstream. + # However, the comtypes code generation detects these as `RemoteRead` and `RemoteWrite` + # for very subtle reasons, see e.g. https://stackoverflow.com/q/19820999/. We will not + # rename these in this manual import for the sake of consistency. COMMETHOD( [], HRESULT, @@ -41,6 +46,7 @@ def RemoteRead(self, cb: int) -> Tuple["Array[c_ubyte]", int]: pv = (c_ubyte * cb)() pcb_read = pointer(c_ulong(0)) self.__com_RemoteRead(pv, c_ulong(cb), pcb_read) # type: ignore + # return both `out` parameters return pv, pcb_read.contents.value if TYPE_CHECKING: From c9a4afcc507abd943852a1c7c5953e17c47b5ed9 Mon Sep 17 00:00:00 2001 From: jonschz Date: Fri, 5 Jul 2024 07:37:28 +0200 Subject: [PATCH 7/8] Fix refactoring issues --- comtypes/client/_generate.py | 2 +- comtypes/stream.py | 2 +- comtypes/test/test_client.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/comtypes/client/_generate.py b/comtypes/client/_generate.py index 24ff7960..26db88c6 100644 --- a/comtypes/client/_generate.py +++ b/comtypes/client/_generate.py @@ -262,7 +262,7 @@ def _get_known_namespaces() -> Tuple[ Note: The interfaces that should be included in `__known_symbols__` should be limited to those that can be said to be bound to the design concept of COM, such as - `IUnknown`, and those defined in `objidl` and `oaidl`. + `IUnknown`, and those defined in `stream` and `oaidl`. `comtypes` does NOT aim to statically define all COM object interfaces in its repository. """ diff --git a/comtypes/stream.py b/comtypes/stream.py index 213186c6..d7a3e6fa 100644 --- a/comtypes/stream.py +++ b/comtypes/stream.py @@ -13,7 +13,7 @@ class ISequentialStream(IUnknown): _methods_ = [ # Note that these functions are called `Read` and `Write` in Microsoft's documentation, # see https://learn.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-isequentialstream. - # However, the comtypes code generation detects these as `RemoteRead` and `RemoteWrite` + # However, the comtypes code generation detects these as `RemoteRead` and `RemoteWrite` # for very subtle reasons, see e.g. https://stackoverflow.com/q/19820999/. We will not # rename these in this manual import for the sake of consistency. COMMETHOD( diff --git a/comtypes/test/test_client.py b/comtypes/test/test_client.py index ddba1527..10d6d0e6 100644 --- a/comtypes/test/test_client.py +++ b/comtypes/test/test_client.py @@ -73,7 +73,7 @@ def test_mscorlib(self): def test_portabledeviceapi(self): mod = comtypes.client.GetModule("portabledeviceapi.dll") - from comtypes.objidl import ISequentialStream + from comtypes.stream import ISequentialStream self.assertTrue(issubclass(mod.IStream, ISequentialStream)) @@ -123,10 +123,10 @@ def test_symbols_in_comtypes(self): self._doit(comtypes) - def test_symbols_in_comtypes_objidl(self): - import comtypes.objidl + def test_symbols_in_comtypes_stream(self): + import comtypes.stream - self._doit(comtypes.objidl) + self._doit(comtypes.stream) def test_symbols_in_comtypes_automation(self): import comtypes.automation From 53179fa3a67fc5846cbf1c27d3ac747b9d1177be Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 6 Jul 2024 10:33:49 +0900 Subject: [PATCH 8/8] Apply suggestions from code review --- comtypes/client/_generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comtypes/client/_generate.py b/comtypes/client/_generate.py index 26db88c6..59785bc3 100644 --- a/comtypes/client/_generate.py +++ b/comtypes/client/_generate.py @@ -262,7 +262,7 @@ def _get_known_namespaces() -> Tuple[ Note: The interfaces that should be included in `__known_symbols__` should be limited to those that can be said to be bound to the design concept of COM, such as - `IUnknown`, and those defined in `stream` and `oaidl`. + `IUnknown`, `IDispatch` and `ITypeInfo`. `comtypes` does NOT aim to statically define all COM object interfaces in its repository. """