Skip to content

Commit 16c4981

Browse files
authored
Free out safe arrays after conversion (#2557)
1 parent 21b33aa commit 16c4981

File tree

8 files changed

+37
-4
lines changed

8 files changed

+37
-4
lines changed

.github/workflows/main.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,7 @@ jobs:
157157
with:
158158
version: "0.8.4"
159159
- run: ruff format --check
160-
- run:
161-
| # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary
160+
- run: | # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary
162161
clang-format --Werror --dry-run $(git ls-files '*.cpp' ':!:com/win32comext/mapi/src/MAPIStubLibrary/')
163162
if ($LastExitCode -ne 0) { exit $LastExitCode }
164163
clang-format --Werror --dry-run $(git ls-files '*.h' ':!:Pythonwin/Scintilla/' ':!:com/win32comext/mapi/src/MAPIStubLibrary/')

CHANGES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ https://mhammond.github.io/pywin32_installers.html .
1414
Coming in build 311, as yet unreleased
1515
--------------------------------------
1616
* Fixed a regression that broke special __dunder__ methods with CoClass. (#1870, #2493, @Avasam, @geppi)
17+
* Fixed a memory leak when SafeArrays are used as out parameters (@the-snork)
1718
* Fixed dispatch handling for properties (@the-snork)
1819

1920
Build 310, released 2025/03/16

com/TestSources/PyCOMTest/PyCOMImpl.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,13 @@ STDMETHODIMP CPyCOMTest::GetSafeArrays(SAFEARRAY **attrs, SAFEARRAY **attrs2, SA
379379
return S_OK;
380380
}
381381

382+
STDMETHODIMP CPyCOMTest::GetByteArray(long sizeBytes, SAFEARRAY **array)
383+
{
384+
SAFEARRAYBOUND bound = {static_cast<ULONG>(sizeBytes), 0};
385+
*array = SafeArrayCreate(VT_UI1, 1, &bound);
386+
return S_OK;
387+
}
388+
382389
STDMETHODIMP CPyCOMTest::GetSimpleSafeArray(SAFEARRAY **attrs) { return MakeFillIntArray(attrs, 10, VT_I4); }
383390

384391
STDMETHODIMP CPyCOMTest::CheckVariantSafeArray(SAFEARRAY **attrs, int *result)

com/TestSources/PyCOMTest/PyCOMImpl.h

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
8080
STDMETHOD(SetDoubleSafeArray)(SAFEARRAY *vars, int *retSize);
8181
STDMETHOD(SetFloatSafeArray)(SAFEARRAY *vars, int *retSize);
8282
STDMETHOD(GetSafeArrays)(SAFEARRAY **attrs, SAFEARRAY **attrs2, SAFEARRAY **ints);
83+
STDMETHOD(GetByteArray)(long sizeBytes, SAFEARRAY **array);
8384
STDMETHOD(GetSimpleSafeArray)(SAFEARRAY **ints);
8485
STDMETHOD(ChangeDoubleSafeArray)(SAFEARRAY **vals);
8586
STDMETHOD(GetSimpleCounter)(ISimpleCounter **counter);

com/TestSources/PyCOMTest/PyCOMTest.idl

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ library PyCOMTestLib
235235
HRESULT GetSafeArrays([out] SAFEARRAY(QsAttribute)* attrs,
236236
[out] SAFEARRAY(enum tagQsAttribute)*attrs2,
237237
[out] SAFEARRAY(int)*ints);
238+
HRESULT GetByteArray([in] long sizeInBytes, [out] SAFEARRAY(byte) *bytes);
238239
HRESULT ChangeDoubleSafeArray([in, out]SAFEARRAY(double)*vals);
239240
HRESULT GetSimpleCounter([out, retval] ISimpleCounter** counter);
240241
HRESULT CheckVariantSafeArray([in] SAFEARRAY(VARIANT)* data, [out, retval]int *sum);

com/TestSources/PyCOMTest/PyCOMTest.vcxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,4 @@
161161
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
162162
<ImportGroup Label="ExtensionTargets">
163163
</ImportGroup>
164-
</Project>
164+
</Project>

com/win32com/src/PyIDispatch.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,12 @@ PyObject *PyIDispatch::InvokeTypes(PyObject *self, PyObject *args)
517517

518518
error:
519519
if (dispparams.rgvarg) {
520-
for (i = dispparams.cArgs; i--;) VariantClear(&dispparams.rgvarg[i]);
520+
for (i = dispparams.cArgs; i--;) {
521+
if ((V_VT(&dispparams.rgvarg[i]) & ~VT_TYPEMASK) == (VT_BYREF | VT_ARRAY)) {
522+
SafeArrayDestroy(*V_ARRAYREF(&dispparams.rgvarg[i]));
523+
}
524+
VariantClear(&dispparams.rgvarg[i]);
525+
}
521526
delete[] dispparams.rgvarg;
522527
}
523528
delete[] ArgHelpers;

com/win32com/test/testPyComTest.py

+19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import win32com.test.util
1717
import win32timezone
1818
import winerror
19+
from win32api import CloseHandle, GetCurrentProcessId, OpenProcess
1920
from win32com.client import (
2021
VARIANT,
2122
CastTo,
@@ -24,6 +25,7 @@
2425
constants,
2526
register_record_class,
2627
)
28+
from win32process import GetProcessMemoryInfo
2729

2830
importMsg = "**** PyCOMTest is not installed ***\n PyCOMTest is a Python test specific COM client and server.\n It is likely this server is not installed on this machine\n To install the server, you must get the win32com sources\n and build it using MS Visual C++"
2931

@@ -137,6 +139,16 @@ def TestConstant(constName, pyConst):
137139
), f"Constant value wrong for {constName} - got {comConst}, wanted {pyConst}"
138140

139141

142+
def GetMemoryUsage():
143+
pid = GetCurrentProcessId()
144+
PROCESS_QUERY_INFORMATION = 0x0400
145+
PROCESS_VM_READ = 0x0010
146+
hprocess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
147+
mem_info = GetProcessMemoryInfo(hprocess)
148+
CloseHandle(hprocess)
149+
return mem_info["WorkingSetSize"]
150+
151+
140152
# Simple handler class. This demo only fires one event.
141153
class RandomEventHandler:
142154
def _Init(self):
@@ -601,6 +613,13 @@ def TestGenerated():
601613
TestApplyResult(o.SetLongLongSafeArray, (ll,), len(ll))
602614
TestApplyResult(o.SetULongLongSafeArray, (ll,), len(ll))
603615

616+
# check freeing of safe arrays
617+
mem_before = GetMemoryUsage()
618+
o.GetByteArray(50 * 1024 * 1024)
619+
mem_after = GetMemoryUsage()
620+
delta = mem_after - mem_before
621+
assert delta < 1024 * 1024, f"Memory not freed - delta {delta / (1024 * 1024)} MB"
622+
604623
# Tell the server to do what it does!
605624
TestApplyResult(o.Test2, (constants.Attr2,), constants.Attr2)
606625
TestApplyResult(o.Test3, (constants.Attr2,), constants.Attr2)

0 commit comments

Comments
 (0)