Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
10 changes: 5 additions & 5 deletions com/TestSources/PyCOMTest/PyCOMTest.idl
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ 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 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 +260,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 +299,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
Loading