diff --git a/comtypes/test/test_packing.py b/comtypes/test/test_packing.py index d24f4e8f..13bbfef7 100644 --- a/comtypes/test/test_packing.py +++ b/comtypes/test/test_packing.py @@ -15,16 +15,12 @@ class CalcPackingTest(unittest.TestCase): def test_returns_none_for_default_packing(self): # A naturally laid out single-field struct needs no explicit packing. field = _make_field("a", 32, 32, 0) - struct = typedesc.Structure( - "Good", align=32, members=[field], bases=[], size=32 - ) + struct = typedesc.Structure("Good", align=32, members=[field], size=32) self.assertIsNone(calc_packing(struct, [field])) def test_incomplete_struct_returns_none(self): field = _make_field("a", 32, 32, 0) - struct = typedesc.Structure( - "Incomplete", align=32, members=[field], bases=[], size=None - ) + struct = typedesc.Structure("Incomplete", align=32, members=[field], size=None) self.assertIsNone(calc_packing(struct, [field])) def test_raises_packing_error_with_details_when_layout_is_inconsistent(self): @@ -36,7 +32,7 @@ def test_raises_packing_error_with_details_when_layout_is_inconsistent(self): # ``except ... as`` target after it had been cleared, raising # ``UnboundLocalError`` and masking the real ``PackingError``. field = _make_field("x", 32, 32, 64) - struct = typedesc.Structure("Bad", align=32, members=[field], bases=[], size=96) + struct = typedesc.Structure("Bad", align=32, members=[field], size=96) with self.assertRaises(PackingError) as ctx: calc_packing(struct, [field]) message = str(ctx.exception) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index b8f44961..5510450e 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -428,15 +428,8 @@ def FundamentalType(self, item: typedesc.FundamentalType) -> None: pass # we should check if this is known somewhere def StructureHead(self, head: typedesc.StructureHead) -> None: - for struct in head.struct.bases: - self.generate(struct.get_head()) - self.more.add(struct) - basenames = [self._to_type_name(b) for b in head.struct.bases] - if basenames: - self.imports.add("comtypes", "GUID") - with self.adjust_blank("class") as ofi: - heads.StructureHeadWriter(ofi).write(head, basenames) + heads.StructureHeadWriter(ofi).write(head) self.names.add(head.struct.name) def Structure(self, struct: typedesc.Structure) -> None: @@ -449,38 +442,27 @@ def Union(self, union: typedesc.Union) -> None: def StructureBody(self, body: typedesc.StructureBody) -> None: fields: list[typedesc.Field] = [] - methods: list[typedesc.Method] = [] for m in body.struct.members: if isinstance(m, typedesc.Field): fields.append(m) if isinstance(m.typ, typedesc.Typedef): self.generate(get_real_type(m.typ)) self.generate(m.typ) - elif isinstance(m, typedesc.Method): - methods.append(m) - self.generate(m.returns) - self.generate_all(m.iterArgTypes()) - elif isinstance(m, typedesc.Constructor): - pass # we don't need _pack_ on Unions (I hope, at least), and not # on COM interfaces: - if not methods: - try: - pack = packing.calc_packing(body.struct, fields) - if pack is not None: - with self.adjust_blank("attribute") as ofi: - print(f"{body.struct.name}._pack_ = {pack}", file=ofi) - except packing.PackingError as details: - # if packing fails, write a warning comment to the output. - message = f"Structure {body.struct.name}: {details}" - warnings.warn(message, UserWarning) - with self.adjust_blank("comment") as ofi: - print(f"# WARNING: {details}", file=ofi) + try: + pack = packing.calc_packing(body.struct, fields) + if pack is not None: + with self.adjust_blank("attribute") as ofi: + print(f"{body.struct.name}._pack_ = {pack}", file=ofi) + except packing.PackingError as details: + # if packing fails, write a warning comment to the output. + message = f"Structure {body.struct.name}: {details}" + warnings.warn(message, UserWarning) + with self.adjust_blank("comment") as ofi: + print(f"# WARNING: {details}", file=ofi) if fields: - if body.struct.bases: - assert len(body.struct.bases) == 1 - self.generate(body.struct.bases[0].get_body()) with self.adjust_blank("attribute") as ofi: self._write_structbody_fields(body, fields, ofi) if body.struct.size is None: @@ -489,10 +471,6 @@ def StructureBody(self, body: typedesc.StructureBody) -> None: elif body.struct.name not in packing.dont_assert_size: with self.adjust_blank("assert") as ofi: self._write_structbody_size_assertion(body, ofi) - if methods: - self.imports.add("comtypes", "COMMETHOD") - with self.adjust_blank("attribute") as ofi: - self._write_structbody_commethods(body, methods, ofi) def _write_structbody_fields( self, @@ -537,32 +515,6 @@ def _write_structbody_size_assertion( align = body.struct.align // 8 print(f"assert alignment({name}) == {align}, alignment({name})", file=ofi) - def _write_structbody_commethods( - self, - body: typedesc.StructureBody, - methods: list[typedesc.Method], - ofi: io.StringIO, - ) -> None: - print(f"{body.struct.name}._methods_ = [", file=ofi) - if body.struct.location: - print(f"# {body.struct.location}", file=ofi) - for m in methods: - if m.location: - print(f" # {m.location}", file=ofi) - print( - ( - " COMMETHOD(\n" - " [],\n" - f" {self._to_type_name(m.returns)},\n" - f" '{m.name}',\n" - ), - file=ofi, - ) - for a in m.iterArgTypes(): - print(f" ([], {self._to_type_name(a)}),\n", file=ofi) - print(" ),", file=ofi) - print("]", file=ofi) - ################################################################ # top-level typedesc generators # diff --git a/comtypes/tools/codegenerator/heads.py b/comtypes/tools/codegenerator/heads.py index 9831cd41..1baf68d3 100644 --- a/comtypes/tools/codegenerator/heads.py +++ b/comtypes/tools/codegenerator/heads.py @@ -1,5 +1,4 @@ import io -from collections.abc import Sequence from typing import Optional from comtypes.tools import typedesc @@ -18,54 +17,20 @@ class StructureHeadWriter: def __init__(self, stream: io.StringIO) -> None: self.stream = stream - def write(self, head: typedesc.StructureHead, basenames: Sequence[str]) -> None: + def write(self, head: typedesc.StructureHead) -> None: if head.struct.location: print(f"# {head.struct.location}", file=self.stream) - if basenames: - method_names = [ - m.name for m in head.struct.members if type(m) is typedesc.Method - ] - print( - f"class {head.struct.name}({', '.join(basenames)}):", - file=self.stream, - ) - print( - " _iid_ = GUID('{}') # please look up iid and fill in!", - file=self.stream, - ) - if "Enum" in method_names: - print(" def __iter__(self):", file=self.stream) - print(" return self.Enum()", file=self.stream) - elif method_names == "Next Skip Reset Clone".split(): - print(" def __iter__(self):", file=self.stream) - print(" return self", file=self.stream) - print(file=self.stream) - print(" def next(self):", file=self.stream) - print(" arr, fetched = self.Next(1)", file=self.stream) - print(" if fetched == 0:", file=self.stream) - print(" raise StopIteration", file=self.stream) - print(" return arr[0]", file=self.stream) - - else: - methods = [m for m in head.struct.members if type(m) is typedesc.Method] - - if methods: - # Hm. We cannot generate code for IUnknown... - print("assert 0, 'cannot generate code for IUnknown'", file=self.stream) - print(f"class {head.struct.name}(_com_interface):", file=self.stream) - print(" pass", file=self.stream) - elif type(head.struct) is typedesc.Structure: - print(f"class {head.struct.name}(Structure):", file=self.stream) - if hasattr(head.struct, "_recordinfo_"): - print( - f" _recordinfo_ = {head.struct._recordinfo_!r}", - file=self.stream, - ) - else: - print(" pass", file=self.stream) - elif type(head.struct) is typedesc.Union: - print(f"class {head.struct.name}(Union):", file=self.stream) + if isinstance(head.struct, typedesc.Structure): + print(f"class {head.struct.name}(Structure):", file=self.stream) + if hasattr(head.struct, "_recordinfo_"): + print( + f" _recordinfo_ = {head.struct._recordinfo_!r}", file=self.stream + ) + else: print(" pass", file=self.stream) + elif isinstance(head.struct, typedesc.Union): + print(f"class {head.struct.name}(Union):", file=self.stream) + print(" pass", file=self.stream) class LibraryHeadWriter: diff --git a/comtypes/tools/codegenerator/packing.py b/comtypes/tools/codegenerator/packing.py index b9c219a4..5d389233 100644 --- a/comtypes/tools/codegenerator/packing.py +++ b/comtypes/tools/codegenerator/packing.py @@ -8,12 +8,8 @@ def _calc_packing(struct, fields, pack, isStruct): return -1 if struct.name in dont_assert_size: return None - if struct.bases: - size = struct.bases[0].size - total_align = struct.bases[0].align - else: - size = 0 - total_align = 8 # in bits + size = 0 + total_align = 8 # in bits for i, f in enumerate(fields): if f.bits: # this code cannot handle bit field sizes. # print "##XXX FIXME" diff --git a/comtypes/tools/tlbparser.py b/comtypes/tools/tlbparser.py index 123b9838..189a28b3 100644 --- a/comtypes/tools/tlbparser.py +++ b/comtypes/tools/tlbparser.py @@ -1,5 +1,6 @@ import os import sys +import warnings from _ctypes import COMError from collections.abc import Mapping from ctypes import alignment, c_void_p, sizeof, windll @@ -7,7 +8,7 @@ from typing import Any, Optional, TypeVar from typing import Union as _UnionT -from comtypes import automation, typeinfo +from comtypes import IUnknown, automation, typeinfo from comtypes.tools import typedesc # Is the process 64-bit? @@ -49,7 +50,6 @@ def PTR(typ: _PT) -> typedesc.PointerType[_PT]: "VARIANT", align=alignment(automation.VARIANT) * 8, members=[], - bases=[], size=sizeof(automation.VARIANT) * 8, ) @@ -84,7 +84,6 @@ def PTR(typ: _PT) -> typedesc.PointerType[_PT]: "DECIMAL", align=alignment(automation.DECIMAL) * 8, members=[], - bases=[], size=sizeof(automation.DECIMAL) * 8, ) @@ -158,6 +157,12 @@ def midlSAFEARRAY(typ: _PT) -> typedesc.SAFEARRAYType[_PT]: ################################################################ +# To suppress obvious, non-essential warnings. +# Cast to str only once to avoid redundant work. +_IID_IUnknown = str(IUnknown._iid_) +_IID_IDispatch = str(automation.IDispatch._iid_) + + class Parser: tlib: typeinfo.ITypeLib items: dict[str, ParsedType] @@ -191,12 +196,8 @@ def make_type( f"\n\tGetRefTypeInfo failed in {tlib_name}: {details}" f"\n\tgenerating type '{type_name}' instead" ) - import warnings - warnings.warn(message, UserWarning) - result = typedesc.Structure( - type_name, align=8, members=[], bases=[], size=0 - ) + result = typedesc.Structure(type_name, align=8, members=[], size=0) return result result = self.parse_typeinfo(ti) assert result is not None, ti.GetDocumentation(-1)[0] @@ -236,7 +237,6 @@ def ParseRecord( struct_name, align=ta.cbAlignment * 8, members=members, - bases=[], size=ta.cbSizeInstance * 8, ) self._register(struct_name, struct) @@ -321,12 +321,20 @@ def ParseInterface( # It's an interface without methods anyway. if itf_name != "IOleControlTypes": message = f"Ignoring interface {itf_name} which has no base interface" - import warnings - warnings.warn(message, UserWarning) return None iid = str(ta.guid) + # IUnknown and IDispatch are fundamental COM interfaces, so reuse + # their canonical type-model constants instead of rebuilding a + # model from the type library. This avoids redundant work and + # keeps the handling of these well-known symbols consistent. + if iid == _IID_IUnknown: + self._register(itf_name, IUNKNOWN_type) + return IUNKNOWN_type + if iid == _IID_IDispatch: + self._register(itf_name, IDISPATCH_type) + return IDISPATCH_type idlflags = self.interface_type_flags(ta.wTypeFlags) itf = typedesc.ComInterface(itf_name, None, iid, idlflags, itf_doc) self._register(itf_name, itf) @@ -603,7 +611,6 @@ def ParseUnion( union_name, align=ta.cbAlignment * 8, members=members, - bases=[], size=ta.cbSizeInstance * 8, ) self._register(union_name, union) diff --git a/comtypes/tools/typedesc.py b/comtypes/tools/typedesc.py index 70121ac8..5fcd1917 100644 --- a/comtypes/tools/typedesc.py +++ b/comtypes/tools/typedesc.py @@ -177,7 +177,7 @@ class DispInterface: def __init__( self, name: str, - base: Any, + base: "_UnionT[ComInterface, DispInterface, External]", iid: str, idlflags: list[str], doc: Optional[str], @@ -218,7 +218,7 @@ class ComInterface: def __init__( self, name: str, - base: "Optional[ComInterface]", + base: "_UnionT[None, ComInterface, External]", iid: str, idlflags: list[str], doc: Optional[str], diff --git a/comtypes/tools/typedesc_base.py b/comtypes/tools/typedesc_base.py index 1cd1352a..ccd94910 100644 --- a/comtypes/tools/typedesc_base.py +++ b/comtypes/tools/typedesc_base.py @@ -1,6 +1,5 @@ # typedesc.py - classes representing C type descriptions from typing import Any, Generic, Optional, SupportsInt, TypeVar, final -from typing import Union as _UnionT _TD = TypeVar("_TD") @@ -167,8 +166,7 @@ def __init__(self, struct: "_Struct_Union_Base") -> None: class _Struct_Union_Base: name: str align: int - members: list[_UnionT["Field", Method, Constructor]] - bases: list["_Struct_Union_Base"] + members: list["Field"] artificial: Optional[Any] size: Optional[int] _recordinfo_: tuple[str, int, int, int, str] @@ -193,14 +191,12 @@ def __init__( name: str, align: SupportsInt, members: list["Field"], - bases: list[Any], size: Optional[SupportsInt], artificial: Optional[Any] = None, ) -> None: self.name = name self.align = int(align) self.members = members - self.bases = bases self.artificial = artificial if size is not None: self.size = int(size) @@ -216,14 +212,12 @@ def __init__( name: str, align: SupportsInt, members: list["Field"], - bases: list[Any], size: Optional[SupportsInt], artificial: Optional[Any] = None, ) -> None: self.name = name self.align = int(align) self.members = members - self.bases = bases self.artificial = artificial if size is not None: self.size = int(size)