Skip to content

utils COM typelib

utils_COM_typelib

Module provides experimental typelib support.

compileTypeLib(cls)

Source code in src\utils_COMobjects\utils_COM_typelib.py
def compileTypeLib(cls):  # docsig: disable=SIG101
    idl = get_filename(cls)
    tlb = os.path.splitext(idl)[0] + '.tlb'
    # tlb = pathlib.Path(idl).stem + '.tlb'
    if newer(idl, tlb):
        print("Compiling %s" % (idl,))
        rc = os.system('midl "%s"' % (idl,))
        if rc:
            err_msg = f"Compiling typelib for {cls.__name__} with MIDL failed!"
            raise RuntimeError(err_msg)
        # can't prevent MIDL from generating the stubs, just nuke them
        tlbdir = os.path.dirname(inspect.getfile(cls))
        # tlbdir = pathlib.Path(inspect.getfile(cls)).parent
        for helpfile in f"dlldata.c {cls.__name__ + '_i.c'} {cls.__name__ + '_p.c'} {cls.__name__ + '.h'}".split():
            os.remove(os.path.join(tlbdir, helpfile))

generateIDL(cls, clsmodule: types.ModuleType) -> bool

Source code in src\utils_COMobjects\utils_COM_typelib.py
def generateIDL(cls, clsmodule: types.ModuleType) -> bool:  # docsig: disable=SIG101

    def setinterface(classname, cominterfaces: list[str]) -> str:  # docsig: disable=SIG101
        if len(cominterfaces) == 0:
            return "I" + str(classname)
        else:
            return cominterfaces[0]

    def PythonTypeToIDLType(annotations, argname: str) -> str:  # docsig: disable=SIG101
        argtypeIDL = None
        try:
            if argname in annotations:
                pythonargtype = annotations[argname]
                argtypePy = pythonargtype.__name__
                argtypeIDL = {
                    'bool' : "VARIANT_BOOL",
                    'str'  : "BSTR*",
                    'float': "double",
                    'int'  : "long",
                    ''     : "VARIANT*"
                }[argtypePy]
            else:
                argtypeIDL = "VARIANT*"
        except Exception as err:
            #  print("Error: " + str(err) + "\n")
            argtypeIDL = "VARIANT*"
        return argtypeIDL

    # list of all classes assigned to same typelib
    clslist = get_typelib_classes(cls, clsmodule)
    if len(clslist) > 1:
        print(f"Typelib generation not only for {cls.__name__} but all classes defined in {cls.__module__} assigned to {cls._reg_typelib_filename_}")
    # determine filename
    idlfile = get_filename(cls, ".idl")
    tlbfile = get_filename(cls, ".tlb")

    okIDL = True

    # update IDL if python module file changed
    if newer(sys.modules[cls.__module__].__file__, idlfile):

        # import IDL from other files
        idl = "// Generated .IDL file (by Python code)\n//\n" + f"// typelib filename: {tlbfile} \n\n"
        idl += "import \"oaidl.idl\";\nimport \"ocidl.idl\";\nimport \"unknwn.idl\";\n\n"

        for clslistmemberid, clslistmember in clslist:

            # determine interface name
            interface = setinterface(clslistmember.__name__, clslistmember._com_interfaces_)

            # interface header
            idl += "[\n"
            idl += "\tobject,\n"
            idl += "\tuuid(" + clslistmember._typelib_interfaceID_[1:-1] + "),\n"
            idl += "\tdual,\n"
            idl += "\thelpstring(\"" + interface + " Interface\"),\n"
            idl += "\tpointer_default(unique)\n"
            idl += "]\n"
            idl += "interface " + interface + " : IDispatch\n"

            # counter for interface items
            dispid = 1  # start from 1 as zero equates to default member
            idl += "{\n"

            # interface methods
            if hasattr(clslistmember, '_public_methods_'):
                clsmember = inspect.getmembers(clslistmember, inspect.isfunction or inspect.ismethod)
                # for method in clslistmember._public_methods_:
                for method in clsmember:
                    if method[0] in clslistmember._public_methods_:
                        idl += "\tid(" + str(dispid) + "), helpstring(\"method " + method[0] + "\")]\n"
                        idl += "\tHRESULT " + method[0] + "(\n"
                        fullargspec = inspect.getfullargspec(method[1])
                        arga = len(fullargspec.annotations)
                        if arga > 0:
                            argc = len(fullargspec.args)
                            if argc > 0:
                                if isinstance(cls.__dict__[method[0]], staticmethod):
                                    argidxstart = 0
                                else:
                                    argidxstart = 1
                                for argidx in range(argidxstart, argc):
                                    arg = fullargspec.args[argidx]
                                    argtype = PythonTypeToIDLType(fullargspec.annotations, arg)
                                    idl += "\t\t[in] " + argtype + " " + arg + ",\n"
                            argtype = PythonTypeToIDLType(fullargspec.annotations, "return")
                            # idl += "\t\t[out, retval] " + argtype + "* retval\n"
                            idl += "\t\t[out, retval] " + argtype
                            if argtype[-1] != "*":
                                idl += "*"
                            idl += " retval\n"
                        else:
                            print(f"Type information not available for {clslistmember.__name__}.{method[0]}. Please check IDL-File.")
                            okIDL = False
                        dispid += 1
                        idl += "\t);\n"

            # interface properties
            if hasattr(clslistmember, '_public_attrs_'):
                for attr in clslistmember._public_attrs_:
                    idl += "\t[id(" + str(dispid) + "), propget, helpstring(\"property getter for " + attr + "\")]\n"
                    idl += "\tHRESULT " + attr + "(\n"
                    idl += "\t\t[out, retval] VARIANT *getval\n"
                    idl += "\t);\n"
                    if hasattr(clslistmember, '_readonly_attrs_'):
                        if attr not in clslistmember._readonly_attrs_:
                            idl += "\tid(" + str(dispid) + "), propput, helpstring(\"property setter for " + attr + "\")]\n"
                            idl += "\tHRESULT " + attr + "(\n"
                            idl += "\t\t[in] VARIANT setval\n"
                            idl += "\t);\n"
                    dispid += 1

            idl += "};\n\n"

        # library header
        idl += "[\n"
        idl += "\tuuid(" + cls._typelib_guid_[1:-1] + "),\n"
        idl += "\tversion(" + str(cls._typelib_version_[0]) + "." + str(cls._typelib_version_[1]) + "),\n"
        idl += "\thelpstring(\"" + cls._reg_typelib_filename_ + " - Python generated Type Library for COM registration\")\n"
        idl += "]\n"
        typelibname = getattr(cls, "_typelib_name_", "")
        if typelibname == "":
            typelibname = cls.__name__
        idl += "library " + typelibname + "\n"
        # library imports
        idl += "{\n"
        idl += "\t// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}\n"
        idl += "\timportlib(\"stdole32.tlb\");\n"
        idl += "\timportlib(\"stdole2.tlb\");\n\n"

        # library enums
        enumclslist = get_enum_classes(clsmodule)
        for clslistmemberid, clslistmember in enumclslist:
            idl += "\ttypedef enum {\n"
            firstkey = True
            for enumkey in clslistmember.__members__:
                if firstkey:
                    firstkey = False
                else:
                    idl += ",\n"
                if isinstance(clslistmember[enumkey].value, str):
                    idl += "\t\t" + enumkey + " = \"" + clslistmember[enumkey].value + "\""
                else:
                    idl += "\t\t" + enumkey + " = " + str(clslistmember[enumkey].value) + ""
            idl += "\n\t} " + clslistmember.__name__ + ";\n"
        if len(enumclslist) > 0:
            idl += "\n"

        for clslistmemberid, clslistmember in clslist:

            # library class
            interface = setinterface(clslistmember.__name__, clslistmember._com_interfaces_)   # determine interface name again because new loop
            idl += "\t[\n"
            idl += "\t\tuuid(" + clslistmember._reg_clsid_[1:-1] + "),\n"
            idl += "\t\thelpstring(\"coclass COM object for Python class " + clslistmember.__name__ + "\"),\n"
            idl += "\t]\n"
            idl += "\tcoclass " + clslistmember.__name__ + " : IDispatch\n"
            idl += "\t{\n"
            idl += "\t\t[default] interface " + interface + ";\n"
            idl += "\t}\n"

        idl += "}"

        if not okIDL:
            idlfile = idlfile.replace(".idl", "_to-be-checked.idl")
        with open(idlfile, "w+") as f:
            f.write(idl)

    else:
        okIDL = False

    return okIDL

