Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Coming in build 311, as yet unreleased
* Fixed a regression that broke special __dunder__ methods with CoClass. (#1870, #2493, @Avasam, @geppi)
* Fixed a memory leak when SafeArrays are used as out parameters (@the-snork)
* Fixed dispatch handling for properties (@the-snork)
* Fixed struct handling in makepy generated code (@the-snork)

Build 310, released 2025/03/16
------------------------------
Expand Down
10 changes: 10 additions & 0 deletions com/TestSources/PyCOMTest/PyCOMImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,16 @@ HRESULT CPyCOMTest::GetStruct(TestStruct1 *ret)
return S_OK;
}

HRESULT CPyCOMTest::GetOutStruct(TestStruct1 *ret)
{
if (ret == NULL) {
return E_POINTER;
}
ret->int_value = 99;
ret->str_value = SysAllocString(L"Hello from C++");
return S_OK;
}

HRESULT CPyCOMTest::ModifyStruct(TestStruct1 *prec)
{
prec->int_value = 100;
Expand Down
1 change: 1 addition & 0 deletions com/TestSources/PyCOMTest/PyCOMImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
STDMETHOD(TestOptionals2)(double dval, BSTR strval, short sval, SAFEARRAY **pRet);
STDMETHOD(TestOptionals3)(double dval, short sval, IPyCOMTest **outinterface2);
STDMETHOD(GetStruct)(TestStruct1 *ret);
STDMETHOD(GetOutStruct)(TestStruct1 *ret);
STDMETHOD(DoubleString)(BSTR inStr, BSTR *outStr);
STDMETHOD(DoubleInOutString)(BSTR *str);
STDMETHOD(TestMyInterface)(IUnknown *t);
Expand Down
11 changes: 6 additions & 5 deletions com/TestSources/PyCOMTest/PyCOMTest.idl
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ library PyCOMTestLib
HRESULT GetSafeArrays([out] SAFEARRAY(QsAttribute)* attrs,
[out] SAFEARRAY(enum tagQsAttribute)*attrs2,
[out] SAFEARRAY(int)*ints);
HRESULT GetByteArray([in] long sizeInBytes, [out] SAFEARRAY(byte) *bytes);
HRESULT GetByteArray([in] long sizeInBytes, [out] SAFEARRAY(byte) *bytes);
HRESULT ChangeDoubleSafeArray([in, out]SAFEARRAY(double)*vals);
HRESULT GetSimpleCounter([out, retval] ISimpleCounter** counter);
HRESULT CheckVariantSafeArray([in] SAFEARRAY(VARIANT)* data, [out, retval]int *sum);
Expand All @@ -261,7 +261,8 @@ library PyCOMTestLib
[in, defaultvalue(1)] short sval,
[out, retval] IPyCOMTest **ppout );
HRESULT GetStruct([out, retval]TestStruct1 *ret);
HRESULT DoubleString([in] BSTR inStr, [out, retval] BSTR *outStr);
HRESULT GetOutStruct([out] TestStruct1 *ret);
HRESULT DoubleString([in] BSTR inStr, [out, retval] BSTR *outStr);
HRESULT DoubleInOutString([in,out] BSTR *str);
[restricted] HRESULT NotScriptable([in,out] int *val);

Expand Down Expand Up @@ -299,9 +300,9 @@ library PyCOMTestLib
// reserved words etc
HRESULT None();
HRESULT def();
// Test struct byref as [ in, out ] parameter.
HRESULT ModifyStruct([ in, out ] TestStruct1 * prec);
HRESULT VerifyArrayOfStructs([in] TestStruct2 * prec, [ out, retval ] VARIANT_BOOL * is_ok);
// Test struct byref as [in, out] parameter.
HRESULT ModifyStruct([in, out] TestStruct1 * prec);
HRESULT VerifyArrayOfStructs([in] TestStruct2 * prec, [out, retval] VARIANT_BOOL * is_ok);
};

// Define a new class to test how Python handles derived interfaces!
Expand Down
19 changes: 18 additions & 1 deletion com/win32com/client/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,22 @@ def MakeDispatchFuncMethod(self, entry, name, bMakeClass=1):
if doc and doc[1]:
ret.append(linePrefix + "\t" + _makeDocString(doc[1]))

for i in range(len(fdesc[2])):
desc = fdesc[2][i]

