mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-10-30 21:35:06 +00:00
111 lines
5.0 KiB
Python
111 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Write the method tables of the COM interfaces that a Raven component implements.
|
|
|
|
Every interface starts as a table of stubs, one per method in the order of the public headers, so an object can be completed one method
|
|
at a time and a call to a method nobody wrote shows up by name. The output holds, for each interface, an enumeration of the method
|
|
positions, the stubs that report themselves through the component's `unimplemented` function, and the default table.
|
|
|
|
The methods are read from the MinGW headers in any of their three forms: a C method table that starts from its base interface's table
|
|
(`IBaseVtbl Base;`), a C method table that lists every method, or only the C++ declaration `interface Name : public Base { ... };`.
|
|
|
|
usage: generate-com-tables.py INCLUDE_DIRECTORY OUTPUT.h TITLE HEADER:INTERFACE...
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
UNKNOWN = [("QueryInterface", 2), ("AddRef", 0), ("Release", 0)]
|
|
METHOD = re.compile(r"STDMETHOD_?\(\s*(?:[^,()]+,\s*)?(\w+)\s*\)|\(STDMETHODCALLTYPE\s*\*\s*(\w+)\)")
|
|
# Arguments wider than a 32-bit word when passed by value: a 32-bit caller pushes this many words for them.
|
|
WIDE = {"VARIANT": 4, "DECIMAL": 4, "DATE": 2, "double": 2, "LONGLONG": 2, "ULONGLONG": 2, "INT64": 2, "UINT64": 2, "LARGE_INTEGER": 2,
|
|
"ULARGE_INTEGER": 2, "FILETIME": 2, "CY": 2, "POINT": 2, "SIZE": 2, "RECT": 4, "D2D1_POINT_2F": 2, "D2D1_SIZE_F": 2}
|
|
|
|
|
|
def body_of(text, opening):
|
|
"""The text between the brace that ends `opening` and its matching close."""
|
|
start = text.index("{", opening.end() - 1)
|
|
depth = 0
|
|
for at in range(start, len(text)):
|
|
if text[at] == "{":
|
|
depth += 1
|
|
elif text[at] == "}":
|
|
depth -= 1
|
|
if not depth:
|
|
return text[start + 1:at]
|
|
raise SystemExit("unbalanced braces")
|
|
|
|
|
|
def words(body, match):
|
|
"""The 32-bit stack words of the arguments after `This` that the C method table gives the method, which a stub must take so that it
|
|
leaves a 32-bit caller's stack as the real method would. A C++ declaration has no table and counts none."""
|
|
if not match.group(2):
|
|
return 0
|
|
rest = body[match.end():]
|
|
start = rest.index("(")
|
|
depth, at = 0, start
|
|
for at in range(start, len(rest)):
|
|
depth += {"(": 1, ")": -1}.get(rest[at], 0)
|
|
if not depth:
|
|
break
|
|
arguments = [argument.strip() for argument in rest[start + 1:at].split(",")][1:]
|
|
total = 0
|
|
for argument in arguments:
|
|
if "*" in argument:
|
|
total += 1
|
|
continue
|
|
kind = argument.replace("const ", "").split()[0] if argument else ""
|
|
total += WIDE.get(kind, 1)
|
|
return total
|
|
|
|
|
|
def methods(headers, name):
|
|
if name == "IUnknown":
|
|
return list(UNKNOWN)
|
|
for text in headers:
|
|
table = re.search(r"typedef struct %sVtbl\s*\{" % name, text)
|
|
if table:
|
|
body = body_of(text, table)
|
|
base = re.search(r"\b(\w+)Vtbl\s+Base\s*;", body)
|
|
found = methods(headers, base.group(1)) if base else []
|
|
found += [(m.group(1) or m.group(2), words(body, m)) for m in METHOD.finditer(body)]
|
|
return found
|
|
for text in headers:
|
|
declaration = re.search(r"interface\s+%s\s*:\s*public\s+(\w+)\s*\{" % name, text)
|
|
if declaration:
|
|
body = body_of(text, declaration)
|
|
return methods(headers, declaration.group(1)) + [(m.group(1) or m.group(2), 0) for m in METHOD.finditer(body)]
|
|
raise SystemExit(f"no methods found for {name}")
|
|
|
|
|
|
def main():
|
|
include, output, title = Path(sys.argv[1]), Path(sys.argv[2]), sys.argv[3]
|
|
wanted = [item.split(":") for item in sys.argv[4:]]
|
|
headers = [(include / header).read_text() for header in dict.fromkeys(header for header, _ in wanted)]
|
|
out = [f"/* Generated by scripts/generate-com-tables.py from the {title} headers. Do not edit. */\n",
|
|
"/* The stubs return E_NOTIMPL and say which method was called; a table starts as all stubs. */\n"]
|
|
for _, name in wanted:
|
|
found = methods(headers, name)
|
|
names = [method for method, _ in found]
|
|
# An overloaded name takes its position as a suffix after its first use.
|
|
seen = {}
|
|
for index, method in enumerate(names):
|
|
if method in seen:
|
|
names[index] = f"{method}_{index}"
|
|
seen.setdefault(method, index)
|
|
out.append("\nenum {\n")
|
|
for index, method in enumerate(names):
|
|
out.append(f"\t{name}_{method}_slot = {index},\n")
|
|
out.append(f"\t{name}_methods = {len(names)}\n}};\n")
|
|
for index, method in enumerate(names):
|
|
arguments = "".join(f", DWORD a{word}" for word in range(found[index][1]))
|
|
out.append(f'static HRESULT STDMETHODCALLTYPE {name}_stub_{index}(void *self{arguments}) {{ return unimplemented("{name}", "{method}"); }}\n')
|
|
out.append(f"static void *{name}_table[{len(names)}] = {{\n")
|
|
for index in range(len(names)):
|
|
out.append(f"\t(void *){name}_stub_{index},\n")
|
|
out.append("};\n")
|
|
output.write_text("".join(out))
|
|
|
|
|
|
main()
|