get_enum_classes(clsmodule: types.ModuleType) -> list[Any]

Source code in src\utils_COMobjects\utils_COM_typelib.py
def get_enum_classes(clsmodule: types.ModuleType) -> list[Any]:  # docsig: disable=SIG101
    return inspect.getmembers(clsmodule, lambda clsmember: getattr(getattr(clsmember, "__base__", ""), "__name__", "") == "Enum" and inspect.isclass)

get_filename(cls, ext: str = '.idl') -> str

Source code in src\utils_COMobjects\utils_COM_typelib.py
def get_filename(cls, ext: str = ".idl") -> str:  # docsig: disable=SIG101
    if cls._reg_typelib_filename_ == "" or cls._reg_typelib_filename_ == cls.__name__ + ".tlb" or cls._reg_typelib_filename_ == cls.__name__:
        return os.path.join(os.path.dirname(inspect.getfile(cls)), cls.__name__ + ext)
        # return pathlib.Path(inspect.getfile(cls)).parent.joinpath(cls.__name__ + ext)
    else:
        return os.path.join(os.path.dirname(inspect.getfile(cls)), os.path.splitext(cls._reg_typelib_filename_)[0] + ext)

get_typelib_classes(cls, clsmodule: types.ModuleType) -> list[Any]

Source code in src\utils_COMobjects\utils_COM_typelib.py
def get_typelib_classes(cls, clsmodule: types.ModuleType) -> list[Any]:  # docsig: disable=SIG101
    tlbfile = cls._reg_typelib_filename_
    if tlbfile == "":
        return [cls]
    else:
        return inspect.getmembers(clsmodule, lambda clsmember: getattr(clsmember, "_reg_typelib_filename_", "") == tlbfile and inspect.isclass)

