diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e5069f98c5..86f51b9458 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -58,6 +58,16 @@ jobs:
python -m win32.scripts.pywin32_postinstall -install -destination "$UserSite"
pywin32_postinstall -remove -destination "$UserSite"
+ # Compilation and registration of the PyCOMTest server dll
+ - name: Set up MSVC
+ uses: microsoft/setup-msbuild@v2
+ - name: Build and register the PyCOMTest server dll
+ run: |
+ cd com/TestSources/PyCOMTest
+ msbuild .\PyCOMTest.sln -property:Configuration=Release
+ cd x64/Release
+ regsvr32 .\PyCOMTest.dll
+
- name: Run tests
# Run the tests directly from the source dir so support files (eg, .wav files etc)
# can be found - they aren't installed into the Python tree.
diff --git a/.gitignore b/.gitignore
index 1ce8634b32..8c5ee60106 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,15 +18,23 @@ arm64libs/
pywin32.egg-info/
PyWin32.kpf
+# Visual Studio project files
+*.sln
+*.suo
+*.vc*proj*
+*.user
+
# COM test bits
com/TestSources/Build
com/TestSources/PyCOMTest/PyCOMTest.h
-com/TestSources/PyCOMTest/PyCOMTest.sln
com/TestSources/PyCOMTest/PyCOMTest.suo
com/TestSources/PyCOMTest/PyCOMTest.tlb
-com/TestSources/PyCOMTest/PyCOMTest.vc*proj*
com/TestSources/PyCOMTest/PyCOMTest_i.c
com/TestSources/PyCOMTest/.vs/
+com/TestSources/PyCOMTest/PyCOMTest/
+com/TestSources/PyCOMTest/x64/
+!com/TestSources/PyCOMTest/PyCOMTest.sln
+!com/TestSources/PyCOMTest/PyCOMTest.vcxproj
# SWIG generated files.
com/win32comext/adsi/src/*.cpp
@@ -50,12 +58,6 @@ win32/src/win32service_messages.h
win32/src/win32evtlog_messages.h
isapi/src/pyISAPI_messages.h
-# Visual Studio project files
-*.sln
-*.suo
-*.vcproj
-*.user
-
# Eclipse + PyDev
.project
.pydevproject
diff --git a/CHANGES.txt b/CHANGES.txt
index 81eacd13b2..8955b85a63 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -13,6 +13,7 @@ https://mhammond.github.io/pywin32_installers.html .
Coming in build 311, as yet unreleased
--------------------------------------
+* Fixed a regression that broke special __dunder__ methods with CoClass. (#1870, #2493, @Avasam, @geppi)
Build 310, released 2025/03/16
------------------------------
diff --git a/com/TestSources/PyCOMTest/PyCOMTest.idl b/com/TestSources/PyCOMTest/PyCOMTest.idl
index db67625015..2d7b42833b 100644
--- a/com/TestSources/PyCOMTest/PyCOMTest.idl
+++ b/com/TestSources/PyCOMTest/PyCOMTest.idl
@@ -26,7 +26,7 @@ typedef enum // Missing EnumTestAttributes2
uuid(6bcdcb60-5605-11d0-ae5f-cadd4c000000),
version(1.1),
// an extended character in the help string should stress things...
- helpstring("Python COM Test Harness 1.0 Type Library, � pywin32 contributors")
+ helpstring("Python COM Test Harness 1.0 Type Library, © pywin32 contributors")
]
library PyCOMTestLib
{
@@ -70,7 +70,7 @@ library PyCOMTestLib
const long LongTest2 = 0x7FFFFFFFL;
const unsigned char UCharTest = 255;
const char CharTest = -1;
- const LPWSTR StringTest = L"Hello Wo�ld";
+ const LPWSTR StringTest = L"Hello Wo®ld";
};
enum TestAttributes3{ // Note missing the enum name.
diff --git a/com/TestSources/PyCOMTest/PyCOMTest.sln b/com/TestSources/PyCOMTest/PyCOMTest.sln
new file mode 100644
index 0000000000..c420a591f1
--- /dev/null
+++ b/com/TestSources/PyCOMTest/PyCOMTest.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35027.167
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyCOMTest", "PyCOMTest.vcxproj", "{8EB3046C-6CE8-4537-9B58-6EDD46E6D632}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Debug|x64.ActiveCfg = Debug|x64
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Debug|x64.Build.0 = Debug|x64
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Debug|x86.ActiveCfg = Debug|Win32
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Debug|x86.Build.0 = Debug|Win32
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Release|x64.ActiveCfg = Release|x64
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Release|x64.Build.0 = Release|x64
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Release|x86.ActiveCfg = Release|Win32
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E15B431A-88C2-47F5-B809-AAC25279BD21}
+ EndGlobalSection
+EndGlobal
diff --git a/com/TestSources/PyCOMTest/PyCOMTest.vcxproj b/com/TestSources/PyCOMTest/PyCOMTest.vcxproj
new file mode 100644
index 0000000000..1c3d45f337
--- /dev/null
+++ b/com/TestSources/PyCOMTest/PyCOMTest.vcxproj
@@ -0,0 +1,164 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ {8EB3046C-6CE8-4537-9B58-6EDD46E6D632}
+ Win32Proj
+ 10.0.22000.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ NotSet
+
+
+ DynamicLibrary
+ false
+ v143
+ NotSet
+
+
+ DynamicLibrary
+ true
+ v142
+ NotSet
+
+
+ DynamicLibrary
+ false
+ v142
+ NotSet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+
+ WIN32;_DEBUG;_WINDOWS;_USRDLL;PYCOMTEST_EXPORTS;/nologo /MDd /W3 /Gm /GX /ZI /Od /D _DEBUG /D WIN32 /D _WINDOWS /D _USRDLL /D _UNICODE /D _WINDLL /D _AFXDLL /D _MBCS /Yupreconn.h /FD /c;%(PreprocessorDefinitions)
+ Level3
+
+
+ true
+ Windows
+ Connect.def
+
+
+
+
+ _DEBUG;_WINDOWS;_USRDLL;PYCOMTEST_EXPORTS;/nologo /MDd /W3 /Gm /GX /ZI /Od /D _DEBUG /D WIN32 /D _WINDOWS /D _USRDLL /D _UNICODE /D _WINDLL /D _AFXDLL /D _MBCS /Yupreconn.h /FD /c;%(PreprocessorDefinitions)
+ Level3
+
+
+ true
+ Windows
+ Connect.def
+
+
+
+
+ WIN32;NDEBUG;_WINDOWS;_USRDLL;PYCOMTEST_EXPORTS;/nologo /MD /W3 /GX /O2 /D NDEBUG /D WIN32 /D _WINDOWS /D _USRDLL /D _UNICODE /D _WINDLL /D _AFXDLL /D _MBCS /Yupreconn.h /FD /c;%(PreprocessorDefinitions)
+ Level3
+
+
+ true
+ Windows
+ true
+ true
+ Connect.def
+
+
+
+
+ NDEBUG;_WINDOWS;_USRDLL;PYCOMTEST_EXPORTS;/nologo /MD /W3 /GX /O2 /D NDEBUG /D WIN32 /D _WINDOWS /D _USRDLL /D _UNICODE /D _WINDLL /D _AFXDLL /D _MBCS /Yupreconn.h /FD /c;%(PreprocessorDefinitions)
+ Level3
+
+
+ true
+ Windows
+ true
+ false
+ false
+ Connect.def
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename).h
+ %(Filename).tlb
+ %(Filename).h
+ %(Filename).tlb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/com/win32com/client/__init__.py b/com/win32com/client/__init__.py
index ae879590b5..d060fae00d 100644
--- a/com/win32com/client/__init__.py
+++ b/com/win32com/client/__init__.py
@@ -628,18 +628,7 @@ class CoClassBaseClass:
def __init__(self, oobj=None):
if oobj is None:
oobj = pythoncom.new(self.CLSID)
- dispobj = self.__dict__["_dispobj_"] = self.default_interface(oobj)
- # See comments below re the special methods.
- for maybe in [
- "__call__",
- "__str__",
- "__int__",
- "__iter__",
- "__len__",
- "__bool__",
- ]:
- if hasattr(dispobj, maybe):
- setattr(self, maybe, getattr(self, "__maybe" + maybe))
+ self.__dict__["_dispobj_"] = self.default_interface(oobj)
def __repr__(self):
return f""
@@ -663,31 +652,29 @@ def __setattr__(self, attr, value):
pass
self.__dict__[attr] = value
- # Special methods don't use __getattr__ etc, so explicitly delegate here.
- # Note however, that not all are safe to let bubble up - things like
- # `bool(ob)` will break if the object defines __int__ but then raises an
- # attribute error - eg, see #1753.
- # It depends on what the wrapped COM object actually defines whether these
- # will exist on the underlying object, so __init__ explicitly checks if they
- # do and if so, wires them up.
+ # Special methods don't use __getattr__ etc, so explicitly delegate here.
+ # Some wrapped objects might not have them, but that's OK - the attribute
+ # error can just bubble up.
+ # This was initially implemented to address #1699 which did cause a problem
+ # with bool() in #1753 because the code initially implemented __nonzero__
+ # instead of __bool__, which was pointed out in the conclusion of #1870.
+ def __call__(self, *args, **kwargs):
+ return self.__dict__["_dispobj_"](*args, **kwargs)
- def __maybe__call__(self, *args, **kwargs):
- return self.__dict__["_dispobj_"].__call__(*args, **kwargs)
+ def __str__(self, *args):
+ return str(self.__dict__["_dispobj_"])
- def __maybe__str__(self, *args):
- return self.__dict__["_dispobj_"].__str__(*args)
+ def __int__(self, *args):
+ return int(self.__dict__["_dispobj_"])
- def __maybe__int__(self, *args):
- return self.__dict__["_dispobj_"].__int__(*args)
+ def __iter__(self):
+ return iter(self.__dict__["_dispobj_"])
- def __maybe__iter__(self):
- return self.__dict__["_dispobj_"].__iter__()
+ def __len__(self):
+ return len(self.__dict__["_dispobj_"])
- def __maybe__len__(self):
- return self.__dict__["_dispobj_"].__len__()
-
- def __maybe__bool__(self):
- return self.__dict__["_dispobj_"].__bool__()
+ def __bool__(self):
+ return bool(self.__dict__["_dispobj_"])
# A very simple VARIANT class. Only to be used with poorly-implemented COM
diff --git a/com/win32com/test/testArrays.py b/com/win32com/test/testArrays.py
index aa4ad16af4..0576efe679 100644
--- a/com/win32com/test/testArrays.py
+++ b/com/win32com/test/testArrays.py
@@ -51,7 +51,7 @@ def _normalize_array(a):
class ArrayTest(util.TestCase):
def setUp(self):
- self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest")
+ self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest", bForDemand=False)
def tearDown(self):
self.arr = None
diff --git a/com/win32com/test/testPyComTest.py b/com/win32com/test/testPyComTest.py
index f8b0c73733..d525741fc5 100644
--- a/com/win32com/test/testPyComTest.py
+++ b/com/win32com/test/testPyComTest.py
@@ -36,7 +36,9 @@
from win32com.client import gencache
try:
- gencache.EnsureModule("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)
+ gencache.EnsureModule(
+ "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1, bForDemand=False
+ )
except pythoncom.com_error:
print("The PyCOMTest module can not be located or generated.")
print(importMsg)
diff --git a/com/win32com/test/testall.py b/com/win32com/test/testall.py
index db30a0a8ee..991dbdf6c8 100644
--- a/com/win32com/test/testall.py
+++ b/com/win32com/test/testall.py
@@ -178,10 +178,11 @@ def testit(self):
custom_test_cases = [
# Level 1 tests.
- [],
- # Level 2 tests.
[
PyCOMTest,
+ ],
+ # Level 2 tests.
+ [
PippoTest,
],
# Level 3 tests