if (
desc[1] & (pythoncom.PARAMFLAG_FOUT | pythoncom.PARAMFLAG_FIN)
== pythoncom.PARAMFLAG_FOUT
):
if (
desc[0] & pythoncom.VT_RECORD
and desc[3]
and desc[3].__class__.__name__ == "PyIID"
):
newVal = f"pythoncom.GetRecordFromGuids(CLSID, MajorVersion, MinorVersion, LCID, {desc[3]!r})"
ret.append(f"{linePrefix}\tif {names[i+1]} == {defOutArg}:")
ret.append(f"{linePrefix}\t\t{names[i+1]} = {newVal}")

resclsid = entry.GetResultCLSID()
if resclsid:
resclsid = "'%s'" % resclsid
Expand Down Expand Up @@ -592,7 +608,7 @@ def _ResolveType(typerepr, itypeinfo):
return pythoncom.VT_UNKNOWN, clsid, retdoc

elif typeKind == pythoncom.TKIND_RECORD:
return pythoncom.VT_RECORD, None, None
return pythoncom.VT_RECORD, resultAttr.iid, None
raise NotSupportedException("Can not resolve alias or user-defined type")
return typeSubstMap.get(typerepr, typerepr), None, None

Expand All @@ -612,6 +628,7 @@ def _BuildArgList(fdesc, names):
name_num = 0
while len(names) < numArgs:
names.append("arg%d" % (len(names),))

# As per BuildCallList(), avoid huge lines.
# Hack a "\n" at the end of every 5th name
for i in range(0, len(names), 5):
Expand Down
11 changes: 11 additions & 0 deletions com/win32com/client/genpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,17 @@ def do_generate(self):
print("}", file=stream)
print(file=stream)

# create classes for records
for record in recordItems.values():
if record.clsid != pythoncom.IID_NULL:
print(f"class {record.doc[0]}:", file=stream)
print("\tdef __new__(cls, *args, **kwargs):", file=stream)
print(
f"\t\treturn pythoncom.GetRecordFromGuids(CLSID, MajorVersion, MinorVersion, LCID, '{record.clsid}')",
file=stream,
)
print(file=stream)

# Write out _all_ my generated CLSID's in the map
if self.generate_type == GEN_FULL:
print("CLSIDToClassMap = {", file=stream)
Expand Down
4 changes: 4 additions & 0 deletions com/win32com/client/makepy.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ def GenerateFromTypeLibSpec(
outputName = full_name + ".py"
fileUse = gen.open_writer(outputName)
progress.LogBeginGenerate(outputName)
elif isinstance(file, str):
fileUse = gen.open_writer(file)
else:
fileUse = file

Expand All @@ -330,6 +332,8 @@ def GenerateFromTypeLibSpec(
if file is None:
with gencache.ModuleMutex(this_name):
gen.finish_writer(outputName, fileUse, worked)
elif isinstance(file, str):
gen.finish_writer(file, fileUse, worked)
importlib.invalidate_caches()
if bToGenDir:
progress.SetDescription("Importing module")
Expand Down
34 changes: 34 additions & 0 deletions com/win32com/test/testPyComTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
sys.coinit_flags = 0 # Must be free-threaded!
import datetime
import decimal
import importlib
import os
import time

Expand All @@ -23,7 +24,9 @@
DispatchBaseClass,
Record,
constants,
makepy,
register_record_class,
selecttlb,
)
from win32process import GetProcessMemoryInfo

Expand Down Expand Up @@ -885,6 +888,34 @@ def TestQueryInterface(long_lived_server=0, iterations=5):
tester.TestQueryInterface()


def TestMakePy():
tlb = [
entry
for entry in selecttlb.EnumTlbs()
if entry.clsid.lower() == "{6bcdcb60-5605-11d0-ae5f-cadd4c000000}"
]
if len(tlb) == 0:
return

file_name = "makepy_generated.py"

makepy.GenerateFromTypeLibSpec(tlb[0].dll, file_name)

spec = importlib.util.spec_from_file_location("makepy_generated", file_name)
module = importlib.util.module_from_spec(spec)
sys.modules["makepy_generated"] = module
spec.loader.exec_module(module)

coclass = module.CoPyCOMTest()

# test structs
coclass.GetStruct()
coclass.GetOutStruct()

data = module.TestStruct1()
coclass.ModifyStruct(data)


class Tester(win32com.test.util.TestCase):
def testVTableInProc(self):
# We used to crash running this the second time - do it a few times
Expand Down Expand Up @@ -922,6 +953,9 @@ def testDynamic(self):
def testGenerated(self):
TestGenerated()

def testMakePy(self):
TestMakePy()


if __name__ == "__main__":
# XXX - todo - Complete hack to crank threading support.
Expand Down