registerTypeLib(cls)

Source code in src\utils_COMobjects\utils_COM_typelib.py
def registerTypeLib(cls):  # docsig: disable=SIG101

    def registerTypeLibfile(tlbfile: str):
        tlbfile = os.path.abspath(tlbfile)
        # tlbfile = pathlib.Path(tlbfile).absolute()
        typelib = pythoncom.LoadTypeLib(tlbfile)
        pythoncom.RegisterTypeLib(typelib, tlbfile)

    unregister_typelib(cls)
    tlbfile = getattr(cls, "_reg_typelib_filename_", "")
    if tlbfile == "":
        tlbfile = get_filename(cls, ".tbl")
    if tlbfile != "":
        registerTypeLibfile(tlbfile)

unregister_typelib(cls)

Source code in src\utils_COMobjects\utils_COM_typelib.py
def unregister_typelib(cls):  # docsig: disable=SIG101
    tlb_guid = getattr(cls, "_typelib_guid_")
    major, minor = getattr(cls, "_typelib_version_", (1, 0))
    lcid = getattr(cls, "_typelib_lcid_", 0)
    try:
        pythoncom.UnRegisterTypeLib(tlb_guid, major, minor, lcid)
        print('Unregistered type library for class {cls.__name__}.')
    except pythoncom.com_error:
        raise