From 2e3bacaaa43a03b9ed0b38a93379a37b4cc709dc Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 1/6] test: Add tests for `CoGetClassObject` and `IClassFactory.CreateInstance`. --- comtypes/test/test_classfactory.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 comtypes/test/test_classfactory.py diff --git a/comtypes/test/test_classfactory.py b/comtypes/test/test_classfactory.py new file mode 100644 index 00000000..fa44d356 --- /dev/null +++ b/comtypes/test/test_classfactory.py @@ -0,0 +1,30 @@ +import unittest as ut + +from comtypes import GUID, CoGetClassObject, shelllink +from comtypes.server import IClassFactory + +CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") + +REGDB_E_CLASSNOTREG = -2147221164 # 0x80040154 + + +class Test_CreateInstance(ut.TestCase): + def test_from_CoGetClassObject(self): + class_factory = CoGetClassObject(CLSID_ShellLink) + self.assertIsInstance(class_factory, IClassFactory) + shlnk = class_factory.CreateInstance(interface=shelllink.IShellLinkW) + self.assertIsInstance(shlnk, shelllink.IShellLinkW) + shlnk.SetDescription("sample") + self.assertEqual(shlnk.GetDescription(), "sample") + + def test_raises_valueerror_if_takes_dynamic_true_and_interface_explicitly(self): + class_factory = CoGetClassObject(CLSID_ShellLink) + self.assertIsInstance(class_factory, IClassFactory) + with self.assertRaises(ValueError): + class_factory.CreateInstance(interface=shelllink.IShellLinkW, dynamic=True) + + def test_raises_class_not_reg_error_if_non_existent_clsid(self): + # calling `CoGetClassObject` with a non-existent CLSID raises an `OSError`. + with self.assertRaises(OSError) as cm: + CoGetClassObject(GUID.create_new()) + self.assertEqual(cm.exception.winerror, REGDB_E_CLASSNOTREG) From a98c5492b8fea116efe6c454d4af1e96522989d0 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 2/6] refactor: Convert `CoGetClassObject` annotations from comment-based to inline. --- comtypes/_post_coinit/misc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/comtypes/_post_coinit/misc.py b/comtypes/_post_coinit/misc.py index 39b1b0d3..1607f80b 100644 --- a/comtypes/_post_coinit/misc.py +++ b/comtypes/_post_coinit/misc.py @@ -160,8 +160,12 @@ def CoGetClassObject( ) -> _T_IUnknown: ... -def CoGetClassObject(clsid, clsctx=None, pServerInfo=None, interface=None): - # type: (GUID, Optional[int], Optional[COSERVERINFO], Optional[Type[IUnknown]]) -> IUnknown +def CoGetClassObject( + clsid: GUID, + clsctx: Optional[int] = None, + pServerInfo: "Optional[COSERVERINFO]" = None, + interface: Optional[type[IUnknown]] = None, +) -> IUnknown: if clsctx is None: clsctx = CLSCTX_SERVER if interface is None: From 62d07f7d2bc3a6f6af4e1c5b2831919d448daef0 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 3/6] fix: Correct type hint for `punkouter` in `IClassFactory.CreateInstance`. Removed erroneous `type` annotation from `punkouter` parameter. --- comtypes/server/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comtypes/server/__init__.py b/comtypes/server/__init__.py index 02fbd868..ca6e2c3d 100644 --- a/comtypes/server/__init__.py +++ b/comtypes/server/__init__.py @@ -30,7 +30,7 @@ class IClassFactory(IUnknown): def CreateInstance( self, - punkouter: Optional[type["_Pointer[IUnknown]"]] = None, + punkouter: Optional["_Pointer[IUnknown]"] = None, interface: Optional[type[IUnknown]] = None, dynamic: bool = False, ) -> Any: From f0601ba8c166dabc00a8978f001ce04b0d923010 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 4/6] refactor: Enhance `IClassFactory.CreateInstance` with `overload` type hints. --- comtypes/server/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/comtypes/server/__init__.py b/comtypes/server/__init__.py index ca6e2c3d..4f9473dc 100644 --- a/comtypes/server/__init__.py +++ b/comtypes/server/__init__.py @@ -1,6 +1,6 @@ import ctypes from ctypes import HRESULT, POINTER, byref -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, overload import comtypes import comtypes.client @@ -15,6 +15,9 @@ from comtypes import hints # type: ignore +_T_IUnknown = TypeVar("_T_IUnknown", bound=IUnknown) + + ################################################################ # Interfaces class IClassFactory(IUnknown): @@ -28,6 +31,20 @@ class IClassFactory(IUnknown): STDMETHOD(HRESULT, "LockServer", [ctypes.c_int]), ] + @overload + def CreateInstance( + self, + punkouter: Optional["_Pointer[IUnknown]"] = None, + interface: type[_T_IUnknown] = IUnknown, + dynamic: Literal[False] = False, + ) -> _T_IUnknown: ... + @overload + def CreateInstance( + self, + punkouter: Optional["_Pointer[IUnknown]"] = None, + interface: None = None, + dynamic: Literal[True] = True, + ) -> Any: ... def CreateInstance( self, punkouter: Optional["_Pointer[IUnknown]"] = None, From 08749627c9d2320ccb0a824484f15959a3e4cb0a Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 5/6] test: Refine `test_classfactory` for clarity and type checker compatibility. --- comtypes/test/test_classfactory.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/comtypes/test/test_classfactory.py b/comtypes/test/test_classfactory.py index fa44d356..a8aaae4a 100644 --- a/comtypes/test/test_classfactory.py +++ b/comtypes/test/test_classfactory.py @@ -9,7 +9,7 @@ class Test_CreateInstance(ut.TestCase): - def test_from_CoGetClassObject(self): + def test_returns_specified_interface_type_instance(self): class_factory = CoGetClassObject(CLSID_ShellLink) self.assertIsInstance(class_factory, IClassFactory) shlnk = class_factory.CreateInstance(interface=shelllink.IShellLinkW) @@ -21,7 +21,10 @@ def test_raises_valueerror_if_takes_dynamic_true_and_interface_explicitly(self): class_factory = CoGetClassObject(CLSID_ShellLink) self.assertIsInstance(class_factory, IClassFactory) with self.assertRaises(ValueError): - class_factory.CreateInstance(interface=shelllink.IShellLinkW, dynamic=True) + class_factory.CreateInstance( # type: ignore + interface=shelllink.IShellLinkW, + dynamic=True, # type: ignore + ) def test_raises_class_not_reg_error_if_non_existent_clsid(self): # calling `CoGetClassObject` with a non-existent CLSID raises an `OSError`. From 3d0c1ec30921a3c7667a0bcf03c35d97ac4d9158 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 17 Jan 2026 16:03:21 +0900 Subject: [PATCH 6/6] test: Add test for `IClassFactory.CreateInstance` returning `IUnknown`. --- comtypes/test/test_classfactory.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/comtypes/test/test_classfactory.py b/comtypes/test/test_classfactory.py index a8aaae4a..5bd247dd 100644 --- a/comtypes/test/test_classfactory.py +++ b/comtypes/test/test_classfactory.py @@ -1,6 +1,6 @@ import unittest as ut -from comtypes import GUID, CoGetClassObject, shelllink +from comtypes import GUID, CoGetClassObject, IUnknown, shelllink from comtypes.server import IClassFactory CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") @@ -17,6 +17,16 @@ def test_returns_specified_interface_type_instance(self): shlnk.SetDescription("sample") self.assertEqual(shlnk.GetDescription(), "sample") + def test_returns_iunknown_type_instance(self): + class_factory = CoGetClassObject(CLSID_ShellLink) + self.assertIsInstance(class_factory, IClassFactory) + punk = class_factory.CreateInstance() + self.assertIsInstance(punk, IUnknown) + self.assertNotIsInstance(punk, shelllink.IShellLinkW) + shlnk = punk.QueryInterface(shelllink.IShellLinkW) + shlnk.SetDescription("sample") + self.assertEqual(shlnk.GetDescription(), "sample") + def test_raises_valueerror_if_takes_dynamic_true_and_interface_explicitly(self): class_factory = CoGetClassObject(CLSID_ShellLink) self.assertIsInstance(class_factory, IClassFactory)