Skip to content
Merged
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
10 changes: 3 additions & 7 deletions comtypes/test/test_packing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand Down
72 changes: 12 additions & 60 deletions comtypes/tools/codegenerator/codegenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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
#
Expand Down
57 changes: 11 additions & 46 deletions comtypes/tools/codegenerator/heads.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import io
from collections.abc import Sequence
from typing import Optional

from comtypes.tools import typedesc
Expand All @@ -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:
Expand Down
8 changes: 2 additions & 6 deletions comtypes/tools/codegenerator/packing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
31 changes: 19 additions & 12 deletions comtypes/tools/tlbparser.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import os
import sys
import warnings
from _ctypes import COMError
from collections.abc import Mapping
from ctypes import alignment, c_void_p, sizeof, windll
from ctypes.wintypes import MAX_PATH
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?
Expand Down Expand Up @@ -49,7 +50,6 @@ def PTR(typ: _PT) -> typedesc.PointerType[_PT]:
"VARIANT",
align=alignment(automation.VARIANT) * 8,
members=[],
bases=[],
size=sizeof(automation.VARIANT) * 8,
)

Expand Down Expand Up @@ -84,7 +84,6 @@ def PTR(typ: _PT) -> typedesc.PointerType[_PT]:
"DECIMAL",
align=alignment(automation.DECIMAL) * 8,
members=[],
bases=[],
size=sizeof(automation.DECIMAL) * 8,
)

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -236,7 +237,6 @@ def ParseRecord(
struct_name,
align=ta.cbAlignment * 8,
members=members,
bases=[],
size=ta.cbSizeInstance * 8,
)
self._register(struct_name, struct)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -603,7 +611,6 @@ def ParseUnion(
union_name,
align=ta.cbAlignment * 8,
members=members,
bases=[],
size=ta.cbSizeInstance * 8,
)
self._register(union_name, union)
Expand Down
4 changes: 2 additions & 2 deletions comtypes/tools/typedesc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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],
Expand Down
8 changes: 1 addition & 7 deletions comtypes/tools/typedesc_base.py
Original file line number Diff line number Diff line change
@@ -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")

Expand Down Expand Up @@ -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]
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Loading