You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1000 lines
39 KiB
1000 lines
39 KiB
# Code generator to generate code for everything contained in COM type
|
|
# libraries.
|
|
import os
|
|
import io
|
|
import keyword
|
|
from comtypes.tools import typedesc
|
|
import comtypes.client
|
|
import comtypes.client._generate
|
|
|
|
version = "$Rev$"[6:-2]
|
|
|
|
__warn_on_munge__ = __debug__
|
|
|
|
|
|
class lcid(object):
|
|
def __repr__(self):
|
|
return "_lcid"
|
|
lcid = lcid()
|
|
|
|
class dispid(object):
|
|
def __init__(self, memid):
|
|
self.memid = memid
|
|
|
|
def __repr__(self):
|
|
return "dispid(%s)" % self.memid
|
|
|
|
class helpstring(object):
|
|
def __init__(self, text):
|
|
self.text = text
|
|
|
|
def __repr__(self):
|
|
return "helpstring(%r)" % self.text
|
|
|
|
|
|
# XXX Should this be in ctypes itself?
|
|
ctypes_names = {
|
|
"unsigned char": "c_ubyte",
|
|
"signed char": "c_byte",
|
|
"char": "c_char",
|
|
|
|
"wchar_t": "c_wchar",
|
|
|
|
"short unsigned int": "c_ushort",
|
|
"short int": "c_short",
|
|
|
|
"long unsigned int": "c_ulong",
|
|
"long int": "c_long",
|
|
"long signed int": "c_long",
|
|
|
|
"unsigned int": "c_uint",
|
|
"int": "c_int",
|
|
|
|
"long long unsigned int": "c_ulonglong",
|
|
"long long int": "c_longlong",
|
|
|
|
"double": "c_double",
|
|
"float": "c_float",
|
|
|
|
# Hm...
|
|
"void": "None",
|
|
}
|
|
|
|
def get_real_type(tp):
|
|
if type(tp) is typedesc.Typedef:
|
|
return get_real_type(tp.typ)
|
|
elif isinstance(tp, typedesc.CvQualifiedType):
|
|
return get_real_type(tp.typ)
|
|
return tp
|
|
|
|
ASSUME_STRINGS = True
|
|
|
|
def _calc_packing(struct, fields, pack, isStruct):
|
|
# Try a certain packing, raise PackingError if field offsets,
|
|
# total size ot total alignment is wrong.
|
|
if struct.size is None: # incomplete struct
|
|
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
|
|
for i, f in enumerate(fields):
|
|
if f.bits: # this code cannot handle bit field sizes.
|
|
## print "##XXX FIXME"
|
|
return -2 # XXX FIXME
|
|
s, a = storage(f.typ)
|
|
if pack is not None:
|
|
a = min(pack, a)
|
|
if size % a:
|
|
size += a - size % a
|
|
if isStruct:
|
|
if size != f.offset:
|
|
raise PackingError("field %s offset (%s/%s)" % (f.name, size, f.offset))
|
|
size += s
|
|
else:
|
|
size = max(size, s)
|
|
total_align = max(total_align, a)
|
|
if total_align != struct.align:
|
|
raise PackingError("total alignment (%s/%s)" % (total_align, struct.align))
|
|
a = total_align
|
|
if pack is not None:
|
|
a = min(pack, a)
|
|
if size % a:
|
|
size += a - size % a
|
|
if size != struct.size:
|
|
raise PackingError("total size (%s/%s)" % (size, struct.size))
|
|
|
|
def calc_packing(struct, fields):
|
|
# try several packings, starting with unspecified packing
|
|
isStruct = isinstance(struct, typedesc.Structure)
|
|
for pack in [None, 16*8, 8*8, 4*8, 2*8, 1*8]:
|
|
try:
|
|
_calc_packing(struct, fields, pack, isStruct)
|
|
except PackingError as details:
|
|
continue
|
|
else:
|
|
if pack is None:
|
|
return None
|
|
return pack/8
|
|
raise PackingError("PACKING FAILED: %s" % details)
|
|
|
|
class PackingError(Exception):
|
|
pass
|
|
|
|
try:
|
|
set
|
|
except NameError:
|
|
# Python 2.3
|
|
from sets import Set as set
|
|
|
|
# XXX These should be filtered out in gccxmlparser.
|
|
dont_assert_size = set(
|
|
[
|
|
"__si_class_type_info_pseudo",
|
|
"__class_type_info_pseudo",
|
|
]
|
|
)
|
|
|
|
def storage(t):
|
|
# return the size and alignment of a type
|
|
if isinstance(t, typedesc.Typedef):
|
|
return storage(t.typ)
|
|
elif isinstance(t, typedesc.ArrayType):
|
|
s, a = storage(t.typ)
|
|
return s * (int(t.max) - int(t.min) + 1), a
|
|
return int(t.size), int(t.align)
|
|
|
|
################################################################
|
|
|
|
class Generator(object):
|
|
|
|
def __init__(self, ofi, known_symbols=None):
|
|
self._externals = {}
|
|
self.output = ofi
|
|
self.stream = io.StringIO()
|
|
self.imports = io.StringIO()
|
|
## self.stream = self.imports = self.output
|
|
self.known_symbols = known_symbols or {}
|
|
|
|
self.done = set() # type descriptions that have been generated
|
|
self.names = set() # names that have been generated
|
|
|
|
def generate(self, item):
|
|
if item in self.done:
|
|
return
|
|
if isinstance(item, typedesc.StructureHead):
|
|
name = getattr(item.struct, "name", None)
|
|
else:
|
|
name = getattr(item, "name", None)
|
|
if name in self.known_symbols:
|
|
mod = self.known_symbols[name]
|
|
print("from %s import %s" % (mod, name), file=self.imports)
|
|
self.done.add(item)
|
|
if isinstance(item, typedesc.Structure):
|
|
self.done.add(item.get_head())
|
|
self.done.add(item.get_body())
|
|
return
|
|
mth = getattr(self, type(item).__name__)
|
|
# to avoid infinite recursion, we have to mark it as done
|
|
# before actually generating the code.
|
|
self.done.add(item)
|
|
mth(item)
|
|
|
|
def generate_all(self, items):
|
|
for item in items:
|
|
self.generate(item)
|
|
|
|
def _make_relative_path(self, path1, path2):
|
|
"""path1 and path2 are pathnames.
|
|
Return path1 as a relative path to path2, if possible.
|
|
"""
|
|
path1 = os.path.abspath(path1)
|
|
path2 = os.path.abspath(path2)
|
|
common = os.path.commonprefix([os.path.normcase(path1),
|
|
os.path.normcase(path2)])
|
|
if not os.path.isdir(common):
|
|
return path1
|
|
if not common.endswith("\\"):
|
|
return path1
|
|
if not os.path.isdir(path2):
|
|
path2 = os.path.dirname(path2)
|
|
# strip the common prefix
|
|
path1 = path1[len(common):]
|
|
path2 = path2[len(common):]
|
|
|
|
parts2 = path2.split("\\")
|
|
return "..\\" * len(parts2) + path1
|
|
|
|
def generate_code(self, items, filename=None):
|
|
self.filename = filename
|
|
if filename is not None:
|
|
# Hm, what is the CORRECT encoding?
|
|
print("# -*- coding: mbcs -*-", file=self.output)
|
|
if os.path.isabs(filename):
|
|
# absolute path
|
|
print("typelib_path = %r" % filename, file=self.output)
|
|
elif not os.path.dirname(filename) and not os.path.isfile(filename):
|
|
# no directory given, and not in current directory.
|
|
print("typelib_path = %r" % filename, file=self.output)
|
|
else:
|
|
# relative path; make relative to comtypes.gen.
|
|
path = self._make_relative_path(filename, comtypes.gen.__path__[0])
|
|
print("import os", file=self.output)
|
|
print("typelib_path = os.path.normpath(", file=self.output)
|
|
print(" os.path.abspath(os.path.join(os.path.dirname(__file__),", file=self.output)
|
|
print(" %r)))" % path, file=self.output)
|
|
|
|
p = os.path.normpath(os.path.abspath(os.path.join(comtypes.gen.__path__[0],
|
|
path)))
|
|
assert os.path.isfile(p)
|
|
print("_lcid = 0 # change this if required", file=self.imports)
|
|
print("from ctypes import *", file=self.imports)
|
|
items = set(items)
|
|
loops = 0
|
|
while items:
|
|
loops += 1
|
|
self.more = set()
|
|
self.generate_all(items)
|
|
|
|
items |= self.more
|
|
items -= self.done
|
|
|
|
self.output.write(self.imports.getvalue())
|
|
self.output.write("\n\n")
|
|
self.output.write(self.stream.getvalue())
|
|
|
|
import textwrap
|
|
wrapper = textwrap.TextWrapper(subsequent_indent=" ",
|
|
break_long_words=False)
|
|
# XXX The space before '%s' is needed to make sure that the entire list
|
|
# does not get pushed to the next line when the first name is
|
|
# excessively long.
|
|
text = "__all__ = [ %s]" % ", ".join([repr(str(n)) for n in self.names])
|
|
|
|
for line in wrapper.wrap(text):
|
|
print(line, file=self.output)
|
|
print("from comtypes import _check_version; _check_version(%r)" % version, file=self.output)
|
|
return loops
|
|
|
|
def type_name(self, t, generate=True):
|
|
# Return a string, containing an expression which can be used
|
|
# to refer to the type. Assumes the 'from ctypes import *'
|
|
# namespace is available.
|
|
if isinstance(t, typedesc.SAFEARRAYType):
|
|
return "_midlSAFEARRAY(%s)" % self.type_name(t.typ)
|
|
## if isinstance(t, typedesc.CoClass):
|
|
## return "%s._com_interfaces_[0]" % t.name
|
|
if isinstance(t, typedesc.Typedef):
|
|
return t.name
|
|
if isinstance(t, typedesc.PointerType):
|
|
if ASSUME_STRINGS:
|
|
x = get_real_type(t.typ)
|
|
if isinstance(x, typedesc.FundamentalType):
|
|
if x.name == "char":
|
|
self.need_STRING()
|
|
return "STRING"
|
|
elif x.name == "wchar_t":
|
|
self.need_WSTRING()
|
|
return "WSTRING"
|
|
|
|
result = "POINTER(%s)" % self.type_name(t.typ, generate)
|
|
# XXX Better to inspect t.typ!
|
|
if result.startswith("POINTER(WINFUNCTYPE"):
|
|
return result[len("POINTER("):-1]
|
|
if result.startswith("POINTER(CFUNCTYPE"):
|
|
return result[len("POINTER("):-1]
|
|
elif result == "POINTER(None)":
|
|
return "c_void_p"
|
|
return result
|
|
elif isinstance(t, typedesc.ArrayType):
|
|
return "%s * %s" % (self.type_name(t.typ, generate), int(t.max)+1)
|
|
elif isinstance(t, typedesc.FunctionType):
|
|
args = [self.type_name(x, generate) for x in [t.returns] + list(t.iterArgTypes())]
|
|
if "__stdcall__" in t.attributes:
|
|
return "WINFUNCTYPE(%s)" % ", ".join(args)
|
|
else:
|
|
return "CFUNCTYPE(%s)" % ", ".join(args)
|
|
elif isinstance(t, typedesc.CvQualifiedType):
|
|
# const and volatile are ignored
|
|
return "%s" % self.type_name(t.typ, generate)
|
|
elif isinstance(t, typedesc.FundamentalType):
|
|
return ctypes_names[t.name]
|
|
elif isinstance(t, typedesc.Structure):
|
|
return t.name
|
|
elif isinstance(t, typedesc.Enumeration):
|
|
if t.name:
|
|
return t.name
|
|
return "c_int" # enums are integers
|
|
return t.name
|
|
|
|
def need_VARIANT_imports(self, value):
|
|
text = repr(value)
|
|
if "Decimal(" in text:
|
|
print("from decimal import Decimal", file=self.imports)
|
|
if "datetime.datetime(" in text:
|
|
print("import datetime", file=self.imports)
|
|
|
|
_STRING_defined = False
|
|
def need_STRING(self):
|
|
if self._STRING_defined:
|
|
return
|
|
print("STRING = c_char_p", file=self.imports)
|
|
self._STRING_defined = True
|
|
|
|
_WSTRING_defined = False
|
|
def need_WSTRING(self):
|
|
if self._WSTRING_defined:
|
|
return
|
|
print("WSTRING = c_wchar_p", file=self.imports)
|
|
self._WSTRING_defined = True
|
|
|
|
_OPENARRAYS_defined = False
|
|
def need_OPENARRAYS(self):
|
|
if self._OPENARRAYS_defined:
|
|
return
|
|
print("OPENARRAY = POINTER(c_ubyte) # hack, see comtypes/tools/codegenerator.py", file=self.imports)
|
|
self._OPENARRAYS_defined = True
|
|
|
|
_arraytypes = 0
|
|
def ArrayType(self, tp):
|
|
self._arraytypes += 1
|
|
self.generate(get_real_type(tp.typ))
|
|
self.generate(tp.typ)
|
|
|
|
_enumvalues = 0
|
|
def EnumValue(self, tp):
|
|
value = int(tp.value)
|
|
if keyword.iskeyword(tp.name):
|
|
# XXX use logging!
|
|
if __warn_on_munge__:
|
|
print("# Fixing keyword as EnumValue for %s" % tp.name)
|
|
tp.name += "_"
|
|
print("%s = %d" % (tp.name, value), file=self.stream)
|
|
self.names.add(tp.name)
|
|
self._enumvalues += 1
|
|
|
|
_enumtypes = 0
|
|
def Enumeration(self, tp):
|
|
self._enumtypes += 1
|
|
print(file=self.stream)
|
|
if tp.name:
|
|
print("# values for enumeration '%s'" % tp.name, file=self.stream)
|
|
else:
|
|
print("# values for unnamed enumeration", file=self.stream)
|
|
# Some enumerations have the same name for the enum type
|
|
# and an enum value. Excel's XlDisplayShapes is such an example.
|
|
# Since we don't have separate namespaces for the type and the values,
|
|
# we generate the TYPE last, overwriting the value. XXX
|
|
for item in tp.values:
|
|
self.generate(item)
|
|
if tp.name:
|
|
print("%s = c_int # enum" % tp.name, file=self.stream)
|
|
self.names.add(tp.name)
|
|
|
|
_GUID_defined = False
|
|
def need_GUID(self):
|
|
if self._GUID_defined:
|
|
return
|
|
self._GUID_defined = True
|
|
modname = self.known_symbols.get("GUID")
|
|
if modname:
|
|
print("from %s import GUID" % modname, file=self.imports)
|
|
|
|
_typedefs = 0
|
|
def Typedef(self, tp):
|
|
self._typedefs += 1
|
|
if type(tp.typ) in (typedesc.Structure, typedesc.Union):
|
|
self.generate(tp.typ.get_head())
|
|
self.more.add(tp.typ)
|
|
else:
|
|
self.generate(tp.typ)
|
|
if self.type_name(tp.typ) in self.known_symbols:
|
|
stream = self.imports
|
|
else:
|
|
stream = self.stream
|
|
if tp.name != self.type_name(tp.typ):
|
|
print("%s = %s" % \
|
|
(tp.name, self.type_name(tp.typ)), file=stream)
|
|
self.names.add(tp.name)
|
|
|
|
def FundamentalType(self, item):
|
|
pass # we should check if this is known somewhere
|
|
|
|
def StructureHead(self, head):
|
|
for struct in head.struct.bases:
|
|
self.generate(struct.get_head())
|
|
self.more.add(struct)
|
|
if head.struct.location:
|
|
print("# %s %s" % head.struct.location, file=self.stream)
|
|
basenames = [self.type_name(b) for b in head.struct.bases]
|
|
if basenames:
|
|
self.need_GUID()
|
|
method_names = [m.name for m in head.struct.members if type(m) is typedesc.Method]
|
|
print("class %s(%s):" % (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("class %s(_com_interface):" % head.struct.name, file=self.stream)
|
|
print(" pass", file=self.stream)
|
|
elif type(head.struct) == typedesc.Structure:
|
|
print("class %s(Structure):" % head.struct.name, file=self.stream)
|
|
if hasattr(head.struct, "_recordinfo_"):
|
|
print(" _recordinfo_ = %r" % (head.struct._recordinfo_,), file=self.stream)
|
|
else:
|
|
print(" pass", file=self.stream)
|
|
elif type(head.struct) == typedesc.Union:
|
|
print("class %s(Union):" % head.struct.name, file=self.stream)
|
|
print(" pass", file=self.stream)
|
|
self.names.add(head.struct.name)
|
|
|
|
_structures = 0
|
|
def Structure(self, struct):
|
|
self._structures += 1
|
|
self.generate(struct.get_head())
|
|
self.generate(struct.get_body())
|
|
|
|
Union = Structure
|
|
|
|
def StructureBody(self, body):
|
|
fields = []
|
|
methods = []
|
|
for m in body.struct.members:
|
|
if type(m) is typedesc.Field:
|
|
fields.append(m)
|
|
if type(m.typ) is typedesc.Typedef:
|
|
self.generate(get_real_type(m.typ))
|
|
self.generate(m.typ)
|
|
elif type(m) is typedesc.Method:
|
|
methods.append(m)
|
|
self.generate(m.returns)
|
|
self.generate_all(m.iterArgTypes())
|
|
elif type(m) is typedesc.Constructor:
|
|
pass
|
|
|
|
# we don't need _pack_ on Unions (I hope, at least), and not
|
|
# on COM interfaces:
|
|
if not methods:
|
|
try:
|
|
pack = calc_packing(body.struct, fields)
|
|
if pack is not None:
|
|
print("%s._pack_ = %s" % (body.struct.name, pack), file=self.stream)
|
|
except PackingError as details:
|
|
# if packing fails, write a warning comment to the output.
|
|
import warnings
|
|
message = "Structure %s: %s" % (body.struct.name, details)
|
|
warnings.warn(message, UserWarning)
|
|
print("# WARNING: %s" % details, file=self.stream)
|
|
|
|
if fields:
|
|
if body.struct.bases:
|
|
assert len(body.struct.bases) == 1
|
|
self.generate(body.struct.bases[0].get_body())
|
|
# field definition normally span several lines.
|
|
# Before we generate them, we need to 'import' everything they need.
|
|
# So, call type_name for each field once,
|
|
for f in fields:
|
|
self.type_name(f.typ)
|
|
print("%s._fields_ = [" % body.struct.name, file=self.stream)
|
|
if body.struct.location:
|
|
print(" # %s %s" % body.struct.location, file=self.stream)
|
|
# unnamed fields will get autogenerated names "_", "_1". "_2", "_3", ...
|
|
unnamed_index = 0
|
|
for f in fields:
|
|
if not f.name:
|
|
if unnamed_index:
|
|
fieldname = "_%d" % unnamed_index
|
|
else:
|
|
fieldname = "_"
|
|
unnamed_index += 1
|
|
print(" # Unnamed field renamed to '%s'" % fieldname, file=self.stream)
|
|
else:
|
|
fieldname = f.name
|
|
if f.bits is None:
|
|
print(" ('%s', %s)," % (fieldname, self.type_name(f.typ)), file=self.stream)
|
|
else:
|
|
print(" ('%s', %s, %s)," % (fieldname, self.type_name(f.typ), f.bits), file=self.stream)
|
|
print("]", file=self.stream)
|
|
|
|
if body.struct.size is None:
|
|
msg = ("# The size provided by the typelib is incorrect.\n"
|
|
"# The size and alignment check for %s is skipped.")
|
|
print(msg % body.struct.name, file=self.stream)
|
|
elif body.struct.name not in dont_assert_size:
|
|
size = body.struct.size // 8
|
|
print("assert sizeof(%s) == %s, sizeof(%s)" % \
|
|
(body.struct.name, size, body.struct.name), file=self.stream)
|
|
align = body.struct.align // 8
|
|
print("assert alignment(%s) == %s, alignment(%s)" % \
|
|
(body.struct.name, align, body.struct.name), file=self.stream)
|
|
|
|
if methods:
|
|
self.need_COMMETHOD()
|
|
# method definitions normally span several lines.
|
|
# Before we generate them, we need to 'import' everything they need.
|
|
# So, call type_name for each field once,
|
|
for m in methods:
|
|
self.type_name(m.returns)
|
|
for a in m.iterArgTypes():
|
|
self.type_name(a)
|
|
print("%s._methods_ = [" % body.struct.name, file=self.stream)
|
|
if body.struct.location:
|
|
print("# %s %s" % body.struct.location, file=self.stream)
|
|
|
|
for m in methods:
|
|
if m.location:
|
|
print(" # %s %s" % m.location, file=self.stream)
|
|
print(" COMMETHOD([], %s, '%s'," % (
|
|
self.type_name(m.returns),
|
|
m.name), file=self.stream)
|
|
for a in m.iterArgTypes():
|
|
print(" ( [], %s, )," % self.type_name(a), file=self.stream)
|
|
print(" ),", file=self.stream)
|
|
print("]", file=self.stream)
|
|
|
|
_midlSAFEARRAY_defined = False
|
|
def need_midlSAFEARRAY(self):
|
|
if self._midlSAFEARRAY_defined:
|
|
return
|
|
print("from comtypes.automation import _midlSAFEARRAY", file=self.imports)
|
|
self._midlSAFEARRAY_defined = True
|
|
|
|
_CoClass_defined = False
|
|
def need_CoClass(self):
|
|
if self._CoClass_defined:
|
|
return
|
|
print("from comtypes import CoClass", file=self.imports)
|
|
self._CoClass_defined = True
|
|
|
|
_dispid_defined = False
|
|
def need_dispid(self):
|
|
if self._dispid_defined:
|
|
return
|
|
print("from comtypes import dispid", file=self.imports)
|
|
self._dispid_defined = True
|
|
|
|
_COMMETHOD_defined = False
|
|
def need_COMMETHOD(self):
|
|
if self._COMMETHOD_defined:
|
|
return
|
|
print("from comtypes import helpstring", file=self.imports)
|
|
print("from comtypes import COMMETHOD", file=self.imports)
|
|
self._COMMETHOD_defined = True
|
|
|
|
_DISPMETHOD_defined = False
|
|
def need_DISPMETHOD(self):
|
|
if self._DISPMETHOD_defined:
|
|
return
|
|
print("from comtypes import DISPMETHOD, DISPPROPERTY, helpstring", file=self.imports)
|
|
self._DISPMETHOD_defined = True
|
|
|
|
################################################################
|
|
# top-level typedesc generators
|
|
#
|
|
def TypeLib(self, lib):
|
|
# lib.name, lib.gui, lib.major, lib.minor, lib.doc
|
|
|
|
# Hm, in user code we have to write:
|
|
# class MyServer(COMObject, ...):
|
|
# _com_interfaces_ = [MyTypeLib.IInterface]
|
|
# _reg_typelib_ = MyTypeLib.Library._reg_typelib_
|
|
# ^^^^^^^
|
|
# Should the '_reg_typelib_' attribute be at top-level in the
|
|
# generated code, instead as being an attribute of the
|
|
# 'Library' symbol?
|
|
print("class Library(object):", file=self.stream)
|
|
if lib.doc:
|
|
print(" %r" % lib.doc, file=self.stream)
|
|
if lib.name:
|
|
print(" name = %r" % lib.name, file=self.stream)
|
|
print(" _reg_typelib_ = (%r, %r, %r)" % (lib.guid, lib.major, lib.minor), file=self.stream)
|
|
print(file=self.stream)
|
|
|
|
def External(self, ext):
|
|
# ext.docs - docstring of typelib
|
|
# ext.symbol_name - symbol to generate
|
|
# ext.tlib - the ITypeLib pointer to the typelibrary containing the symbols definition
|
|
#
|
|
# ext.name filled in here
|
|
|
|
libdesc = str(ext.tlib.GetLibAttr()) # str(TLIBATTR) is unique for a given typelib
|
|
if libdesc in self._externals: # typelib wrapper already created
|
|
modname = self._externals[libdesc]
|
|
# we must fill in ext.name, it is used by self.type_name()
|
|
ext.name = "%s.%s" % (modname, ext.symbol_name)
|
|
return
|
|
|
|
modname = comtypes.client._generate._name_module(ext.tlib)
|
|
ext.name = "%s.%s" % (modname, ext.symbol_name)
|
|
self._externals[libdesc] = modname
|
|
print("import", modname, file=self.imports)
|
|
comtypes.client.GetModule(ext.tlib)
|
|
|
|
def Constant(self, tp):
|
|
print("%s = %r # Constant %s" % (tp.name,
|
|
tp.value,
|
|
self.type_name(tp.typ, False)), file=self.stream)
|
|
self.names.add(tp.name)
|
|
|
|
def SAFEARRAYType(self, sa):
|
|
self.generate(sa.typ)
|
|
self.need_midlSAFEARRAY()
|
|
|
|
_pointertypes = 0
|
|
def PointerType(self, tp):
|
|
self._pointertypes += 1
|
|
if type(tp.typ) is typedesc.ComInterface:
|
|
# this defines the class
|
|
self.generate(tp.typ.get_head())
|
|
# this defines the _methods_
|
|
self.more.add(tp.typ)
|
|
elif type(tp.typ) is typedesc.PointerType:
|
|
self.generate(tp.typ)
|
|
elif type(tp.typ) in (typedesc.Union, typedesc.Structure):
|
|
self.generate(tp.typ.get_head())
|
|
self.more.add(tp.typ)
|
|
elif type(tp.typ) is typedesc.Typedef:
|
|
self.generate(tp.typ)
|
|
else:
|
|
self.generate(tp.typ)
|
|
|
|
def CoClass(self, coclass):
|
|
self.need_GUID()
|
|
self.need_CoClass()
|
|
print("class %s(CoClass):" % coclass.name, file=self.stream)
|
|
doc = getattr(coclass, "doc", None)
|
|
if doc:
|
|
print(" %r" % doc, file=self.stream)
|
|
print(" _reg_clsid_ = GUID(%r)" % coclass.clsid, file=self.stream)
|
|
print(" _idlflags_ = %s" % coclass.idlflags, file=self.stream)
|
|
if self.filename is not None:
|
|
print(" _typelib_path_ = typelib_path", file=self.stream)
|
|
##X print >> self.stream, "POINTER(%s).__ctypes_from_outparam__ = wrap" % coclass.name
|
|
|
|
libid = coclass.tlibattr.guid
|
|
wMajor, wMinor = coclass.tlibattr.wMajorVerNum, coclass.tlibattr.wMinorVerNum
|
|
print(" _reg_typelib_ = (%r, %s, %s)" % (str(libid), wMajor, wMinor), file=self.stream)
|
|
|
|
for itf, idlflags in coclass.interfaces:
|
|
self.generate(itf.get_head())
|
|
implemented = []
|
|
sources = []
|
|
for item in coclass.interfaces:
|
|
# item is (interface class, impltypeflags)
|
|
if item[1] & 2: # IMPLTYPEFLAG_FSOURCE
|
|
# source interface
|
|
where = sources
|
|
else:
|
|
# sink interface
|
|
where = implemented
|
|
if item[1] & 1: # IMPLTYPEFLAG_FDEAULT
|
|
# The default interface should be the first item on the list
|
|
where.insert(0, item[0].name)
|
|
else:
|
|
where.append(item[0].name)
|
|
if implemented:
|
|
print("%s._com_interfaces_ = [%s]" % (coclass.name, ", ".join(implemented)), file=self.stream)
|
|
if sources:
|
|
print("%s._outgoing_interfaces_ = [%s]" % (coclass.name, ", ".join(sources)), file=self.stream)
|
|
print(file=self.stream)
|
|
self.names.add(coclass.name)
|
|
|
|
def ComInterface(self, itf):
|
|
self.generate(itf.get_head())
|
|
self.generate(itf.get_body())
|
|
self.names.add(itf.name)
|
|
|
|
def _is_enuminterface(self, itf):
|
|
# Check if this is an IEnumXXX interface
|
|
if not itf.name.startswith("IEnum"):
|
|
return False
|
|
member_names = [mth.name for mth in itf.members]
|
|
for name in ("Next", "Skip", "Reset", "Clone"):
|
|
if name not in member_names:
|
|
return False
|
|
return True
|
|
|
|
def ComInterfaceHead(self, head):
|
|
if head.itf.name in self.known_symbols:
|
|
return
|
|
base = head.itf.base
|
|
if head.itf.base is None:
|
|
# we don't beed to generate IUnknown
|
|
return
|
|
self.generate(base.get_head())
|
|
self.more.add(base)
|
|
basename = self.type_name(head.itf.base)
|
|
|
|
self.need_GUID()
|
|
print("class %s(%s):" % (head.itf.name, basename), file=self.stream)
|
|
print(" _case_insensitive_ = True", file=self.stream)
|
|
doc = getattr(head.itf, "doc", None)
|
|
if doc:
|
|
print(" %r" % doc, file=self.stream)
|
|
print(" _iid_ = GUID(%r)" % head.itf.iid, file=self.stream)
|
|
print(" _idlflags_ = %s" % head.itf.idlflags, file=self.stream)
|
|
|
|
if self._is_enuminterface(head.itf):
|
|
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(" item, fetched = self.Next(1)", file=self.stream)
|
|
print(" if fetched:", file=self.stream)
|
|
print(" return item", file=self.stream)
|
|
print(" raise StopIteration", file=self.stream)
|
|
print(file=self.stream)
|
|
|
|
print(" def __getitem__(self, index):", file=self.stream)
|
|
print(" self.Reset()", file=self.stream)
|
|
print(" self.Skip(index)", file=self.stream)
|
|
print(" item, fetched = self.Next(1)", file=self.stream)
|
|
print(" if fetched:", file=self.stream)
|
|
print(" return item", file=self.stream)
|
|
print(" raise IndexError(index)", file=self.stream)
|
|
print(file=self.stream)
|
|
|
|
def ComInterfaceBody(self, body):
|
|
# The base class must be fully generated, including the
|
|
# _methods_ list.
|
|
self.generate(body.itf.base)
|
|
|
|
# make sure we can generate the body
|
|
for m in body.itf.members:
|
|
for a in m.arguments:
|
|
self.generate(a[0])
|
|
self.generate(m.returns)
|
|
|
|
self.need_COMMETHOD()
|
|
self.need_dispid()
|
|
print("%s._methods_ = [" % body.itf.name, file=self.stream)
|
|
for m in body.itf.members:
|
|
if isinstance(m, typedesc.ComMethod):
|
|
self.make_ComMethod(m, "dual" in body.itf.idlflags)
|
|
else:
|
|
raise TypeError("what's this?")
|
|
|
|
print("]", file=self.stream)
|
|
print("################################################################", file=self.stream)
|
|
print("## code template for %s implementation" % body.itf.name, file=self.stream)
|
|
print("##class %s_Impl(object):" % body.itf.name, file=self.stream)
|
|
|
|
methods = {}
|
|
for m in body.itf.members:
|
|
if isinstance(m, typedesc.ComMethod):
|
|
# m.arguments is a sequence of tuples:
|
|
# (argtype, argname, idlflags, docstring)
|
|
# Some typelibs have unnamed method parameters!
|
|
inargs = [a[1] or '<unnamed>' for a in m.arguments
|
|
if not 'out' in a[2]]
|
|
outargs = [a[1] or '<unnamed>' for a in m.arguments
|
|
if 'out' in a[2]]
|
|
if 'propget' in m.idlflags:
|
|
methods.setdefault(m.name, [0, inargs, outargs, m.doc])[0] |= 1
|
|
elif 'propput' in m.idlflags:
|
|
methods.setdefault(m.name, [0, inargs[:-1], inargs[-1:], m.doc])[0] |= 2
|
|
else:
|
|
methods[m.name] = [0, inargs, outargs, m.doc]
|
|
|
|
for name, (typ, inargs, outargs, doc) in methods.items():
|
|
if typ == 0: # method
|
|
print("## def %s(%s):" % (name, ", ".join(["self"] + inargs)), file=self.stream)
|
|
print("## %r" % (doc or "-no docstring-"), file=self.stream)
|
|
print("## #return %s" % (", ".join(outargs)), file=self.stream)
|
|
elif typ == 1: # propget
|
|
print("## @property", file=self.stream)
|
|
print("## def %s(%s):" % (name, ", ".join(["self"] + inargs)), file=self.stream)
|
|
print("## %r" % (doc or "-no docstring-"), file=self.stream)
|
|
print("## #return %s" % (", ".join(outargs)), file=self.stream)
|
|
elif typ == 2: # propput
|
|
print("## def _set(%s):" % ", ".join(["self"] + inargs + outargs), file=self.stream)
|
|
print("## %r" % (doc or "-no docstring-"), file=self.stream)
|
|
print("## %s = property(fset = _set, doc = _set.__doc__)" % name, file=self.stream)
|
|
elif typ == 3: # propget + propput
|
|
print("## def _get(%s):" % ", ".join(["self"] + inargs), file=self.stream)
|
|
print("## %r" % (doc or "-no docstring-"), file=self.stream)
|
|
print("## #return %s" % (", ".join(outargs)), file=self.stream)
|
|
print("## def _set(%s):" % ", ".join(["self"] + inargs + outargs), file=self.stream)
|
|
print("## %r" % (doc or "-no docstring-"), file=self.stream)
|
|
print("## %s = property(_get, _set, doc = _set.__doc__)" % name, file=self.stream)
|
|
else:
|
|
raise RuntimeError("BUG")
|
|
print("##", file=self.stream)
|
|
print(file=self.stream)
|
|
|
|
def DispInterface(self, itf):
|
|
self.generate(itf.get_head())
|
|
self.generate(itf.get_body())
|
|
self.names.add(itf.name)
|
|
|
|
def DispInterfaceHead(self, head):
|
|
self.generate(head.itf.base)
|
|
basename = self.type_name(head.itf.base)
|
|
|
|
self.need_GUID()
|
|
print("class %s(%s):" % (head.itf.name, basename), file=self.stream)
|
|
print(" _case_insensitive_ = True", file=self.stream)
|
|
doc = getattr(head.itf, "doc", None)
|
|
if doc:
|
|
print(" %r" % doc, file=self.stream)
|
|
print(" _iid_ = GUID(%r)" % head.itf.iid, file=self.stream)
|
|
print(" _idlflags_ = %s" % head.itf.idlflags, file=self.stream)
|
|
print(" _methods_ = []", file=self.stream)
|
|
|
|
def DispInterfaceBody(self, body):
|
|
# make sure we can generate the body
|
|
for m in body.itf.members:
|
|
if isinstance(m, typedesc.DispMethod):
|
|
for a in m.arguments:
|
|
self.generate(a[0])
|
|
self.generate(m.returns)
|
|
elif isinstance(m, typedesc.DispProperty):
|
|
self.generate(m.typ)
|
|
else:
|
|
raise TypeError(m)
|
|
|
|
self.need_dispid()
|
|
self.need_DISPMETHOD()
|
|
print("%s._disp_methods_ = [" % body.itf.name, file=self.stream)
|
|
for m in body.itf.members:
|
|
if isinstance(m, typedesc.DispMethod):
|
|
self.make_DispMethod(m)
|
|
elif isinstance(m, typedesc.DispProperty):
|
|
self.make_DispProperty(m)
|
|
else:
|
|
raise TypeError(m)
|
|
print("]", file=self.stream)
|
|
|
|
################################################################
|
|
# non-toplevel method generators
|
|
#
|
|
def make_ComMethod(self, m, isdual):
|
|
# typ, name, idlflags, default
|
|
if isdual:
|
|
idlflags = [dispid(m.memid)] + m.idlflags
|
|
else:
|
|
# We don't include the dispid for non-dispatch COM interfaces
|
|
idlflags = m.idlflags
|
|
if __debug__ and m.doc:
|
|
idlflags.insert(1, helpstring(m.doc))
|
|
code = " COMMETHOD(%r, %s, '%s'" % (
|
|
idlflags,
|
|
self.type_name(m.returns),
|
|
m.name)
|
|
|
|
if not m.arguments:
|
|
print("%s)," % code, file=self.stream)
|
|
else:
|
|
print("%s," % code, file=self.stream)
|
|
self.stream.write(" ")
|
|
arglist = []
|
|
for typ, name, idlflags, default in m.arguments:
|
|
type_name = self.type_name(typ)
|
|
###########################################################
|
|
# IDL files that contain 'open arrays' or 'conformant
|
|
# varying arrays' method parameters are strange.
|
|
# These arrays have both a 'size_is()' and
|
|
# 'length_is()' attribute, like this example from
|
|
# dia2.idl (in the DIA SDK):
|
|
#
|
|
# interface IDiaSymbol: IUnknown {
|
|
# ...
|
|
# HRESULT get_dataBytes(
|
|
# [in] DWORD cbData,
|
|
# [out] DWORD *pcbData,
|
|
# [out, size_is(cbData),
|
|
# length_is(*pcbData)] BYTE data[]
|
|
# );
|
|
#
|
|
# The really strange thing is that the decompiled type
|
|
# library then contains this declaration, which declares
|
|
# the interface itself as [out] method parameter:
|
|
#
|
|
# interface IDiaSymbol: IUnknown {
|
|
# ...
|
|
# HRESULT _stdcall get_dataBytes(
|
|
# [in] unsigned long cbData,
|
|
# [out] unsigned long* pcbData,
|
|
# [out] IDiaSymbol data);
|
|
#
|
|
# Of course, comtypes does not accept a COM interface
|
|
# as method parameter; so replace the parameter type
|
|
# with the comtypes spelling of 'unsigned char *', and
|
|
# mark the parameter as [in, out], so the IDL
|
|
# equivalent would be like this:
|
|
#
|
|
# interface IDiaSymbol: IUnknown {
|
|
# ...
|
|
# HRESULT _stdcall get_dataBytes(
|
|
# [in] unsigned long cbData,
|
|
# [out] unsigned long* pcbData,
|
|
# [in, out] BYTE data[]);
|
|
###########################################################
|
|
if isinstance(typ, typedesc.ComInterface):
|
|
self.need_OPENARRAYS()
|
|
type_name = "OPENARRAY"
|
|
if 'in' not in idlflags:
|
|
idlflags.append('in')
|
|
if 'lcid' in idlflags:# and 'in' in idlflags:
|
|
default = lcid
|
|
if default is not None:
|
|
self.need_VARIANT_imports(default)
|
|
arglist.append("( %r, %s, '%s', %r )" % (
|
|
idlflags,
|
|
type_name,
|
|
name,
|
|
default))
|
|
else:
|
|
arglist.append("( %r, %s, '%s' )" % (
|
|
idlflags,
|
|
type_name,
|
|
name))
|
|
self.stream.write(",\n ".join(arglist))
|
|
print("),", file=self.stream)
|
|
|
|
def make_DispMethod(self, m):
|
|
idlflags = [dispid(m.dispid)] + m.idlflags
|
|
if __debug__ and m.doc:
|
|
idlflags.insert(1, helpstring(m.doc))
|
|
# typ, name, idlflags, default
|
|
code = " DISPMETHOD(%r, %s, '%s'" % (
|
|
idlflags,
|
|
self.type_name(m.returns),
|
|
m.name)
|
|
|
|
if not m.arguments:
|
|
print("%s)," % code, file=self.stream)
|
|
else:
|
|
print("%s," % code, file=self.stream)
|
|
self.stream.write(" ")
|
|
arglist = []
|
|
for typ, name, idlflags, default in m.arguments:
|
|
self.need_VARIANT_imports(default)
|
|
if default is not None:
|
|
arglist.append("( %r, %s, '%s', %r )" % (
|
|
idlflags,
|
|
self.type_name(typ),
|
|
name,
|
|
default))
|
|
else:
|
|
arglist.append("( %r, %s, '%s' )" % (
|
|
idlflags,
|
|
self.type_name(typ),
|
|
name,
|
|
))
|
|
self.stream.write(",\n ".join(arglist))
|
|
print("),", file=self.stream)
|
|
|
|
def make_DispProperty(self, prop):
|
|
idlflags = [dispid(prop.dispid)] + prop.idlflags
|
|
if __debug__ and prop.doc:
|
|
idlflags.insert(1, helpstring(prop.doc))
|
|
print(" DISPPROPERTY(%r, %s, '%s')," % (
|
|
idlflags,
|
|
self.type_name(prop.typ),
|
|
prop.name), file=self.stream)
|
|
|
|
# shortcut for development
|
|
if __name__ == "__main__":
|
|
from . import tlbparser
|
|
tlbparser.main()
|