Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -15,6 +15,7 @@ Coming in build 311, as yet unreleased
--------------------------------------
* Fixed a regression that broke special __dunder__ methods with CoClass. (#1870, #2493, @Avasam, @geppi)
* 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 @@ -619,6 +619,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 @@ -98,6 +98,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
7 changes: 4 additions & 3 deletions com/TestSources/PyCOMTest/PyCOMTest.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -260,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
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, {repr(desc[3])})"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @geppi, do you have any thoughts on this pr?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, I'm in the mountains over the long Easter weekend and can take a detailed look into this earliest on tuesday. My first thought is that I should probably just write some documentation about the functionality we recently introduced for COM Records with PR #2437.
However, I'm not sure where this documentation should live and what format would be required?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like in-code comments parsed by a tool called AutoDuck is used to generate helpfiles. Those helpfiles are then decompiled to extract the HTML and are hosted as a GitHub page:

In #2432 I'm trying to restore shipping the helpfiles (by adding them to the build commands without building pywin32 twice) to users for local no-internet documentation.
In #2577 I added a link to a mirror of AutoDuck's download which included documentation and examples on how to use it. Hopefully that should help with its usage.

ret.append(linePrefix + "\t" + f"if {names[i+1]} == {defOutArg}:")
ret.append(linePrefix + "\t\t" + f"{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 @@ -22,7 +23,9 @@
DispatchBaseClass,
Record,
constants,
makepy,
register_record_class,
selecttlb,
)

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++"
Expand Down Expand Up @@ -866,6 +869,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 @@ -903,6 +934,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