├── .github ├── FUNDING.yml └── workflows │ ├── autofmt.yml │ └── autotest.yml ├── MANIFEST.in ├── comtypes ├── tools │ ├── __init__.py │ └── codegenerator │ │ ├── __init__.py │ │ ├── modulenamer.py │ │ ├── packing.py │ │ └── comments.py ├── gen │ └── __init__.py ├── test │ ├── mylib.tlb │ ├── urlhist.tlb │ ├── TestComServer.tlb │ ├── TestDispServer.tlb │ ├── runtests.py │ ├── setup.py │ ├── test_pump_events.py │ ├── test_jscript.js │ ├── test_subinterface.py │ ├── README.md │ ├── test_clear_cache.py │ ├── test_ienum.py │ ├── test_w_getopt.py │ ├── test_casesensitivity.py │ ├── test_showevents.py │ ├── test_sapi.py │ ├── test_DISPPARAMS.py │ ├── test_BSTR.py │ ├── test_avmc.py │ ├── test_imfattributes.py │ ├── mylib.idl │ ├── test_GUID.py │ ├── test_urlhistory.py │ ├── TestDispServer.idl │ ├── find_memleak.py │ ├── test_wmi.py │ ├── test_word.py │ ├── test_QueryService.py │ ├── test_persist.py │ ├── TestComServer.idl │ ├── test_recordinfo.py │ ├── mytypelib.idl │ ├── test_getactiveobj.py │ ├── test_viewobject.py │ ├── test_msscript.py │ ├── test_client_dynamic.py │ ├── test_findgendir.py │ ├── test_hresult.py │ ├── test_dyndispatch.py │ ├── test_outparam.py │ ├── test_monikers.py │ ├── test_dispifc_safearrays.py │ ├── TestDispServer.py │ ├── test_dispifc_records.py │ └── test_midl_safearray_create.py ├── _post_coinit │ ├── instancemethod.py │ ├── __init__.py │ ├── bstr.py │ └── activeobj.py ├── _tlib_version_checker.py ├── client │ ├── __init__.py │ └── _activeobj.py ├── server │ ├── w_getopt.py │ ├── __init__.py │ ├── automation.py │ └── localserver.py ├── messageloop.py ├── clear_cache.py ├── logutil.py ├── patcher.py ├── stream.py ├── git.py ├── GUID.py └── _meta.py ├── source ├── AvmcIfc.suo ├── AvmcIfc.tlb ├── Debug │ ├── AvmcIfc.dll │ └── AvmcIfc_x64.dll ├── AvmcIfc.vcxproj.user ├── StdAfx.cpp ├── AvmcIfc.def ├── AvmcIfcps.def ├── build-dll.py ├── Avmc.rgs ├── resource.h ├── AvmcIfc.dsw ├── CppTestSrv │ ├── UTIL.H │ ├── REGISTRY.H │ ├── SERVER.CPP │ ├── CoComtypesDispSafearrayParamTest.h │ ├── CoComtypesDispRecordParamTest.h │ ├── UTIL.CPP │ ├── MAKEFILE │ ├── CUNKNOWN.CPP │ ├── CUNKNOWN.H │ ├── SERVER.IDL │ └── CFACTORY.H ├── StdAfx.h ├── AvmcIfc.idl ├── AvmcIfc.cpp ├── AvmcIfc.vcxproj.filters ├── Avmc.h ├── AvmcIfc.sln ├── AvmcIfc.rc └── Avmc.cpp ├── docs ├── requirements.txt └── source │ ├── threading.rst │ ├── mytypelib.idl │ ├── myserver.py │ ├── index.rst │ └── npsupport.rst ├── codecov.yml ├── .bumpversion.cfg ├── SECURITY.md ├── TODO ├── .readthedocs.yaml ├── appveyor.yml ├── .gitignore ├── LICENSE.txt ├── test_pip_install.py ├── admin └── extract-comtypes-repo └── pyproject.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | tidelift: pypi/comtypes 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include README.md 3 | -------------------------------------------------------------------------------- /comtypes/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # the comtypes.tools package 2 | -------------------------------------------------------------------------------- /comtypes/gen/__init__.py: -------------------------------------------------------------------------------- 1 | # comtypes.gen package, directory for generated files. 2 | -------------------------------------------------------------------------------- /source/AvmcIfc.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/source/AvmcIfc.suo -------------------------------------------------------------------------------- /source/AvmcIfc.tlb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/source/AvmcIfc.tlb -------------------------------------------------------------------------------- /comtypes/test/mylib.tlb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/comtypes/test/mylib.tlb -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==8.1.3 2 | sphinx-rtd-theme==3.0.2 3 | sphinx-notfound-page==1.0.2 4 | -------------------------------------------------------------------------------- /source/Debug/AvmcIfc.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/source/Debug/AvmcIfc.dll -------------------------------------------------------------------------------- /comtypes/test/urlhist.tlb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/comtypes/test/urlhist.tlb -------------------------------------------------------------------------------- /source/Debug/AvmcIfc_x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/source/Debug/AvmcIfc_x64.dll -------------------------------------------------------------------------------- /comtypes/test/TestComServer.tlb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/comtypes/test/TestComServer.tlb -------------------------------------------------------------------------------- /comtypes/test/TestDispServer.tlb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enthought/comtypes/HEAD/comtypes/test/TestDispServer.tlb -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | -------------------------------------------------------------------------------- /source/AvmcIfc.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | files = comtypes/__init__.py docs/source/index.rst docs/source/conf.py 3 | commit = True 4 | tag_name = {current_version} 5 | current_version = 1.1.4 6 | 7 | -------------------------------------------------------------------------------- /comtypes/test/runtests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import comtypes.test 4 | 5 | 6 | def main(): 7 | sys.exit(comtypes.test.run(sys.argv[1:])) 8 | 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /comtypes/test/setup.py: -------------------------------------------------------------------------------- 1 | # all the unittests can be converted to exe-files. 2 | import glob 3 | from distutils.core import setup 4 | 5 | import py2exe 6 | 7 | setup(name="test_*", console=glob.glob("test_*.py")) 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 4 | Tidelift will coordinate the fix and disclosure. 5 | -------------------------------------------------------------------------------- /source/StdAfx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // stdafx.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | -------------------------------------------------------------------------------- /comtypes/tools/codegenerator/__init__.py: -------------------------------------------------------------------------------- 1 | from comtypes.tools.codegenerator.modulenamer import ( # noqa 2 | name_friendly_module, 3 | name_wrapper_module, 4 | ) 5 | from comtypes.tools.codegenerator.codegenerator import CodeGenerator, version # noqa 6 | -------------------------------------------------------------------------------- /source/AvmcIfc.def: -------------------------------------------------------------------------------- 1 | ; AvmcIfc.def : Declares the module parameters. 2 | 3 | LIBRARY "AvmcIfc.DLL" 4 | 5 | EXPORTS 6 | DllCanUnloadNow PRIVATE 7 | DllGetClassObject PRIVATE 8 | DllRegisterServer PRIVATE 9 | DllUnregisterServer PRIVATE 10 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | comtypes TODO-list 2 | ================== 3 | 4 | Bugs 5 | ---- 6 | 7 | In test_comserver.py, TestLocalServer.test_get_typeinfo() fails with 8 | an access violation. No idea why. 9 | 10 | Features planned 11 | ---------------- 12 | 13 | Provide a .cab file for easy installation on Windows CE. 14 | -------------------------------------------------------------------------------- /source/AvmcIfcps.def: -------------------------------------------------------------------------------- 1 | 2 | LIBRARY "AvmcIfcPS" 3 | 4 | DESCRIPTION 'Proxy/Stub DLL' 5 | 6 | EXPORTS 7 | DllGetClassObject @1 PRIVATE 8 | DllCanUnloadNow @2 PRIVATE 9 | GetProxyDllInfo @3 PRIVATE 10 | DllRegisterServer @4 PRIVATE 11 | DllUnregisterServer @5 PRIVATE 12 | -------------------------------------------------------------------------------- /docs/source/threading.rst: -------------------------------------------------------------------------------- 1 | ######### 2 | Threading 3 | ######### 4 | 5 | XXX mention single threaded apartments, multi threaded apartments. 6 | ``sys.coinit_flags``, ``CoInitialize``, ``CoUninitialize`` and so on. 7 | All this is pretty advanced stuff. 8 | 9 | XXX mention threading issues, message loops 10 | 11 | 12 | .. |comtypes| replace:: ``comtypes`` 13 | -------------------------------------------------------------------------------- /source/build-dll.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from jaraco.develop import python 4 | from jaraco.develop import vstudio 5 | 6 | vs = vstudio.VisualStudio.find() 7 | env = vs.get_vcvars_env() 8 | msbuild = python.find_in_path('msbuild.exe', env['Path']) 9 | cmd = [msbuild, 'AvmcIfc.sln', '/p:Configuration=Debug', 10 | '/p:Platform=x64'] 11 | subprocess.check_call(cmd, env=env) 12 | -------------------------------------------------------------------------------- /comtypes/_post_coinit/instancemethod.py: -------------------------------------------------------------------------------- 1 | from ctypes import py_object, pythonapi 2 | 3 | pythonapi.PyInstanceMethod_New.argtypes = [py_object] 4 | pythonapi.PyInstanceMethod_New.restype = py_object 5 | PyInstanceMethod_Type = type(pythonapi.PyInstanceMethod_New(id)) 6 | 7 | 8 | def instancemethod(func, inst, cls): 9 | mth = PyInstanceMethod_Type(func) 10 | if inst is None: 11 | return mth 12 | return mth.__get__(inst) 13 | -------------------------------------------------------------------------------- /comtypes/test/test_pump_events.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import unittest 3 | 4 | from comtypes.client import PumpEvents 5 | 6 | 7 | class PumpEventsTest(unittest.TestCase): 8 | def test_pump_events_doesnt_leak_cycles(self): 9 | gc.collect() 10 | for i in range(3): 11 | PumpEvents(0.05) 12 | ncycles = gc.collect() 13 | self.assertEqual(ncycles, 0) 14 | 15 | 16 | if __name__ == "__main__": 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /comtypes/test/test_jscript.js: -------------------------------------------------------------------------------- 1 | var d = new ActiveXObject("TestDispServerLib.TestDispServer"); 2 | 3 | //WScript.Echo("d.Name"); 4 | if (d.Name != "spam, spam, spam") 5 | throw new Error(d.Name); 6 | 7 | //WScript.Echo("d.Name = 'foo'"); 8 | d.Name = "foo"; 9 | 10 | //WScript.Echo("d.Name"); 11 | if (d.Name != "foo") 12 | throw new Error(d.Name); 13 | 14 | //WScript.Echo("d.Eval('1 + 2')"); 15 | var result = d.Eval("1 + 2"); 16 | if (result != 3) 17 | throw new Error(result); 18 | -------------------------------------------------------------------------------- /comtypes/test/test_subinterface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | from ctypes import * 4 | 5 | from comtypes import GUID, IUnknown 6 | 7 | 8 | def test_main(): 9 | from test import test_support 10 | 11 | test_support.run_unittest(Test) 12 | 13 | 14 | class Test(unittest.TestCase): 15 | def test_subinterface(self): 16 | class ISub(IUnknown): 17 | pass 18 | 19 | def test_subclass(self): 20 | class X(c_void_p): 21 | pass 22 | -------------------------------------------------------------------------------- /source/Avmc.rgs: -------------------------------------------------------------------------------- 1 | HKCR 2 | { 3 | AvmcIfc.Avmc.1 = s 'Avmc Class' 4 | { 5 | CLSID = s '{41BDBDFC-A848-4523-A149-ADD3AE1E6D84}' 6 | } 7 | AvmcIfc.Avmc = s 'Avmc Class' 8 | { 9 | CLSID = s '{41BDBDFC-A848-4523-A149-ADD3AE1E6D84}' 10 | } 11 | NoRemove CLSID 12 | { 13 | ForceRemove {41BDBDFC-A848-4523-A149-ADD3AE1E6D84} = s 'Avmc Class' 14 | { 15 | ProgID = s 'AvmcIfc.Avmc.1' 16 | VersionIndependentProgID = s 'AvmcIfc.Avmc' 17 | InprocServer32 = s '%MODULE%' 18 | { 19 | val ThreadingModel = s 'both' 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/source/mytypelib.idl: -------------------------------------------------------------------------------- 1 | import "oaidl.idl"; 2 | import "ocidl.idl"; 3 | 4 | [ 5 | uuid(11C65963-BD45-4B56-A06E-1610532B8613), 6 | dual, 7 | oleautomation 8 | ] 9 | interface IMyInterface : IDispatch { 10 | HRESULT MyMethod([in] INT a, [in] INT b, [out, retval] INT *presult); 11 | } 12 | 13 | [ 14 | uuid(F0D8338A-BDC1-45D7-A14F-27D64E7BCA18) 15 | ] 16 | library MyTypeLib 17 | { 18 | importlib("stdole2.tlb"); 19 | 20 | [uuid(FBA0A6D0-B775-40D7-924A-B593B6EFA091)] 21 | coclass MyObject { 22 | [default] interface IMyInterface; 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /source/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Developer Studio generated include file. 3 | // Used by AvmcIfc.rc 4 | // 5 | #define IDS_PROJNAME 100 6 | #define IDS_AVMC_DESC 101 7 | #define IDR_Avmc 102 8 | 9 | // Next default values for new objects 10 | // 11 | #ifdef APSTUDIO_INVOKED 12 | #ifndef APSTUDIO_READONLY_SYMBOLS 13 | #define _APS_NEXT_RESOURCE_VALUE 201 14 | #define _APS_NEXT_COMMAND_VALUE 32768 15 | #define _APS_NEXT_CONTROL_VALUE 201 16 | #define _APS_NEXT_SYMED_VALUE 103 17 | #endif 18 | #endif 19 | -------------------------------------------------------------------------------- /source/AvmcIfc.dsw: -------------------------------------------------------------------------------- 1 | Microsoft Developer Studio Workspace File, Format Version 6.00 2 | # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! 3 | 4 | ############################################################################### 5 | 6 | Project: "AvmcIfc"=.\AvmcIfc.dsp - Package Owner=<4> 7 | 8 | Package=<5> 9 | {{{ 10 | }}} 11 | 12 | Package=<4> 13 | {{{ 14 | }}} 15 | 16 | ############################################################################### 17 | 18 | Global: 19 | 20 | Package=<5> 21 | {{{ 22 | }}} 23 | 24 | Package=<3> 25 | {{{ 26 | }}} 27 | 28 | ############################################################################### 29 | 30 | -------------------------------------------------------------------------------- /comtypes/test/README.md: -------------------------------------------------------------------------------- 1 | Running tests 2 | ------------- 3 | From the projects root directory, run: 4 | 5 | python -m unittest discover -s ./comtypes/test -t comtypes\test 6 | 7 | Or, from PROJECT_ROOT/comtypes/test: 8 | 9 | python -m unittest discover 10 | 11 | TODO 12 | ---- 13 | 14 | - [ ] Look at every skipped test and see if it can be fixed and made runnable as a regular 15 | unit test. 16 | - [ ] Remove the custom test runner stuff. See `comtypes/test/__init__.py` 17 | and `. /settup.py` for details. 18 | - [ ] If python 2.whatever is going to be supported we need to set up tox or something 19 | to run the tests on python 3 and python 2. 20 | -------------------------------------------------------------------------------- /source/CppTestSrv/UTIL.H: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | #ifndef __Util_h__ 10 | #define __Util_h__ 11 | 12 | // 13 | // Util.h - Shared utilities 14 | // 15 | #include 16 | 17 | namespace Util 18 | { 19 | void Trace(const char* szLabel, const char* szText, HRESULT hr) ; 20 | 21 | void ErrorMessage(HRESULT hr) ; 22 | } ; 23 | 24 | 25 | // 26 | // Overloaded insertion operator for converting from 27 | // Unicode (wchar_t) to non-Unicode. 28 | // 29 | std::ostream& operator<< ( std::ostream& os, const wchar_t* wsz ) ; 30 | 31 | #endif // __Util_h__ -------------------------------------------------------------------------------- /comtypes/_tlib_version_checker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def _check_version(actual, tlib_cached_mtime=None): 6 | from comtypes.tools.codegenerator import version as required 7 | 8 | if actual != required: 9 | raise ImportError("Wrong version") 10 | if not hasattr(sys, "frozen"): 11 | g = sys._getframe(1).f_globals 12 | tlb_path = g.get("typelib_path") 13 | try: 14 | tlib_curr_mtime = os.stat(tlb_path).st_mtime 15 | except (OSError, TypeError): 16 | return 17 | if not tlib_cached_mtime or abs(tlib_curr_mtime - tlib_cached_mtime) >= 1: 18 | raise ImportError("Typelib different than module") 19 | -------------------------------------------------------------------------------- /.github/workflows/autofmt.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: [main] 4 | 5 | jobs: 6 | formatter: 7 | name: auto-formatter 8 | runs-on: windows-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | - name: Set up Python 13 | uses: actions/setup-python@v5 14 | with: 15 | python-version: 3.9 16 | - name: Install ruff 17 | run: pip install ruff==0.6.9 18 | - name: Check format 19 | run: python -m ruff format comtypes/. --check --diff 20 | - name: Check lint 21 | run: python -m ruff check --output-format=github comtypes/. 22 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version, and other tools you might need 8 | build: 9 | os: ubuntu-24.04 10 | tools: 11 | python: "3.13" 12 | 13 | # Build documentation in the "docs/" directory with Sphinx 14 | sphinx: 15 | configuration: docs/source/conf.py 16 | 17 | # TODO: Setup recommended configurations. 18 | # Optionally, but recommended, 19 | # declare the Python requirements required to build your documentation 20 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 21 | python: 22 | install: 23 | - requirements: docs/requirements.txt 24 | -------------------------------------------------------------------------------- /comtypes/_post_coinit/__init__.py: -------------------------------------------------------------------------------- 1 | """comtypes._post_coinit 2 | 3 | This subpackage contains symbols that should be imported into `comtypes/__init__.py` 4 | after `CoInitializeEx` is defined and called, and symbols that can be defined in 5 | any order during the initialization of the `comtypes` package. 6 | 7 | These were previously defined in `comtypes/__init__.py`, but due to the codebase 8 | of the file becoming bloated, reducing the ease of changes and increasing 9 | cognitive load, they have been moved here. 10 | 11 | This subpackage is called simultaneously with the initialization of `comtypes`. 12 | So it is necessary to maintain minimal settings to keep the lightweight action 13 | when the package is initialized. 14 | """ 15 | 16 | from comtypes._post_coinit.unknwn import _shutdown # noqa 17 | -------------------------------------------------------------------------------- /docs/source/myserver.py: -------------------------------------------------------------------------------- 1 | import comtypes 2 | import comtypes.server.localserver 3 | 4 | from comtypes.client import GetModule 5 | GetModule("mytypelib.tlb") 6 | 7 | from comtypes.gen.MyTypeLib import MyObject 8 | 9 | class MyObjectImpl(MyObject): 10 | # registry entries 11 | _reg_threading_ = "Both" 12 | _reg_progid_ = "MyTypeLib.MyObject.1" 13 | _reg_novers_progid_ = "MyTypeLib.MyObject" 14 | _reg_desc_ = "Simple COM server for testing" 15 | _reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER | comtypes.CLSCTX_LOCAL_SERVER 16 | _regcls_ = comtypes.server.localserver.REGCLS_MULTIPLEUSE 17 | 18 | def MyMethod(self, a, b): 19 | return a + b 20 | 21 | if __name__ == "__main__": 22 | from comtypes.server.register import UseCommandLine 23 | UseCommandLine(MyObjectImpl) 24 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | build: off 3 | max_jobs: 3 4 | 5 | init: 6 | - git config --global core.autocrlf input 7 | 8 | shallow_clone: true 9 | 10 | environment: 11 | matrix: 12 | - py: Python37 13 | - py: Python37-x64 14 | - py: Python38 15 | - py: Python38-x64 16 | - py: Python39 17 | - py: Python39-x64 18 | - py: Python310 19 | - py: Python310-x64 20 | - py: Python311 21 | - py: Python311-x64 22 | - py: Python312 23 | - py: Python312-x64 24 | 25 | test_script: 26 | - C:\%py%\Scripts\pip.exe install --upgrade setuptools 27 | - C:\%py%\python.exe -m pip install . 28 | - C:\%py%\Scripts\pip.exe uninstall comtypes -y 29 | - C:\%py%\python.exe test_pip_install.py 30 | - C:\%py%\python.exe -m unittest discover -v -s ./comtypes/test -t comtypes\test 31 | -------------------------------------------------------------------------------- /comtypes/tools/codegenerator/modulenamer.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import comtypes 4 | from comtypes import typeinfo 5 | 6 | 7 | def name_wrapper_module(tlib: typeinfo.ITypeLib) -> str: 8 | """Determine the name of a typelib wrapper module""" 9 | libattr = tlib.GetLibAttr() 10 | guid = str(libattr.guid)[1:-1].replace("-", "_") 11 | modname = f"_{guid}_{libattr.lcid}_{libattr.wMajorVerNum}_{libattr.wMinorVerNum}" 12 | return f"comtypes.gen.{modname}" 13 | 14 | 15 | def name_friendly_module(tlib: typeinfo.ITypeLib) -> Optional[str]: 16 | """Determine the friendly-name of a typelib module. 17 | If cannot get friendly-name from typelib, returns `None`. 18 | """ 19 | try: 20 | modulename = tlib.GetDocumentation(-1)[0] 21 | except comtypes.COMError: 22 | return 23 | return f"comtypes.gen.{modulename}" 24 | -------------------------------------------------------------------------------- /comtypes/test/test_clear_cache.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test for the ``comtypes.clear_cache`` module. 3 | """ 4 | 5 | import contextlib 6 | import runpy 7 | from unittest import TestCase 8 | from unittest.mock import call, patch 9 | 10 | from comtypes.client import _find_gen_dir 11 | 12 | 13 | class ClearCacheTestCase(TestCase): 14 | # we patch sys.stdout so unittest doesn't show the print statements 15 | 16 | @patch("sys.argv", ["clear_cache.py", "-y"]) 17 | @patch("shutil.rmtree") 18 | def test_clear_cache(self, mock_rmtree): 19 | with contextlib.redirect_stdout(None): 20 | runpy.run_module("comtypes.clear_cache", {}, "__main__") 21 | 22 | # because we don't actually delete anything, _find_gen_dir() will 23 | # give the same answer every time we call it 24 | self.assertEqual( 25 | mock_rmtree.call_args_list, [call(_find_gen_dir()) for _ in range(2)] 26 | ) 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # comtypes specific 2 | comtypes/__pycache__/*.pyc 3 | comtypes/client/__pycache__/*.pyc 4 | comtypes/gen/ 5 | !comtypes/gen/__init__.py 6 | comtypes/tools/__pycache__/*.pyc 7 | comtypes.egg-info/* 8 | custom location/* 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # PyCharm project data folder 16 | .idea 17 | 18 | # Distribution / packaging 19 | .Python 20 | env/ 21 | .venv/ 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | 56 | -------------------------------------------------------------------------------- /comtypes/test/test_ienum.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import unittest as ut 3 | from ctypes import POINTER 4 | 5 | import comtypes.client 6 | from comtypes import GUID 7 | 8 | 9 | class Test_IEnum(ut.TestCase): 10 | def test_ienum(self): 11 | with contextlib.redirect_stdout(None): # supress warnings, see test_client.py 12 | comtypes.client.GetModule("msvidctl.dll") 13 | from comtypes.gen import MSVidCtlLib as vidlib 14 | 15 | CLSID_AviSplitter = GUID("{1b544c20-fd0b-11ce-8c63-00aa0044b51e}") 16 | 17 | avisplitter = comtypes.client.CreateObject( 18 | CLSID_AviSplitter, 19 | interface=vidlib.IBaseFilter, 20 | ) 21 | pinEnum = avisplitter.EnumPins() 22 | self.assertIsInstance(pinEnum, POINTER(vidlib.IEnumPins)) 23 | # make sure pinEnum is iterable and non-empty 24 | pins = list(pinEnum) 25 | self.assertGreater(len(pins), 0) 26 | 27 | 28 | if __name__ == "__main__": 29 | ut.main() 30 | -------------------------------------------------------------------------------- /source/StdAfx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, 3 | // but are changed infrequently 4 | 5 | #if !defined(AFX_STDAFX_H__E3A0B645_B548_4701_B164_973567C5C219__INCLUDED_) 6 | #define AFX_STDAFX_H__E3A0B645_B548_4701_B164_973567C5C219__INCLUDED_ 7 | 8 | #if _MSC_VER > 1000 9 | #pragma once 10 | #endif // _MSC_VER > 1000 11 | 12 | #define STRICT 13 | #ifndef _WIN32_WINNT 14 | #define _WIN32_WINNT 0x0501 15 | #endif 16 | #define _ATL_APARTMENT_THREADED 17 | 18 | #include 19 | //You may derive a class from CComModule and use it if you want to override 20 | //something, but do not change the name of _Module 21 | extern CComModule _Module; 22 | #include 23 | 24 | //{{AFX_INSERT_LOCATION}} 25 | // Microsoft Visual C++ will insert additional declarations immediately before the previous line. 26 | 27 | #endif // !defined(AFX_STDAFX_H__E3A0B645_B548_4701_B164_973567C5C219__INCLUDED) 28 | -------------------------------------------------------------------------------- /comtypes/test/test_w_getopt.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from comtypes.server.w_getopt import GetoptError, w_getopt 4 | 5 | 6 | class TestCase(unittest.TestCase): 7 | def test_1(self): 8 | args = "-embedding spam /RegServer foo /UnregSERVER blabla".split() 9 | opts, args = w_getopt(args, "regserver unregserver embedding".split()) 10 | self.assertEqual( 11 | opts, [("embedding", ""), ("regserver", ""), ("unregserver", "")] 12 | ) 13 | self.assertEqual(args, ["spam", "foo", "blabla"]) 14 | 15 | def test_2(self): 16 | args = "/TLB Hello.Tlb HELLO.idl".split() 17 | opts, args = w_getopt(args, ["tlb:"]) 18 | self.assertEqual(opts, [("tlb", "Hello.Tlb")]) 19 | self.assertEqual(args, ["HELLO.idl"]) 20 | 21 | def test_3(self): 22 | # Invalid option 23 | with self.assertRaises(GetoptError): 24 | w_getopt("/TLIB hello.tlb hello.idl".split(), ["tlb:"]) 25 | 26 | def test_4(self): 27 | # Missing argument 28 | with self.assertRaises(GetoptError): 29 | w_getopt("/TLB".split(), ["tlb:"]) 30 | -------------------------------------------------------------------------------- /source/CppTestSrv/REGISTRY.H: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | #ifndef __Registry_H__ 10 | #define __Registry_H__ 11 | // 12 | // Registry.h 13 | // - Helper functions registering and unregistering a component. 14 | // 15 | 16 | // This function will register a component in the Registry. 17 | // The component calls this function from its DllRegisterServer function. 18 | HRESULT RegisterServer(HMODULE hModule, 19 | const CLSID& clsid, 20 | LPCWSTR szFriendlyName, 21 | LPCWSTR szVerIndProgID, 22 | LPCWSTR szProgID, 23 | const GUID& libid) ; 24 | 25 | // This function will unregister a component. Components 26 | // call this function from their DllUnregisterServer function. 27 | HRESULT UnregisterServer(const CLSID& clsid, 28 | LPCWSTR szVerIndProgID, 29 | LPCWSTR szProgID, 30 | const GUID* libid) ; 31 | 32 | #endif -------------------------------------------------------------------------------- /comtypes/test/test_casesensitivity.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import unittest 3 | 4 | from comtypes.client import GetModule 5 | 6 | with contextlib.redirect_stdout(None): # supress warnings 7 | GetModule("msvidctl.dll") 8 | from comtypes.gen import MSVidCtlLib as msvidctl 9 | 10 | 11 | class TestCase(unittest.TestCase): 12 | def test(self): 13 | # IDispatch(IUnknown) 14 | # IMSVidDevice(IDispatch) 15 | # IMSVidInputDevice(IMSVidDevice) 16 | # IMSVidPlayback(IMSVidOutputDevice) 17 | 18 | self.assertTrue(issubclass(msvidctl.IMSVidPlayback, msvidctl.IMSVidInputDevice)) 19 | self.assertTrue(issubclass(msvidctl.IMSVidInputDevice, msvidctl.IMSVidDevice)) 20 | 21 | # names in the base class __map_case__ must also appear in the 22 | # subclass. 23 | for name in msvidctl.IMSVidDevice.__map_case__: 24 | self.assertIn(name, msvidctl.IMSVidInputDevice.__map_case__) 25 | self.assertIn(name, msvidctl.IMSVidPlayback.__map_case__) 26 | 27 | for name in msvidctl.IMSVidInputDevice.__map_case__: 28 | self.assertIn(name, msvidctl.IMSVidPlayback.__map_case__) 29 | 30 | 31 | if __name__ == "__main__": 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /comtypes/test/test_showevents.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import unittest 3 | from typing import Optional 4 | 5 | 6 | def load_tests( 7 | loader: unittest.TestLoader, tests: unittest.TestSuite, pattern: Optional[str] 8 | ) -> unittest.TestSuite: 9 | import comtypes.test.test_showevents 10 | 11 | tests.addTests(doctest.DocTestSuite(comtypes.test.test_showevents)) 12 | return tests 13 | 14 | 15 | class ShowEventsExamples: 16 | def StdFont_ShowEvents(self): 17 | """ 18 | >>> from comtypes.client import CreateObject, GetModule, ShowEvents, PumpEvents 19 | >>> _ = GetModule('scrrun.dll') # generating `Scripting` also generates `stdole` 20 | >>> from comtypes.gen import stdole 21 | >>> font = CreateObject(stdole.StdFont) 22 | >>> conn = ShowEvents(font) 23 | # event found: FontEvents_FontChanged 24 | >>> font.Name = 'Arial' 25 | Event FontEvents_FontChanged(None, 'Name') 26 | >>> font.Italic = True 27 | Event FontEvents_FontChanged(None, 'Italic') 28 | >>> PumpEvents(0.01) # just calling. assertion does in `test_pump_events.py` 29 | >>> conn.disconnect() 30 | """ 31 | pass 32 | 33 | 34 | if __name__ == "__main__": 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /comtypes/test/test_sapi.py: -------------------------------------------------------------------------------- 1 | # http://www.microsoft.com/technet/scriptcenter/funzone/games/sapi.mspx 2 | # ../gen/_C866CA3A_32F7_11D2_9602_00C04F8EE628_0_5_0 3 | # http://thread.gmane.org/gmane.comp.python.ctypes.user/1485 4 | 5 | import os 6 | import tempfile 7 | import unittest 8 | 9 | from comtypes.client import CreateObject 10 | 11 | 12 | class Test(unittest.TestCase): 13 | def test(self, dynamic=False): 14 | engine = CreateObject("SAPI.SpVoice", dynamic=dynamic) 15 | stream = CreateObject("SAPI.SpFileStream", dynamic=dynamic) 16 | from comtypes.gen import SpeechLib 17 | 18 | fd, fname = tempfile.mkstemp(suffix=".wav") 19 | os.close(fd) 20 | 21 | stream.Open(fname, SpeechLib.SSFMCreateForWrite) 22 | 23 | # engine.AudioStream is a propputref property 24 | engine.AudioOutputStream = stream 25 | self.assertEqual(engine.AudioOutputStream, stream) 26 | engine.speak("Hello, World", 0) 27 | stream.Close() 28 | filesize = os.stat(fname).st_size 29 | self.assertTrue(filesize > 100, "filesize only %d bytes" % filesize) 30 | os.unlink(fname) 31 | 32 | def test_dyndisp(self): 33 | return self.test(dynamic=True) 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /comtypes/test/test_DISPPARAMS.py: -------------------------------------------------------------------------------- 1 | import unittest as ut 2 | 3 | 4 | class TestCase(ut.TestCase): 5 | def test(self): 6 | from comtypes.automation import DISPPARAMS, VARIANT 7 | 8 | dp = DISPPARAMS() 9 | dp.rgvarg = (VARIANT * 3)() 10 | 11 | for i in range(3): 12 | self.assertEqual(dp.rgvarg[i].value, None) 13 | 14 | dp.rgvarg[0].value = 42 15 | dp.rgvarg[1].value = "spam" 16 | dp.rgvarg[2].value = "foo" 17 | 18 | # damn, there's still this old bug! 19 | 20 | self.assertEqual(dp.rgvarg[0].value, 42) 21 | # these fail: 22 | # self.failUnlessEqual(dp.rgvarg[1].value, "spam") 23 | # self.failUnlessEqual(dp.rgvarg[2].value, "foo") 24 | 25 | def X_test_2(self): 26 | # basically the same test as above 27 | from comtypes.automation import DISPPARAMS, VARIANT 28 | 29 | args = [42, None, "foo"] 30 | 31 | dp = DISPPARAMS() 32 | dp.rgvarg = (VARIANT * 3)(*list(map(VARIANT, args[::-1]))) 33 | 34 | import gc 35 | 36 | gc.collect() 37 | 38 | self.assertEqual(dp.rgvarg[0].value, 42) 39 | self.assertEqual(dp.rgvarg[1].value, "spam") 40 | self.assertEqual(dp.rgvarg[2].value, "foo") 41 | 42 | 43 | if __name__ == "__main__": 44 | ut.main() 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is OSI Certified Open Source Software. 2 | OSI Certified is a certification mark of the Open Source Initiative. 3 | 4 | Copyright (c) 2006-2013, Thomas Heller. 5 | Copyright (c) 2014, Comtypes Developers. 6 | All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /comtypes/test/test_BSTR.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import WinDLL 3 | from ctypes.wintypes import UINT 4 | 5 | from comtypes import BSTR 6 | from comtypes.test.find_memleak import find_memleak 7 | 8 | 9 | class Test(unittest.TestCase): 10 | def check_leaks(self, func, limit=0): 11 | bytes = find_memleak(func) 12 | self.assertFalse(bytes > limit, f"Leaks {bytes} bytes") 13 | 14 | def test_creation(self): 15 | def doit(): 16 | BSTR("abcdef" * 100) 17 | 18 | # It seems this test is unreliable. Sometimes it leaks 4096 19 | # bytes, sometimes not. Try to workaround that... 20 | self.check_leaks(doit, limit=4096) 21 | 22 | def test_from_param(self): 23 | def doit(): 24 | BSTR.from_param("abcdef") 25 | 26 | self.check_leaks(doit) 27 | 28 | def test_inargs(self): 29 | oleaut32 = WinDLL("oleaut32") 30 | 31 | SysStringLen = oleaut32.SysStringLen 32 | SysStringLen.argtypes = [BSTR] 33 | SysStringLen.restype = UINT 34 | 35 | self.assertEqual(SysStringLen("abc xyz"), 7) 36 | 37 | def doit(): 38 | SysStringLen("abc xyz") 39 | SysStringLen("abc xyz") 40 | SysStringLen(BSTR("abc def")) 41 | 42 | self.check_leaks(doit) 43 | 44 | 45 | if __name__ == "__main__": 46 | unittest.main() 47 | -------------------------------------------------------------------------------- /comtypes/client/__init__.py: -------------------------------------------------------------------------------- 1 | """comtypes.client - High level client level COM support package.""" 2 | 3 | import ctypes 4 | import logging 5 | 6 | from comtypes import RevokeActiveObject, automation # noqa 7 | from comtypes.client import dynamic, lazybind # noqa 8 | from comtypes.client._activeobj import RegisterActiveObject # noqa 9 | from comtypes.client._code_cache import _find_gen_dir 10 | from comtypes.client._constants import Constants # noqa 11 | from comtypes.client._events import GetEvents, PumpEvents, ShowEvents 12 | from comtypes.client._generate import GetModule 13 | from comtypes.client._managing import GetBestInterface, _manage, wrap_outparam # noqa 14 | from comtypes.hresult import * # noqa 15 | 16 | gen_dir = _find_gen_dir() 17 | import comtypes.gen # noqa 18 | 19 | ### for testing 20 | ##gen_dir = None 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | # backwards compatibility: 26 | wrap = GetBestInterface 27 | 28 | # Should we do this for POINTER(IUnknown) also? 29 | ctypes.POINTER(automation.IDispatch).__ctypes_from_outparam__ = wrap_outparam # type: ignore 30 | 31 | from comtypes.client._activeobj import GetActiveObject 32 | from comtypes.client._create import ( 33 | CoGetObject, 34 | CreateObject, 35 | GetClassObject, 36 | ) 37 | 38 | # fmt: off 39 | __all__ = [ 40 | "CreateObject", "GetActiveObject", "CoGetObject", "GetEvents", 41 | "ShowEvents", "PumpEvents", "GetModule", "GetClassObject", 42 | ] 43 | # fmt: on 44 | -------------------------------------------------------------------------------- /comtypes/_post_coinit/bstr.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from ctypes import WinDLL, _SimpleCData 3 | from typing import TYPE_CHECKING, Any 4 | 5 | if TYPE_CHECKING: 6 | from comtypes import hints # type: ignore 7 | 8 | 9 | _oleaut32 = WinDLL("oleaut32") 10 | 11 | _SysFreeString = _oleaut32.SysFreeString 12 | 13 | 14 | class BSTR(_SimpleCData): 15 | """The windows BSTR data type""" 16 | 17 | _type_ = "X" 18 | _needsfree = False 19 | 20 | def __repr__(self) -> str: 21 | return f"{self.__class__.__name__}({self.value!r})" 22 | 23 | def __ctypes_from_outparam__(self) -> Any: 24 | self._needsfree = True 25 | return self.value 26 | 27 | def __del__(self, _free: Callable[["BSTR"], Any] = _SysFreeString) -> None: 28 | # Free the string if self owns the memory 29 | # or if instructed by __ctypes_from_outparam__. 30 | if self._b_base_ is None or self._needsfree: 31 | _free(self) 32 | 33 | @classmethod 34 | def from_param(cls, value: Any) -> "hints.Self": 35 | """Convert into a foreign function call parameter.""" 36 | if isinstance(value, cls): 37 | return value 38 | # Although the builtin SimpleCData.from_param call does the 39 | # right thing, it doesn't ensure that SysFreeString is called 40 | # on destruction. 41 | return cls(value) 42 | 43 | 44 | _SysFreeString.argtypes = [BSTR] 45 | _SysFreeString.restype = None 46 | -------------------------------------------------------------------------------- /comtypes/test/test_avmc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from comtypes.client import CreateObject 4 | from comtypes.test.find_memleak import find_memleak 5 | 6 | 7 | @unittest.skip( 8 | "This test does not work. Apparently it's supposed to work with the 'avmc' stuff " 9 | "in comtypes/source, but it doesn't. It's not clear to me why." 10 | ) 11 | class Test(unittest.TestCase): 12 | """Test COM records""" 13 | 14 | def test(self): 15 | # The ATL COM dll 16 | avmc = CreateObject("AvmcIfc.Avmc.1") 17 | 18 | # This returns an array (a list) of DeviceInfo records. 19 | devs = avmc.FindAllAvmc() 20 | 21 | self.assertEqual(devs[0].Flags, 12) 22 | self.assertEqual(devs[0].ID, 13) 23 | self.assertEqual(devs[0].LocId, 14) 24 | self.assertEqual(devs[0].Description, "Avmc") 25 | self.assertEqual(devs[0].SerialNumber, "1234") 26 | 27 | self.assertEqual(devs[1].Flags, 22) 28 | self.assertEqual(devs[1].ID, 23) 29 | self.assertEqual(devs[1].LocId, 24) 30 | self.assertEqual(devs[1].Description, "Avmc2") 31 | self.assertEqual(devs[1].SerialNumber, "5678") 32 | 33 | # # Leaks... where? 34 | # def doit(): 35 | # avmc.FindAllAvmc() 36 | # self.check_leaks(doit) 37 | 38 | def check_leaks(self, func, limit=0): 39 | bytes = find_memleak(func) 40 | self.assertFalse(bytes > limit, f"Leaks {bytes} bytes") 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /comtypes/test/test_imfattributes.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import unittest as ut 3 | from ctypes import HRESULT, POINTER, WinDLL, c_uint32, pointer 4 | 5 | import comtypes.client 6 | from comtypes import GUID 7 | 8 | 9 | class Test_IMFAttributes(ut.TestCase): 10 | def test_imfattributes(self): 11 | with contextlib.redirect_stdout(None): # supress warnings, see test_client.py 12 | comtypes.client.GetModule("msvidctl.dll") 13 | from comtypes.gen import MSVidCtlLib 14 | 15 | _mfplat = WinDLL("mfplat") 16 | 17 | UINT32 = c_uint32 18 | _MFCreateAttributes = _mfplat.MFCreateAttributes 19 | _MFCreateAttributes.argtypes = [ 20 | POINTER(POINTER(MSVidCtlLib.IMFAttributes)), 21 | UINT32, 22 | ] 23 | _MFCreateAttributes.restype = HRESULT 24 | 25 | imf_attrs = POINTER(MSVidCtlLib.IMFAttributes)() 26 | hres = _MFCreateAttributes(pointer(imf_attrs), 2) 27 | self.assertEqual(hres, 0) 28 | 29 | MF_TRANSCODE_ADJUST_PROFILE = GUID("{9c37c21b-060f-487c-a690-80d7f50d1c72}") 30 | set_int_value = 1 31 | # IMFAttributes.SetUINT32() is an example of a function that has a parameter 32 | # without an `in` or `out` direction; see also test_inout_args.py 33 | imf_attrs.SetUINT32(MF_TRANSCODE_ADJUST_PROFILE, set_int_value) 34 | get_int_value = imf_attrs.GetUINT32(MF_TRANSCODE_ADJUST_PROFILE) 35 | self.assertEqual(set_int_value, get_int_value) 36 | 37 | 38 | if __name__ == "__main__": 39 | ut.main() 40 | -------------------------------------------------------------------------------- /comtypes/server/w_getopt.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | 3 | 4 | class GetoptError(Exception): 5 | pass 6 | 7 | 8 | def w_getopt( 9 | args: Sequence[str], options: str 10 | ) -> tuple[Sequence[tuple[str, str]], Sequence[str]]: 11 | """A getopt for Windows. 12 | 13 | Options may start with either '-' or '/', the option names may 14 | have more than one letter (/tlb or -RegServer), and option names 15 | are case insensitive. 16 | 17 | Returns two elements, just as getopt.getopt. The first is a list 18 | of (option, value) pairs in the same way getopt.getopt does, but 19 | there is no '-' or '/' prefix to the option name, and the option 20 | name is always lower case. The second is the list of arguments 21 | which do not belong to an option. 22 | 23 | Different from getopt.getopt, a single argument not belonging to an option 24 | does not terminate parsing. 25 | """ 26 | opts = [] 27 | arguments = [] 28 | while args: 29 | if args[0][:1] in "/-": 30 | arg = args[0][1:] # strip the '-' or '/' 31 | arg = arg.lower() 32 | 33 | if arg + ":" in options: 34 | try: 35 | opts.append((arg, args[1])) 36 | except IndexError: 37 | raise GetoptError(f"option '{args[0]}' requires an argument") 38 | args = args[1:] 39 | elif arg in options: 40 | opts.append((arg, "")) 41 | else: 42 | raise GetoptError(f"invalid option '{args[0]}'") 43 | args = args[1:] 44 | else: 45 | arguments.append(args[0]) 46 | args = args[1:] 47 | 48 | return opts, arguments 49 | -------------------------------------------------------------------------------- /comtypes/test/mylib.idl: -------------------------------------------------------------------------------- 1 | import "oaidl.idl"; 2 | import "ocidl.idl"; 3 | [uuid(f4f74946-4546-44bd-a073-9ea6f9fe78cb)] library TestLib { 4 | [object, 5 | oleautomation, 6 | dual, 7 | uuid(ed978f5f-cc45-4fcc-a7a6-751ffa8dfedd)] 8 | interface IMyInterface : IDispatch { 9 | [id(100), propget] HRESULT Name([out, retval] BSTR *pname); 10 | [id(100), propput] HRESULT Name([in] BSTR name); 11 | [id(101)] HRESULT MixedInOut([in] int a, [out] int *b, [in] int c, [out] int *d); 12 | [id(102)] HRESULT MultiInOutArgs([in, out] int *pa, [in, out] int *pb); 13 | HRESULT MultiInOutArgs2([in, out] int *pa, [out] int *pb); 14 | HRESULT MultiInOutArgs3([out] int *pa, [out] int *pb); 15 | HRESULT MultiInOutArgs4([out] int *pa, [in, out] int *pb); 16 | HRESULT GetStackTrace([in] ULONG FrameOffset, 17 | [in, out] INT *Frames, 18 | [in] ULONG FramesSize, 19 | [out, optional] ULONG *FramesFilled); 20 | HRESULT dummy([in] SAFEARRAY(VARIANT *) foo); 21 | HRESULT DoSomething(); 22 | HRESULT DoSomethingElse(); 23 | } 24 | 25 | [object, 26 | oleautomation, 27 | dual, 28 | uuid(f7c48a90-64ea-4bb8-abf1-b3a3aa996848)] 29 | interface IMyEventInterface : IDispatch { 30 | [id(103)] HRESULT OnSomething(); 31 | [id(104)] HRESULT OnSomethingElse([out, retval] int *px); 32 | } 33 | 34 | 35 | [uuid(fa9de8f4-20de-45fc-b079-648572428817)] 36 | coclass MyServer { 37 | [default] interface IMyInterface; 38 | [default, source] interface IMyEventInterface; 39 | }; 40 | } -------------------------------------------------------------------------------- /source/AvmcIfc.idl: -------------------------------------------------------------------------------- 1 | // AvmcIfc.idl : IDL source for AvmcIfc.dll 2 | // 3 | 4 | // This file will be processed by the MIDL tool to 5 | // produce the type library (AvmcIfc.tlb) and marshalling code. 6 | 7 | import "oaidl.idl"; 8 | import "ocidl.idl"; 9 | 10 | // 1129c4e1-f46a-4b61-a464-406d7df3e516 11 | 12 | [ 13 | uuid(6C7A25CB-7938-4BE0-A285-12C616717FDD), 14 | version(1.0), 15 | helpstring("FTDI Device info node") 16 | ] 17 | typedef struct DeviceInfo { 18 | [helpstring("Special case variant")] VARIANT Special; 19 | [helpstring("Name of the variable")] BSTR Name; 20 | [helpstring("Value of the variable")] long Value; 21 | [helpstring("Flags")] long Flags; //ULONG 22 | [helpstring("Device Type")] long Type; //ULONG 23 | [helpstring("Device Id")] long ID; //ULONG 24 | [helpstring("Local Id")] long LocId; //DWORD 25 | [helpstring("Device's Serial Number")] BSTR SerialNumber; 26 | [helpstring("Device's Description")] BSTR Description; 27 | [helpstring("Device current handle")] long ftHandle; //ULONG 28 | } DeviceInfo; 29 | 30 | [ 31 | object, 32 | uuid(6C7A25CC-7938-4BE0-A285-12C616717FDD), 33 | dual, 34 | helpstring("IAvmc Interface"), 35 | pointer_default(unique) 36 | ] 37 | interface IAvmc : IDispatch 38 | { 39 | [id(1), helpstring("method FindAllAvmc")] HRESULT FindAllAvmc([out] SAFEARRAY(DeviceInfo) *avmcList); 40 | }; 41 | 42 | [ 43 | uuid(70577167-ED71-4977-B719-2C40C6DD8E1D), 44 | version(1.0), 45 | helpstring("AvmcIfc 1.0 Type Library") 46 | ] 47 | library AVMCIFCLib 48 | { 49 | importlib("stdole32.tlb"); 50 | importlib("stdole2.tlb"); 51 | 52 | 53 | [ 54 | uuid(41BDBDFC-A848-4523-A149-ADD3AE1E6D84), 55 | helpstring("Avmc Class") 56 | ] 57 | coclass Avmc 58 | { 59 | [default] interface IAvmc; 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /comtypes/messageloop.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from ctypes import WinDLL, WinError, byref 3 | from ctypes.wintypes import MSG 4 | from typing import TYPE_CHECKING, SupportsIndex 5 | 6 | if TYPE_CHECKING: 7 | from collections.abc import Callable, Iterable 8 | from ctypes import _CArgObject 9 | from typing import Any 10 | 11 | _FilterCallable = Callable[["_CArgObject"], Iterable[Any]] # type: ignore 12 | 13 | _user32 = WinDLL("user32") 14 | 15 | GetMessage = _user32.GetMessageA 16 | GetMessage.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint] 17 | TranslateMessage = _user32.TranslateMessage 18 | DispatchMessage = _user32.DispatchMessageA 19 | 20 | 21 | class _MessageLoop: 22 | def __init__(self) -> None: 23 | self._filters: list["_FilterCallable"] = [] 24 | 25 | def insert_filter(self, obj: "_FilterCallable", index: SupportsIndex = -1) -> None: 26 | self._filters.insert(index, obj) 27 | 28 | def remove_filter(self, obj: "_FilterCallable") -> None: 29 | self._filters.remove(obj) 30 | 31 | def run(self) -> None: 32 | msg = MSG() 33 | lpmsg = byref(msg) 34 | while 1: 35 | ret = GetMessage(lpmsg, 0, 0, 0) 36 | if ret == -1: 37 | raise WinError() 38 | elif ret == 0: 39 | return # got WM_QUIT 40 | if not self.filter_message(lpmsg): 41 | TranslateMessage(lpmsg) 42 | DispatchMessage(lpmsg) 43 | 44 | def filter_message(self, lpmsg: "_CArgObject") -> bool: 45 | return any(list(filter(lpmsg)) for filter in self._filters) 46 | 47 | 48 | _messageloop = _MessageLoop() 49 | 50 | run = _messageloop.run 51 | insert_filter = _messageloop.insert_filter 52 | remove_filter = _messageloop.remove_filter 53 | 54 | __all__ = ["run", "insert_filter", "remove_filter"] 55 | -------------------------------------------------------------------------------- /comtypes/test/test_GUID.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from comtypes import GUID 4 | 5 | 6 | class Test(unittest.TestCase): 7 | def test_GUID_null(self): 8 | self.assertEqual(GUID(), GUID()) 9 | self.assertEqual( 10 | str(GUID()), 11 | "{00000000-0000-0000-0000-000000000000}", 12 | ) 13 | 14 | def test_dunder_eq(self): 15 | self.assertEqual( 16 | GUID("{00000000-0000-0000-C000-000000000046}"), 17 | GUID("{00000000-0000-0000-C000-000000000046}"), 18 | ) 19 | 20 | def test_duner_str(self): 21 | self.assertEqual( 22 | str(GUID("{0002DF01-0000-0000-C000-000000000046}")), 23 | "{0002DF01-0000-0000-C000-000000000046}", 24 | ) 25 | 26 | def test_dunder_repr(self): 27 | self.assertEqual( 28 | repr(GUID("{0002DF01-0000-0000-C000-000000000046}")), 29 | 'GUID("{0002DF01-0000-0000-C000-000000000046}")', 30 | ) 31 | 32 | def test_invalid_constructor_arg(self): 33 | with self.assertRaises(WindowsError): 34 | GUID("abc") 35 | 36 | def test_from_progid(self): 37 | self.assertEqual( 38 | GUID.from_progid("Scripting.FileSystemObject"), 39 | GUID("{0D43FE01-F093-11CF-8940-00A0C9054228}"), 40 | ) 41 | with self.assertRaises(WindowsError): 42 | GUID.from_progid("abc") 43 | 44 | def test_as_progid(self): 45 | self.assertEqual( 46 | GUID("{0D43FE01-F093-11CF-8940-00A0C9054228}").as_progid(), 47 | "Scripting.FileSystemObject", 48 | ) 49 | with self.assertRaises(WindowsError): 50 | GUID("{00000000-0000-0000-C000-000000000046}").as_progid() 51 | 52 | def test_create_new(self): 53 | self.assertNotEqual(GUID.create_new(), GUID.create_new()) 54 | 55 | 56 | if __name__ == "__main__": 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /comtypes/clear_cache.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import contextlib 3 | import os 4 | import sys 5 | from shutil import rmtree # TESTS ASSUME USE OF RMTREE 6 | 7 | 8 | # if supporting Py>=3.11 only, this might be `contextlib.chdir`. 9 | # https://docs.python.org/3/library/contextlib.html#contextlib.chdir 10 | @contextlib.contextmanager 11 | def chdir(path): 12 | """Context manager to change the current working directory.""" 13 | work_dir = os.getcwd() 14 | os.chdir(path) 15 | yield 16 | os.chdir(work_dir) 17 | 18 | 19 | def main(): 20 | parser = argparse.ArgumentParser( 21 | prog="py -m comtypes.clear_cache", description="Removes comtypes cache folders." 22 | ) 23 | parser.add_argument( 24 | "-y", help="Pre-approve deleting all folders", action="store_true" 25 | ) 26 | args = parser.parse_args() 27 | 28 | if not args.y: 29 | confirm = input("Remove comtypes cache directories? (y/n): ") 30 | if confirm.lower() != "y": 31 | print("Cache directories NOT removed") 32 | return 33 | 34 | # change cwd to avoid import from local folder during installation process 35 | with chdir(os.path.dirname(sys.executable)): 36 | try: 37 | import comtypes.client 38 | except ImportError: 39 | print("Could not import comtypes", file=sys.stderr) 40 | sys.exit(1) 41 | 42 | # there are two possible locations for the cache folder (in the comtypes 43 | # folder in site-packages if that is writable, otherwise in APPDATA) 44 | # fortunately, by deleting the first location returned by _find_gen_dir() 45 | # we make it un-writable, so calling it again gives us the APPDATA location 46 | for _ in range(2): 47 | dir_path = comtypes.client._find_gen_dir() 48 | rmtree(dir_path) 49 | print(f'Removed directory "{dir_path}"') 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /test_pip_install.py: -------------------------------------------------------------------------------- 1 | """This test covers 'pip install' issue #155""" 2 | import os 3 | import sys 4 | import shutil 5 | import subprocess 6 | import unittest 7 | 8 | def read_version(): 9 | # Determine the version number by reading it from the file 10 | # 'comtypes\__init__.py'. We cannot import this file (with py3, 11 | # at least) because it is in py2.x syntax. 12 | with open("comtypes/__init__.py") as ofi: 13 | for line in ofi: 14 | if line.startswith("__version__ = "): 15 | var, value = line.split('=') 16 | return value.strip().strip('"').strip("'") 17 | raise NotImplementedError("__version__ is not found in __init__.py") 18 | 19 | 20 | class TestPipInstall(unittest.TestCase): 21 | 22 | def setUp(self): 23 | """prepare the same package that is usually uploaded to PyPI""" 24 | subprocess.check_call([sys.executable, '-m', 'build', '--sdist']) 25 | 26 | filename_for_upload = 'comtypes-%s.tar.gz' % read_version() 27 | self.target_package = os.path.join(os.getcwd(), 'dist', filename_for_upload) 28 | self.pip_exe = os.path.join(os.path.dirname(sys.executable), 'Scripts', 'pip.exe') 29 | 30 | def test_pip_install(self): 31 | """Test that "pip install comtypes-x.y.z.tar.gz" works""" 32 | subprocess.check_call([self.pip_exe, 'install', self.target_package]) 33 | 34 | def test_no_cache_dir_custom_location(self): 35 | """Test that 'pip install comtypes-x.y.z.tar.gz --no-cache-dir --target="...\custom location"' works""" 36 | custom_dir = os.path.join(os.getcwd(), 'custom location') 37 | if os.path.exists(custom_dir): 38 | shutil.rmtree(custom_dir) 39 | os.makedirs(custom_dir) 40 | 41 | # this test catches issue #158 42 | subprocess.check_call('{0} install {1} --no-cache-dir --target="{2}"' \ 43 | ''.format(self.pip_exe, self.target_package, custom_dir)) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /source/CppTestSrv/SERVER.CPP: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | #include "CFactory.h" 10 | #include "Iface.h" 11 | #include "CoComtypesDispRecordParamTest.h" 12 | #include "CoComtypesDispSafearrayParamTest.h" 13 | 14 | 15 | /////////////////////////////////////////////////////////// 16 | // 17 | // Server.cpp 18 | // 19 | // This file contains the component server code. 20 | // The FactoryDataArray contains the components that 21 | // can be served. 22 | // 23 | 24 | // Each component derived from CUnknown defines a static function 25 | // for creating the component with the following prototype. 26 | // HRESULT CreateInstance(IUnknown* pUnknownOuter, 27 | // CUnknown** ppNewComponent) ; 28 | // This function is used to create the component. 29 | // 30 | 31 | // 32 | // The following array contains the data used by CFactory 33 | // to create components. Each element in the array contains 34 | // the CLSID, the pointer to the creation function, and the name 35 | // of the component to place in the Registry. 36 | // 37 | CFactoryData g_FactoryDataArray[] = 38 | { 39 | {&CLSID_CoComtypesDispRecordParamTest, CA::CreateInstance, 40 | L"Comtypes component for dispinterface record parameter tests", // Friendly Name 41 | L"Comtypes.DispRecordParamTest.1", // ProgID 42 | L"Comtypes.DispRecordParamTest", // Version-independent ProgID 43 | &LIBID_ComtypesCppTestSrvLib, // Type Library ID 44 | NULL, 0}, 45 | {&CLSID_CoComtypesDispSafearrayParamTest, CB::CreateInstance, 46 | L"Comtypes component for dispinterface Safearray parameter tests", // Friendly Name 47 | L"Comtypes.DispSafearrayParamTest.1", // ProgID 48 | L"Comtypes.DispSafearrayParamTest", // Version-independent ProgID 49 | &LIBID_ComtypesCppTestSrvLib, // Type Library ID 50 | NULL, 0} 51 | } ; 52 | int g_cFactoryDataEntries 53 | = sizeof(g_FactoryDataArray) / sizeof(CFactoryData) ; 54 | -------------------------------------------------------------------------------- /comtypes/test/test_urlhistory.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from copy import copy 4 | from ctypes import * 5 | 6 | from comtypes.client import CreateObject, GetModule 7 | from comtypes.GUID import _CoTaskMemFree 8 | from comtypes.patcher import Patch 9 | 10 | # ./urlhist.tlb was downloaded somewhere from the internet (?) 11 | 12 | GetModule(os.path.join(os.path.dirname(__file__), "urlhist.tlb")) 13 | from comtypes.gen import urlhistLib 14 | 15 | 16 | # The pwcsTitle and pwcsUrl fields of the _STATURL structure must be 17 | # freed by the caller. The only way to do this without patching the 18 | # generated code directly is to monkey-patch the 19 | # _STATURL.__ctypes_from_outparam__ method like this. 20 | @Patch(urlhistLib._STATURL) 21 | class _: 22 | def __ctypes_from_outparam__(self): 23 | from comtypes.util import cast_field 24 | 25 | result = type(self)() 26 | for n, _ in self._fields_: 27 | setattr(result, n, getattr(self, n)) 28 | url, title = self.pwcsUrl, self.pwcsTitle 29 | _CoTaskMemFree(cast_field(self, "pwcsUrl", c_void_p)) 30 | _CoTaskMemFree(cast_field(self, "pwcsTitle", c_void_p)) 31 | return result 32 | 33 | 34 | from comtypes.test.find_memleak import find_memleak 35 | 36 | 37 | class Test(unittest.TestCase): 38 | def check_leaks(self, func): 39 | bytes = find_memleak(func, (5, 10)) 40 | self.assertFalse(bytes, "Leaks %d bytes" % bytes) 41 | 42 | @unittest.skip( 43 | "This fails with: `TypeError: iter() returned non-iterator of type 'POINTER(IEnumSTATURL)'`" 44 | ) 45 | def test_creation(self): 46 | hist = CreateObject(urlhistLib.UrlHistory) 47 | for x in hist.EnumURLS(): 48 | x.pwcsUrl, x.pwcsTitle 49 | # print (x.pwcsUrl, x.pwcsTitle) 50 | # print x 51 | 52 | def doit(): 53 | for x in hist.EnumURLs(): 54 | pass 55 | 56 | doit() 57 | self.check_leaks(doit) 58 | 59 | 60 | if __name__ == "__main__": 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /comtypes/test/TestDispServer.idl: -------------------------------------------------------------------------------- 1 | /* 2 | 2882fa40-2d69-4880-8073-e81fa29e1785 3 | 7ae4b0e3-5d92-4ab1-b5d0-2a95c1c3ba73 4 | f557bf87-3e3f-4c73-9bc1-7d633d83714b 5 | */ 6 | 7 | import "oaidl.idl"; 8 | import "ocidl.idl"; 9 | 10 | [ 11 | uuid(3b3b2a10-7fef-4bcc-90fe-43a221162b1b), 12 | helpstring("A custom event interface") 13 | ] 14 | dispinterface DTestDispServerEvents { 15 | properties: 16 | 17 | methods: 18 | [id(10)] 19 | void EvalStarted([in] BSTR what); 20 | 21 | [id(11)] 22 | void EvalCompleted([in] BSTR what, [in] VARIANT result); 23 | }; 24 | 25 | [ 26 | uuid(d44d11ba-aa1f-4e93-8f5a-8fa0a4715241), 27 | helpstring("DTestDispServer interface") 28 | ] 29 | dispinterface DTestDispServer { 30 | properties: 31 | [readonly, id(10), helpstring("the id of the server")] 32 | UINT id; 33 | 34 | [id(11), helpstring("the name of the server")] 35 | BSTR name; 36 | 37 | methods: 38 | 39 | [id(12), helpstring("a method that receives an BSTR [in] parameter")] 40 | void SetName([in] BSTR name); 41 | 42 | [id(13), helpstring("evaluate an expression and return the result")] 43 | VARIANT eval([in] BSTR what); 44 | 45 | [id(14), helpstring("evaluate an expression and return the result")] 46 | VARIANT eval2([in] BSTR what); 47 | 48 | [id(16), helpstring("execute a statement")] 49 | void Exec([in] BSTR what); 50 | 51 | [id(17), helpstring("execute a statement")] 52 | void Exec2([in] BSTR what); 53 | 54 | /* Some methods that use defaultvalues */ 55 | [id(100)] 56 | void do_cy([in, defaultvalue(32.78)] CURRENCY *value); 57 | 58 | [id(101)] 59 | void do_date([in, defaultvalue(32)] DATE *value); 60 | }; 61 | 62 | [ 63 | uuid(6baa1c79-4ba0-47f2-9ad7-d2ffb1c0f3e3), 64 | version(1.0), 65 | helpstring("TestDispServer 1.0 Type library") 66 | ] 67 | library TestDispServerLib 68 | { 69 | importlib("stdole2.tlb"); 70 | 71 | [ 72 | uuid(bb2aba53-9d42-435b-acc3-ae2c274517b0), 73 | helpstring("TestDispServer class object") 74 | ] 75 | coclass TestDispServer { 76 | [default] dispinterface DTestDispServer; 77 | [default, source] dispinterface DTestDispServerEvents; 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ######################## 2 | The ``comtypes`` package 3 | ######################## 4 | 5 | |comtypes| is a *pure Python* COM package based on the 6 | `ctypes `_ ffi 7 | foreign function library. |ctypes| is included in Python 2.5 and 8 | later, it is also available for Python 2.4 as separate download. 9 | 10 | While the `pywin32 `_ package 11 | contains superior client side support for *dispatch based* COM 12 | interfaces, it is not possible to access *custom* COM interfaces 13 | unless they are wrapped in C++-code. 14 | 15 | The |comtypes| package makes it easy to access and implement both 16 | custom and dispatch based COM interfaces. 17 | 18 | .. contents:: 19 | 20 | 21 | Functionalities 22 | *************** 23 | 24 | .. toctree:: 25 | :maxdepth: 1 26 | 27 | client 28 | server 29 | com_interfaces 30 | npsupport 31 | threading 32 | 33 | 34 | Links 35 | ***** 36 | 37 | Kourovtsev, Yaroslav (2008). `"Working with Custom COM Interfaces from Python" `_ 38 | 39 | This article describes how to use |comtypes| to access a custom 40 | COM object. 41 | 42 | Chen, Alicia (2012). `"Comtypes: How Dropbox learned to stop worrying and love the COM" `_ 43 | 44 | This article describes Dropbox's experience using |comtypes| to 45 | interact with COM objects in a Windows application. 46 | 47 | 48 | Downloads 49 | ********* 50 | 51 | The |comtypes| project is hosted on `GitHub `_. 52 | Releases can be downloaded from the `GitHub releases `_ 53 | section. 54 | 55 | 56 | Installation 57 | ************ 58 | 59 | |comtypes| is available on `PyPI `_ and 60 | can be installed with ``pip``: 61 | 62 | .. sourcecode:: shell 63 | 64 | pip install comtypes 65 | 66 | 67 | .. |comtypes| replace:: ``comtypes`` 68 | 69 | .. |ctypes| replace:: ``ctypes`` 70 | -------------------------------------------------------------------------------- /comtypes/client/_activeobj.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, TypeVar, overload 2 | from typing import Union as _UnionT 3 | 4 | import comtypes 5 | import comtypes.client.dynamic 6 | from comtypes import GUID, CoClass, IUnknown, automation 7 | from comtypes.client._managing import _manage 8 | 9 | _T_IUnknown = TypeVar("_T_IUnknown", bound=IUnknown) 10 | 11 | 12 | ################################################################ 13 | # 14 | # Object creation 15 | # 16 | @overload 17 | def GetActiveObject(progid: _UnionT[str, type[CoClass], GUID]) -> Any: ... 18 | @overload 19 | def GetActiveObject( 20 | progid: _UnionT[str, type[CoClass], GUID], interface: type[_T_IUnknown] 21 | ) -> _T_IUnknown: ... 22 | def GetActiveObject( 23 | progid: _UnionT[str, type[CoClass], GUID], 24 | interface: Optional[type[IUnknown]] = None, 25 | dynamic: bool = False, 26 | ) -> Any: 27 | """Return a pointer to a running COM object that has been 28 | registered with COM. 29 | 30 | 'progid' may be a string like "Excel.Application", 31 | a string specifying a clsid, a GUID instance, or an object with 32 | a _clsid_ attribute which should be any of the above. 33 | 'interface' allows to force a certain interface. 34 | 'dynamic=True' will return a dynamic dispatch object. 35 | """ 36 | clsid = GUID.from_progid(progid) 37 | if dynamic: 38 | if interface is not None: 39 | raise ValueError("interface and dynamic are mutually exclusive") 40 | interface = automation.IDispatch 41 | elif interface is None: 42 | interface = getattr(progid, "_com_interfaces_", [None])[0] 43 | obj = comtypes.GetActiveObject(clsid, interface=interface) 44 | if dynamic: 45 | return comtypes.client.dynamic.Dispatch(obj) 46 | return _manage(obj, clsid, interface=interface) 47 | 48 | 49 | def RegisterActiveObject( 50 | punk: IUnknown, progid: _UnionT[str, type[CoClass], GUID], weak: bool = True 51 | ) -> int: 52 | clsid = GUID.from_progid(progid) 53 | flags = comtypes.ACTIVEOBJECT_WEAK if weak else comtypes.ACTIVEOBJECT_STRONG 54 | return comtypes.RegisterActiveObject(punk, clsid, flags) 55 | -------------------------------------------------------------------------------- /source/CppTestSrv/CoComtypesDispSafearrayParamTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | // 10 | // CoComtypesDispSafearrayParamTest.cpp - Component 11 | // 12 | 13 | #include "Iface.h" 14 | #include "CUnknown.h" 15 | 16 | /////////////////////////////////////////////////////////// 17 | // 18 | // Component B 19 | // 20 | class CB : public CUnknown, 21 | public IDualSafearrayParamTest 22 | { 23 | public: 24 | // Creation 25 | static HRESULT CreateInstance(IUnknown* pUnknownOuter, 26 | CUnknown** ppNewComponent ) ; 27 | 28 | private: 29 | // Declare the delegating IUnknown. 30 | DECLARE_IUNKNOWN 31 | 32 | // IUnknown 33 | virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, 34 | void** ppv) ; 35 | 36 | // IDispatch 37 | virtual HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo) ; 38 | 39 | virtual HRESULT __stdcall GetTypeInfo( 40 | UINT iTypeInfo, 41 | LCID, // Localization is not supported. 42 | ITypeInfo** ppITypeInfo) ; 43 | 44 | virtual HRESULT __stdcall GetIDsOfNames( 45 | const IID& iid, 46 | OLECHAR** arrayNames, 47 | UINT countNames, 48 | LCID, // Localization is not supported. 49 | DISPID* arrayDispIDs) ; 50 | 51 | virtual HRESULT __stdcall Invoke( 52 | DISPID dispidMember, 53 | const IID& iid, 54 | LCID, // Localization is not supported. 55 | WORD wFlags, 56 | DISPPARAMS* pDispParams, 57 | VARIANT* pvarResult, 58 | EXCEPINFO* pExcepInfo, 59 | UINT* pArgErr) ; 60 | 61 | // Interface IDualSafearrayParamTest 62 | virtual HRESULT __stdcall InitArray(SAFEARRAY* *test_array) ; 63 | virtual HRESULT __stdcall VerifyArray( 64 | SAFEARRAY *test_array, 65 | VARIANT_BOOL* result) ; 66 | 67 | // Initialization 68 | virtual HRESULT Init() ; 69 | 70 | // Constructor 71 | CB(IUnknown* pUnknownOuter) ; 72 | 73 | // Destructor 74 | ~CB() ; 75 | 76 | // Pointer to type information. 77 | ITypeInfo* m_pITypeInfo ; 78 | } ; 79 | -------------------------------------------------------------------------------- /source/AvmcIfc.cpp: -------------------------------------------------------------------------------- 1 | // AvmcIfc.cpp : Implementation of DLL Exports. 2 | 3 | 4 | // Note: Proxy/Stub Information 5 | // To build a separate proxy/stub DLL, 6 | // run nmake -f AvmcIfcps.mk in the project directory. 7 | 8 | #include "stdafx.h" 9 | #include "resource.h" 10 | #include 11 | #include "AvmcIfc.h" 12 | 13 | #include "AvmcIfc_i.c" 14 | #include "Avmc.h" 15 | 16 | 17 | CComModule _Module; 18 | 19 | BEGIN_OBJECT_MAP(ObjectMap) 20 | OBJECT_ENTRY(CLSID_Avmc, Avmc) 21 | END_OBJECT_MAP() 22 | 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // DLL Entry Point 25 | 26 | extern "C" 27 | BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) 28 | { 29 | if (dwReason == DLL_PROCESS_ATTACH) 30 | { 31 | _Module.Init(ObjectMap, hInstance, &LIBID_AVMCIFCLib); 32 | DisableThreadLibraryCalls(hInstance); 33 | } 34 | else if (dwReason == DLL_PROCESS_DETACH) 35 | _Module.Term(); 36 | return TRUE; // ok 37 | } 38 | 39 | ///////////////////////////////////////////////////////////////////////////// 40 | // Used to determine whether the DLL can be unloaded by OLE 41 | 42 | STDAPI DllCanUnloadNow(void) 43 | { 44 | return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; 45 | } 46 | 47 | ///////////////////////////////////////////////////////////////////////////// 48 | // Returns a class factory to create an object of the requested type 49 | 50 | STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) 51 | { 52 | return _Module.GetClassObject(rclsid, riid, ppv); 53 | } 54 | 55 | ///////////////////////////////////////////////////////////////////////////// 56 | // DllRegisterServer - Adds entries to the system registry 57 | 58 | STDAPI DllRegisterServer(void) 59 | { 60 | // registers object, typelib and all interfaces in typelib 61 | return _Module.RegisterServer(TRUE); 62 | } 63 | 64 | ///////////////////////////////////////////////////////////////////////////// 65 | // DllUnregisterServer - Removes entries from the system registry 66 | 67 | STDAPI DllUnregisterServer(void) 68 | { 69 | return _Module.UnregisterServer(TRUE); 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /source/CppTestSrv/CoComtypesDispRecordParamTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | // 10 | // CoComtypesDispRecordParamTest.cpp - Component 11 | // 12 | 13 | #include "Iface.h" 14 | #include "CUnknown.h" 15 | 16 | /////////////////////////////////////////////////////////// 17 | // 18 | // Component A 19 | // 20 | class CA : public CUnknown, 21 | public IDualRecordParamTest 22 | { 23 | public: 24 | // Creation 25 | static HRESULT CreateInstance(IUnknown* pUnknownOuter, 26 | CUnknown** ppNewComponent ) ; 27 | 28 | private: 29 | // Declare the delegating IUnknown. 30 | DECLARE_IUNKNOWN 31 | 32 | // IUnknown 33 | virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, 34 | void** ppv) ; 35 | 36 | // IDispatch 37 | virtual HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo) ; 38 | 39 | virtual HRESULT __stdcall GetTypeInfo( 40 | UINT iTypeInfo, 41 | LCID, // Localization is not supported. 42 | ITypeInfo** ppITypeInfo) ; 43 | 44 | virtual HRESULT __stdcall GetIDsOfNames( 45 | const IID& iid, 46 | OLECHAR** arrayNames, 47 | UINT countNames, 48 | LCID, // Localization is not supported. 49 | DISPID* arrayDispIDs) ; 50 | 51 | virtual HRESULT __stdcall Invoke( 52 | DISPID dispidMember, 53 | const IID& iid, 54 | LCID, // Localization is not supported. 55 | WORD wFlags, 56 | DISPPARAMS* pDispParams, 57 | VARIANT* pvarResult, 58 | EXCEPINFO* pExcepInfo, 59 | UINT* pArgErr) ; 60 | 61 | // Interface IDualRecordParamTest 62 | virtual HRESULT __stdcall InitRecord(StructRecordParamTest* test_record) ; 63 | virtual HRESULT __stdcall VerifyRecord( 64 | StructRecordParamTest* test_record, 65 | VARIANT_BOOL* result) ; 66 | 67 | // Initialization 68 | virtual HRESULT Init() ; 69 | 70 | // Constructor 71 | CA(IUnknown* pUnknownOuter) ; 72 | 73 | // Destructor 74 | ~CA() ; 75 | 76 | // Pointer to type information. 77 | ITypeInfo* m_pITypeInfo ; 78 | } ; 79 | -------------------------------------------------------------------------------- /comtypes/_post_coinit/activeobj.py: -------------------------------------------------------------------------------- 1 | from ctypes import HRESULT, POINTER, OleDLL, byref, c_ulong, c_void_p 2 | from ctypes.wintypes import DWORD, LPVOID 3 | from typing import TYPE_CHECKING, Optional, TypeVar, overload 4 | from typing import Union as _UnionT 5 | 6 | from comtypes import GUID 7 | from comtypes._post_coinit.unknwn import IUnknown 8 | from comtypes.GUID import REFCLSID 9 | 10 | if TYPE_CHECKING: 11 | from comtypes import hints # type: ignore 12 | 13 | 14 | _T_IUnknown = TypeVar("_T_IUnknown", bound=IUnknown) 15 | 16 | ACTIVEOBJECT_STRONG = 0x0 17 | ACTIVEOBJECT_WEAK = 0x1 18 | 19 | 20 | def RegisterActiveObject( 21 | punk: "_UnionT[IUnknown, hints.LP_LP_Vtbl]", clsid: GUID, flags: int 22 | ) -> int: 23 | """Registers a pointer as the active object for its class and returns the handle.""" 24 | handle = c_ulong() 25 | _RegisterActiveObject(punk, byref(clsid), flags, byref(handle)) 26 | return handle.value 27 | 28 | 29 | def RevokeActiveObject(handle: int) -> None: 30 | """Ends a pointer's status as active.""" 31 | _RevokeActiveObject(handle, None) 32 | 33 | 34 | @overload 35 | def GetActiveObject(clsid: GUID, interface: None = None) -> IUnknown: ... 36 | @overload 37 | def GetActiveObject(clsid: GUID, interface: type[_T_IUnknown]) -> _T_IUnknown: ... 38 | def GetActiveObject( 39 | clsid: GUID, interface: Optional[type[IUnknown]] = None 40 | ) -> IUnknown: 41 | """Retrieves a pointer to a running object""" 42 | p = POINTER(IUnknown)() 43 | _GetActiveObject(byref(clsid), None, byref(p)) 44 | if interface is not None: 45 | p = p.QueryInterface(interface) # type: ignore 46 | return p # type: ignore 47 | 48 | 49 | _oleaut32 = OleDLL("oleaut32") 50 | 51 | _RegisterActiveObject = _oleaut32.RegisterActiveObject 52 | _RegisterActiveObject.argtypes = [c_void_p, REFCLSID, DWORD, POINTER(DWORD)] 53 | _RegisterActiveObject.restype = HRESULT 54 | 55 | _RevokeActiveObject = _oleaut32.RevokeActiveObject 56 | _RevokeActiveObject.argtypes = [DWORD, LPVOID] 57 | _RevokeActiveObject.restype = HRESULT 58 | 59 | _GetActiveObject = _oleaut32.GetActiveObject 60 | _GetActiveObject.argtypes = [REFCLSID, LPVOID, POINTER(POINTER(IUnknown))] 61 | _GetActiveObject.restype = HRESULT 62 | -------------------------------------------------------------------------------- /comtypes/logutil.py: -------------------------------------------------------------------------------- 1 | # logutil.py 2 | import logging 3 | from ctypes import WinDLL 4 | from ctypes.wintypes import LPCSTR, LPCWSTR 5 | 6 | _kernel32 = WinDLL("kernel32") 7 | 8 | _OutputDebugStringA = _kernel32.OutputDebugStringA 9 | _OutputDebugStringA.argtypes = [LPCSTR] 10 | _OutputDebugStringA.restype = None 11 | 12 | _OutputDebugStringW = _kernel32.OutputDebugStringW 13 | _OutputDebugStringW.argtypes = [LPCWSTR] 14 | _OutputDebugStringW.restype = None 15 | 16 | 17 | class NTDebugHandler(logging.Handler): 18 | def emit( 19 | self, 20 | record, 21 | writeA=_OutputDebugStringA, 22 | writeW=_OutputDebugStringW, 23 | ): 24 | text = self.format(record) 25 | if isinstance(text, str): 26 | writeA(text + "\n") 27 | else: 28 | writeW(text + "\n") 29 | 30 | 31 | logging.NTDebugHandler = NTDebugHandler 32 | 33 | 34 | def setup_logging(*pathnames): 35 | import configparser 36 | 37 | parser = configparser.ConfigParser() 38 | parser.optionxform = str # use case sensitive option names! 39 | 40 | parser.read(pathnames) 41 | 42 | DEFAULTS = { 43 | "handler": "StreamHandler()", 44 | "format": "%(levelname)s:%(name)s:%(message)s", 45 | "level": "WARNING", 46 | } 47 | 48 | def get(section, option): 49 | try: 50 | return parser.get(section, option, True) 51 | except (configparser.NoOptionError, configparser.NoSectionError): 52 | return DEFAULTS[option] 53 | 54 | levelname = get("logging", "level") 55 | format = get("logging", "format") 56 | handlerclass = get("logging", "handler") 57 | 58 | # convert level name to level value 59 | level = getattr(logging, levelname) 60 | # create the handler instance 61 | handler = eval(handlerclass, vars(logging)) 62 | formatter = logging.Formatter(format) 63 | handler.setFormatter(formatter) 64 | logging.root.addHandler(handler) 65 | logging.root.setLevel(level) 66 | 67 | try: 68 | for name, value in parser.items("logging.levels", True): 69 | value = getattr(logging, value) 70 | logging.getLogger(name).setLevel(value) 71 | except configparser.NoSectionError: 72 | pass 73 | -------------------------------------------------------------------------------- /comtypes/patcher.py: -------------------------------------------------------------------------------- 1 | class Patch: 2 | """ 3 | Implements a class decorator suitable for patching an existing class with 4 | a new namespace. 5 | 6 | For example, consider this trivial class (that your code doesn't own): 7 | 8 | >>> class MyClass: 9 | ... def __init__(self, param): 10 | ... self.param = param 11 | ... def bar(self): 12 | ... print("orig bar") 13 | 14 | To add attributes to MyClass, you can use Patch: 15 | 16 | >>> @Patch(MyClass) 17 | ... class JustANamespace: 18 | ... def print_param(self): 19 | ... print(self.param) 20 | >>> ob = MyClass('foo') 21 | >>> ob.print_param() 22 | foo 23 | 24 | The namespace is assigned None, so there's no mistaking the purpose 25 | >>> JustANamespace 26 | 27 | The patcher will replace the existing methods: 28 | 29 | >>> @Patch(MyClass) 30 | ... class SomeNamespace: 31 | ... def bar(self): 32 | ... print("replaced bar") 33 | >>> ob = MyClass('foo') 34 | >>> ob.bar() 35 | replaced bar 36 | 37 | But it will not replace methods if no_replace is indicated. 38 | 39 | >>> @Patch(MyClass) 40 | ... class AnotherNamespace: 41 | ... @no_replace 42 | ... def bar(self): 43 | ... print("candy bar") 44 | >>> ob = MyClass('foo') 45 | >>> ob.bar() 46 | replaced bar 47 | 48 | """ 49 | 50 | def __init__(self, target): 51 | self.target = target 52 | 53 | def __call__(self, patches): 54 | for name, value in vars(patches).items(): 55 | if name in vars(ReferenceEmptyClass): 56 | continue 57 | no_replace = getattr(value, "__no_replace", False) 58 | if no_replace and hasattr(self.target, name): 59 | continue 60 | setattr(self.target, name, value) 61 | 62 | 63 | def no_replace(f): 64 | """ 65 | Method decorator to indicate that a method definition shall 66 | silently be ignored if it already exists in the target class. 67 | """ 68 | f.__no_replace = True 69 | return f 70 | 71 | 72 | class ReferenceEmptyClass: 73 | """ 74 | This empty class will serve as a reference for attributes present on 75 | any class. 76 | """ 77 | -------------------------------------------------------------------------------- /source/AvmcIfc.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {2e2eeffd-0787-4fc6-910e-67b3c0fd1abf} 6 | cpp;c;cxx;rc;def;r;odl;idl;hpj;bat 7 | 8 | 9 | {c8e183eb-bc67-4cac-84c1-7ca6364c2415} 10 | h;hpp;hxx;hm;inl 11 | 12 | 13 | {7f7732c5-9729-4d48-83f7-406657514f1a} 14 | ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Source Files 31 | 32 | 33 | Resource Files 34 | 35 | 36 | 37 | 38 | Source Files 39 | 40 | 41 | 42 | 43 | Source Files 44 | 45 | 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | -------------------------------------------------------------------------------- /admin/extract-comtypes-repo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script extracts the comtypes repository from the svn.python.org repository. 4 | 5 | if test ! -d mirror 6 | then { 7 | svnadmin create mirror 8 | cat <<'EOF' > mirror/hooks/pre-revprop-change 9 | #!/bin/sh 10 | USER="$3" 11 | 12 | if [ "$USER" = "svnsync" ]; then exit 0; fi 13 | 14 | echo "Only the svnsync user can change revprops" >&2 15 | exit 1 16 | EOF 17 | 18 | chmod +x mirror/hooks/pre-revprop-change 19 | svnsync init --username svnsync file://`pwd`/mirror http://svn.python.org/projects/ 20 | } 21 | fi 22 | 23 | svnsync sync file://`pwd`/mirror 24 | 25 | if test ! -f svn.python.org-dumpfile 26 | then { 27 | echo "Dumping svn.python.org SVN repository mirror, this may take an hour or so." 28 | svnadmin dump ./mirror -r 40000:HEAD > svn.python.org-dumpfile 29 | } fi 30 | 31 | if test ! -f ctypes-dumpfile 32 | then { 33 | rm -fr comtypes-dumpfile 34 | echo "Filtering ctypes out of the svn.python.org SVN repository dumpfile, this may take some minutes." 35 | # It is important that we use svndumpfilter first to create a smaller dumpfile (5 GB reduced to 120 MB), 36 | # otherwise svndumpfilter2 below will run out of memory. 37 | cat svn.python.org-dumpfile | svndumpfilter include ctypes >ctypes-dumpfile 38 | } fi 39 | 40 | if test ! -f comtypes-dumpfile 41 | then { 42 | rm -fr comtypes-repo 43 | echo "Filtering comtypes out of the ctypes dumpfile, this may take some minutes." 44 | # We include ctypes/trunk/comtypes, ctypes/branches/comtypes*, ctypes/tags/comtypes* 45 | # Then we strip off the ctypes/ prefix, and rewrite 'trunk/comtypes' into 'trunk' 46 | cat ctypes-dumpfile | ./svndumpfilter2 --drop-empty-revs --renumber-revs ./mirror ctypes/trunk/comtypes ctypes/branches/comtypes* ctypes/tags/comtypes* | sed "s/-path: ctypes\//-path: /g" | sed "s/-path: trunk\/comtypes/-path: trunk/g" >comtypes-dumpfile 47 | } fi 48 | 49 | if test ! -d comtypes-repo 50 | then { 51 | svnadmin create comtypes-repo 52 | svn mkdir file://`pwd`/comtypes-repo/branches -m "Create initial structure" 53 | svn mkdir file://`pwd`/comtypes-repo/tags -m "Create initial structure" 54 | svnadmin load comtypes-repo < comtypes-dumpfile 55 | } fi 56 | 57 | # Create the dumpfile 58 | svnadmin dump ./comtypes-repo | bzip2 > sfimportcomtypes.bz2 59 | # and test it. 60 | svnadmin create ./new-comtypes-repo 61 | bzcat sfimportcomtypes.bz2 | svnadmin load ./new-comtypes-repo 62 | -------------------------------------------------------------------------------- /comtypes/test/find_memleak.py: -------------------------------------------------------------------------------- 1 | import gc 2 | from ctypes import POINTER, Structure, WinDLL, WinError, byref, c_size_t, sizeof 3 | from ctypes.wintypes import BOOL, DWORD, HANDLE 4 | 5 | ################################################################ 6 | 7 | 8 | class PROCESS_MEMORY_COUNTERS(Structure): 9 | _fields_ = [ 10 | ("cb", DWORD), 11 | ("PageFaultCount", DWORD), 12 | ("PeakWorkingSetSize", c_size_t), 13 | ("WorkingSetSize", c_size_t), 14 | ("QuotaPeakPagedPoolUsage", c_size_t), 15 | ("QuotaPagedPoolUsage", c_size_t), 16 | ("QuotaPeakNonPagedPoolUsage", c_size_t), 17 | ("QuotaNonPagedPoolUsage", c_size_t), 18 | ("PagefileUsage", c_size_t), 19 | ("PeakPagefileUsage", c_size_t), 20 | ] 21 | 22 | def __init__(self): 23 | self.cb = sizeof(self) 24 | 25 | def dump(self): 26 | for n, _ in self._fields_[2:]: 27 | print(n, getattr(self, n) / 1e6) 28 | 29 | 30 | _psapi = WinDLL("psapi") 31 | 32 | _GetProcessMemoryInfo = _psapi.GetProcessMemoryInfo 33 | _GetProcessMemoryInfo.argtypes = [HANDLE, POINTER(PROCESS_MEMORY_COUNTERS), DWORD] 34 | _GetProcessMemoryInfo.restype = BOOL 35 | 36 | 37 | def wss(): 38 | # Return the working set size (memory used by process) 39 | pmi = PROCESS_MEMORY_COUNTERS() 40 | if not _GetProcessMemoryInfo(-1, byref(pmi), sizeof(pmi)): 41 | raise WinError() 42 | return pmi.WorkingSetSize 43 | 44 | 45 | LOOPS = 10, 1000 46 | 47 | 48 | def find_memleak(func, loops=LOOPS): 49 | # call 'func' several times, so that memory consumption 50 | # stabilizes: 51 | for j in range(loops[0]): 52 | for k in range(loops[1]): 53 | func() 54 | gc.collect() 55 | gc.collect() 56 | gc.collect() 57 | bytes = wss() 58 | # call 'func' several times, recording the difference in 59 | # memory consumption before and after the call. Repeat this a 60 | # few times, and return a list containing the memory 61 | # consumption differences. 62 | for j in range(loops[0]): 63 | for k in range(loops[1]): 64 | func() 65 | gc.collect() 66 | gc.collect() 67 | gc.collect() 68 | # return the increased in process size 69 | result = wss() - bytes 70 | # Sometimes the process size did decrease, we do not report leaks 71 | # in this case: 72 | return max(result, 0) 73 | -------------------------------------------------------------------------------- /comtypes/test/test_wmi.py: -------------------------------------------------------------------------------- 1 | import unittest as ut 2 | 3 | from comtypes.client import CoGetObject 4 | 5 | 6 | # WMI has dual interfaces. 7 | # Some methods/properties have "[out] POINTER(VARIANT)" parameters. 8 | # This test checks that these parameters are returned as strings: 9 | # that's what VARIANT.__ctypes_from_outparam__ does. 10 | class Test(ut.TestCase): 11 | def test_wmi(self): 12 | wmi: "WbemScripting.ISWbemServices" = CoGetObject("winmgmts:") 13 | disks = wmi.InstancesOf("Win32_LogicalDisk") 14 | 15 | # There are different typelibs installed for WMI on win2k and winXP. 16 | # WbemScripting refers to their guid: 17 | # Win2k: 18 | # import comtypes.gen._565783C6_CB41_11D1_8B02_00600806D9B6_0_1_1 as mod 19 | # WinXP: 20 | # import comtypes.gen._565783C6_CB41_11D1_8B02_00600806D9B6_0_1_2 as mod 21 | # So, the one that's referenced onm WbemScripting will be used, whether the 22 | # actual typelib is available or not. XXX 23 | from comtypes.gen import WbemScripting 24 | 25 | WbemScripting.wbemPrivilegeCreateToken 26 | 27 | for item in disks: 28 | # obj[index] is forwarded to obj.Item(index) 29 | # .Value is a property with "[out] POINTER(VARIANT)" parameter. 30 | item: "WbemScripting.ISWbemObject" 31 | a = item.Properties_["Caption"].Value 32 | b = item.Properties_.Item("Caption").Value 33 | c = item.Properties_("Caption").Value 34 | self.assertEqual(a, b) 35 | self.assertEqual(a, c) 36 | self.assertTrue(isinstance(a, str)) 37 | self.assertTrue(isinstance(b, str)) 38 | self.assertTrue(isinstance(c, str)) 39 | result = {} 40 | for prop in item.Properties_: 41 | prop: "WbemScripting.ISWbemProperty" 42 | self.assertTrue(isinstance(prop.Name, str)) 43 | prop.Value 44 | result[prop.Name] = prop.Value 45 | # print "\t", (prop.Name, prop.Value) 46 | self.assertEqual(len(item.Properties_), item.Properties_.Count) 47 | self.assertEqual(len(item.Properties_), len(result)) 48 | self.assertTrue(isinstance(item.Properties_["Description"].Value, str)) 49 | # len(obj) is forwared to obj.Count 50 | self.assertEqual(len(disks), disks.Count) 51 | 52 | 53 | if __name__ == "__main__": 54 | ut.main() 55 | -------------------------------------------------------------------------------- /source/Avmc.h: -------------------------------------------------------------------------------- 1 | // Avmc.h: Definition of the Avmc class 2 | // 3 | ////////////////////////////////////////////////////////////////////// 4 | 5 | #if !defined(AFX_AVMC_H__14DBEE48_EB7D_48AD_8BEE_8E15B2D0A780__INCLUDED_) 6 | #define AFX_AVMC_H__14DBEE48_EB7D_48AD_8BEE_8E15B2D0A780__INCLUDED_ 7 | 8 | #if _MSC_VER > 1000 9 | #pragma once 10 | #endif // _MSC_VER > 1000 11 | 12 | #include "resource.h" // main symbols 13 | 14 | ///////////////////////////////////////////////////////////////////////////// 15 | // Avmc 16 | 17 | //#include "FTD2XX.h" 18 | typedef PVOID FT_HANDLE; 19 | typedef ULONG FT_STATUS; 20 | 21 | enum { 22 | FT_OK, 23 | FT_INVALID_HANDLE, 24 | FT_DEVICE_NOT_FOUND, 25 | FT_DEVICE_NOT_OPENED, 26 | FT_IO_ERROR, 27 | FT_INSUFFICIENT_RESOURCES, 28 | FT_INVALID_PARAMETER, 29 | FT_INVALID_BAUD_RATE, 30 | 31 | FT_DEVICE_NOT_OPENED_FOR_ERASE, 32 | FT_DEVICE_NOT_OPENED_FOR_WRITE, 33 | FT_FAILED_TO_WRITE_DEVICE, 34 | FT_EEPROM_READ_FAILED, 35 | FT_EEPROM_WRITE_FAILED, 36 | FT_EEPROM_ERASE_FAILED, 37 | FT_EEPROM_NOT_PRESENT, 38 | FT_EEPROM_NOT_PROGRAMMED, 39 | FT_INVALID_ARGS, 40 | FT_NOT_SUPPORTED, 41 | FT_OTHER_ERROR 42 | }; 43 | 44 | 45 | //#define FT_SUCCESS(status) ((status) == FT_OK) 46 | 47 | typedef struct _ft_device_list_info_node { 48 | ULONG Flags; 49 | ULONG Type; 50 | ULONG ID; 51 | DWORD LocId; 52 | char SerialNumber[16]; 53 | char Description[64]; 54 | FT_HANDLE ftHandle; 55 | } FT_DEVICE_LIST_INFO_NODE; 56 | 57 | class Avmc : 58 | public IDispatchImpl, 59 | public ISupportErrorInfo, 60 | public CComObjectRoot, 61 | public CComCoClass 62 | { 63 | public: 64 | Avmc() {} 65 | BEGIN_COM_MAP(Avmc) 66 | COM_INTERFACE_ENTRY(IDispatch) 67 | COM_INTERFACE_ENTRY(IAvmc) 68 | COM_INTERFACE_ENTRY(ISupportErrorInfo) 69 | END_COM_MAP() 70 | //DECLARE_NOT_AGGREGATABLE(Avmc) 71 | // Remove the comment from the line above if you don't want your object to 72 | // support aggregation. 73 | 74 | DECLARE_REGISTRY_RESOURCEID(IDR_Avmc) 75 | // ISupportsErrorInfo 76 | STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); 77 | 78 | // IAvmc 79 | public: 80 | STDMETHOD(FindAllAvmc)(/*[out]*/ SAFEARRAY **avmcList); 81 | 82 | private: 83 | void FinalizeCommand(char *command, int cmdLength); 84 | int ReadToSimpleArray(int devNum, char *arr); 85 | 86 | private: 87 | FT_DEVICE_LIST_INFO_NODE *devInfo; 88 | char mResArray[512]; // Inconsistant array - just to read results... 89 | }; 90 | 91 | #endif // !defined(AFX_AVMC_H__14DBEE48_EB7D_48AD_8BEE_8E15B2D0A780__INCLUDED_) 92 | -------------------------------------------------------------------------------- /source/CppTestSrv/UTIL.CPP: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | // 10 | // 11 | // util.cpp - Common utilities for printing out messages 12 | // 13 | // 14 | #include 15 | #include //sprintf 16 | #include 17 | #include 18 | // #include 19 | 20 | #include "util.h" 21 | 22 | // We are building a local server. 23 | // Listbox window handle 24 | extern HWND g_hWndListBox ; 25 | 26 | static inline void output(const char* sz) 27 | { 28 | size_t newsize = strlen(sz) + 1; 29 | wchar_t* wcstring = new wchar_t[newsize]; 30 | size_t convertedChars = 0; 31 | mbstowcs_s(&convertedChars, wcstring, newsize, sz, _TRUNCATE); 32 | ::SendMessage(g_hWndListBox, LB_ADDSTRING, 0, (LPARAM)wcstring) ; 33 | delete []wcstring; 34 | } 35 | 36 | // 37 | // Utilities 38 | // 39 | namespace Util 40 | { 41 | 42 | // 43 | // Print out a message with a label. 44 | // 45 | void Trace(const char* szLabel, const char* szText, HRESULT hr) 46 | { 47 | char buf[256] ; 48 | sprintf(buf, "%s: \t%s", szLabel, szText) ; 49 | output(buf) ; 50 | 51 | if (FAILED(hr)) 52 | { 53 | ErrorMessage(hr) ; 54 | } 55 | } 56 | 57 | // 58 | // Print out the COM/OLE error string for an HRESULT. 59 | // 60 | void ErrorMessage(HRESULT hr) 61 | { 62 | LPTSTR pMsgBuf = NULL; 63 | 64 | ::FormatMessage( 65 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 66 | NULL, 67 | hr, 68 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language 69 | (LPTSTR)&pMsgBuf, 70 | 0, 71 | NULL 72 | ) ; 73 | 74 | char buf[256] ; 75 | int iLength = wcslen(pMsgBuf)+1 ; 76 | char* psz = new char[iLength] ; 77 | wcstombs(psz, pMsgBuf, iLength) ; 78 | sprintf(buf, "Error (%x): %s", hr, psz) ; 79 | output(buf) ; 80 | 81 | // Free the buffer. 82 | LocalFree(pMsgBuf) ; 83 | } 84 | 85 | } ; // End Namespace Util 86 | 87 | 88 | // 89 | // Overloaded ostream insertion operator 90 | // Converts from wchar_t to char 91 | // 92 | std::ostream& operator<< ( std::ostream& os, const wchar_t* wsz ) 93 | { 94 | // Length of incoming string 95 | int iLength = wcslen(wsz)+1 ; 96 | 97 | // Allocate buffer for converted string. 98 | char* psz = new char[iLength] ; 99 | 100 | // Convert from wchar_t to char. 101 | wcstombs(psz, wsz, iLength) ; 102 | 103 | // Send it out. 104 | os << psz ; 105 | 106 | // cleanup 107 | delete [] psz ; 108 | return os ; 109 | } 110 | -------------------------------------------------------------------------------- /source/AvmcIfc.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 11.00 2 | # Visual Studio 2010 3 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AvmcIfc", "AvmcIfc.vcxproj", "{8B2CD355-6596-B892-F782-8DCDD607C496}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Win32 = Debug|Win32 8 | Debug|x64 = Debug|x64 9 | Release MinDependency|Win32 = Release MinDependency|Win32 10 | Release MinDependency|x64 = Release MinDependency|x64 11 | Release MinSize|Win32 = Release MinSize|Win32 12 | Release MinSize|x64 = Release MinSize|x64 13 | Unicode Debug|Win32 = Unicode Debug|Win32 14 | Unicode Debug|x64 = Unicode Debug|x64 15 | Unicode Release MinDependency|Win32 = Unicode Release MinDependency|Win32 16 | Unicode Release MinDependency|x64 = Unicode Release MinDependency|x64 17 | Unicode Release MinSize|Win32 = Unicode Release MinSize|Win32 18 | Unicode Release MinSize|x64 = Unicode Release MinSize|x64 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Debug|Win32.ActiveCfg = Debug|Win32 22 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Debug|Win32.Build.0 = Debug|Win32 23 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Debug|x64.ActiveCfg = Debug|x64 24 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Debug|x64.Build.0 = Debug|x64 25 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Release MinDependency|Win32.ActiveCfg = Release MinDependency|Win32 26 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Release MinDependency|x64.ActiveCfg = Release MinDependency|x64 27 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Release MinSize|Win32.ActiveCfg = Release MinSize|Win32 28 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Release MinSize|x64.ActiveCfg = Release MinSize|x64 29 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Debug|Win32.ActiveCfg = Unicode Debug|Win32 30 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Debug|x64.ActiveCfg = Unicode Debug|x64 31 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Release MinDependency|Win32.ActiveCfg = Unicode Release MinDependency|Win32 32 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Release MinDependency|x64.ActiveCfg = Unicode Release MinDependency|x64 33 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Release MinSize|Win32.ActiveCfg = Unicode Release MinSize|Win32 34 | {8B2CD355-6596-B892-F782-8DCDD607C496}.Unicode Release MinSize|x64.ActiveCfg = Unicode Release MinSize|x64 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /comtypes/test/test_word.py: -------------------------------------------------------------------------------- 1 | import time 2 | import unittest 3 | 4 | import comtypes.client 5 | from comtypes import COMError 6 | 7 | try: 8 | comtypes.client.GetModule( 9 | ("{00020905-0000-0000-C000-000000000046}",) 10 | ) # Word libUUID 11 | from comtypes.gen import Word 12 | 13 | IMPORT_FAILED = False 14 | except (ImportError, OSError): 15 | IMPORT_FAILED = True 16 | 17 | 18 | ################################################################ 19 | # 20 | # TODO: 21 | # 22 | # It seems bad that only external test like this 23 | # can verify the behavior of `comtypes` implementation. 24 | # Find a different built-in win32 API to use. 25 | # 26 | ################################################################ 27 | 28 | 29 | class _Sink: 30 | def __init__(self): 31 | self.events = [] 32 | 33 | # Word Application Event 34 | def DocumentChange(self, this, *args): 35 | self.events.append("DocumentChange") 36 | 37 | 38 | @unittest.skipIf(IMPORT_FAILED, "This depends on Word.") 39 | class Test(unittest.TestCase): 40 | def setUp(self): 41 | # create a word instance 42 | self.word = comtypes.client.CreateObject("Word.Application") 43 | 44 | def tearDown(self): 45 | self.word.Quit() 46 | del self.word 47 | 48 | def test(self): 49 | word = self.word 50 | # Get the instance again, and receive events from that 51 | w2 = comtypes.client.GetActiveObject("Word.Application") 52 | sink = _Sink() 53 | conn = comtypes.client.GetEvents(w2, sink=sink) 54 | 55 | word.Visible = 1 56 | 57 | doc = word.Documents.Add() 58 | wrange = doc.Range() 59 | for i in range(10): 60 | wrange.InsertAfter("Hello from comtypes %d\n" % i) 61 | 62 | for i, para in enumerate(doc.Paragraphs): 63 | f = para.Range.Font 64 | f.ColorIndex = i + 1 65 | f.Size = 12 + (2 * i) 66 | 67 | time.sleep(0.5) 68 | 69 | doc.Close(SaveChanges=Word.wdDoNotSaveChanges) 70 | 71 | del word, w2 72 | 73 | time.sleep(0.5) 74 | conn.disconnect() 75 | 76 | self.assertEqual(sink.events, ["DocumentChange", "DocumentChange"]) 77 | 78 | def test_commandbar(self): 79 | word = self.word 80 | word.Visible = 1 81 | tb = word.CommandBars("Standard") 82 | btn = tb.Controls[1] 83 | 84 | # word does not allow programmatic access, so this does fail 85 | with self.assertRaises(COMError): 86 | word.VBE.Events.CommandBarEvents(btn) 87 | 88 | del word 89 | 90 | 91 | if __name__ == "__main__": 92 | unittest.main() 93 | -------------------------------------------------------------------------------- /source/CppTestSrv/MAKEFILE: -------------------------------------------------------------------------------- 1 | # 2 | # Builds the out-of-proc (local server) version of component. 3 | # Call with: nmake -f makefile. 4 | # 5 | # 6 | !MESSAGE Building local out-of-proc server. 7 | TARGETS = server.exe 8 | 9 | # 10 | # Flags - Always compiles debug. 11 | # 12 | CPP_FLAGS = /c /MTd /Zi /Od /D_DEBUG /DUNICODE /EHsc 13 | EXE_LINK_FLAGS = /DEBUG /NODEFAULTLIB:LIBCMT 14 | 15 | LIBS = kernel32.lib uuid.lib advapi32.lib ole32.lib oleaut32.lib 16 | 17 | ################################################# 18 | # 19 | # Targets 20 | # 21 | 22 | all : $(TARGETS) 23 | 24 | ################################################# 25 | # 26 | # Proxy source files 27 | # 28 | iface.h server.tlb proxy.c guids.c dlldata.c : server.idl 29 | midl /h iface.h /iid guids.c /proxy proxy.c server.idl 30 | 31 | ################################################# 32 | # 33 | # Shared source files 34 | # 35 | 36 | guids.obj : guids.c 37 | cl /c /DWIN32 /DUNICODE /DREGISTER_PROXY_DLL guids.c 38 | 39 | ################################################# 40 | # 41 | # Component/server source files 42 | # 43 | 44 | server.obj : server.cpp cunknown.h cfactory.h iface.h 45 | cl $(CPP_FLAGS) server.cpp 46 | 47 | CoComtypesDispRecordParamTest.obj : CoComtypesDispRecordParamTest.cpp \ 48 | CoComtypesDispRecordParamTest.h iface.h registry.h CUnknown.h 49 | cl $(CPP_FLAGS) CoComtypesDispRecordParamTest.cpp 50 | 51 | CoComtypesDispSafearrayParamTest.obj : CoComtypesDispSafearrayParamTest.cpp \ 52 | CoComtypesDispSafearrayParamTest.h iface.h registry.h CUnknown.h 53 | cl $(CPP_FLAGS) CoComtypesDispSafearrayParamTest.cpp 54 | 55 | # 56 | # Helper classes 57 | # 58 | 59 | CUnknown.obj : CUnknown.cpp CUnknown.h 60 | cl $(CPP_FLAGS) CUnknown.cpp 61 | 62 | CFactory.obj : CFactory.cpp CFactory.h 63 | cl $(CPP_FLAGS) CFactory.cpp 64 | 65 | registry.obj : registry.cpp registry.h 66 | cl $(CPP_FLAGS) registry.cpp 67 | 68 | # util.cpp compiled for server. 69 | util.obj : util.cpp util.h 70 | cl $(CPP_FLAGS) util.cpp 71 | 72 | outproc.obj : outproc.cpp CFactory.h CUnknown.h 73 | cl $(CPP_FLAGS) outproc.cpp 74 | 75 | 76 | ################################################# 77 | # 78 | # Link component - Automatically register component. 79 | # 80 | 81 | SERVER_OBJS = Server.obj \ 82 | CoComtypesDispRecordParamTest.obj \ 83 | CoComtypesDispSafearrayParamTest.obj \ 84 | Registry.obj \ 85 | Cfactory.obj \ 86 | Cunknown.obj \ 87 | Util.obj \ 88 | Guids.obj 89 | 90 | Server.exe: $(SERVER_OBJS) outproc.obj 91 | link $(EXE_LINK_FLAGS) $(SERVER_OBJS) \ 92 | outproc.obj libcmtd.lib \ 93 | libcpmtd.lib $(LIBS) user32.lib gdi32.lib 94 | -------------------------------------------------------------------------------- /comtypes/tools/codegenerator/packing.py: -------------------------------------------------------------------------------- 1 | from comtypes.tools import typedesc 2 | 3 | 4 | def _calc_packing(struct, fields, pack, isStruct): 5 | # Try a certain packing, raise PackingError if field offsets, 6 | # total size ot total alignment is wrong. 7 | if struct.size is None: # incomplete struct 8 | return -1 9 | if struct.name in dont_assert_size: 10 | return None 11 | if struct.bases: 12 | size = struct.bases[0].size 13 | total_align = struct.bases[0].align 14 | else: 15 | size = 0 16 | total_align = 8 # in bits 17 | for i, f in enumerate(fields): 18 | if f.bits: # this code cannot handle bit field sizes. 19 | # print "##XXX FIXME" 20 | return -2 # XXX FIXME 21 | s, a = storage(f.typ) 22 | if pack is not None: 23 | a = min(pack, a) 24 | if size % a: 25 | size += a - size % a 26 | if isStruct: 27 | if size != f.offset: 28 | raise PackingError(f"field {f.name} offset ({size}/{f.offset})") 29 | size += s 30 | else: 31 | size = max(size, s) 32 | total_align = max(total_align, a) 33 | if total_align != struct.align: 34 | raise PackingError(f"total alignment ({total_align}/{struct.align})") 35 | a = total_align 36 | if pack is not None: 37 | a = min(pack, a) 38 | if size % a: 39 | size += a - size % a 40 | if size != struct.size: 41 | raise PackingError(f"total size ({size}/{struct.size})") 42 | 43 | 44 | def calc_packing(struct, fields): 45 | # try several packings, starting with unspecified packing 46 | isStruct = isinstance(struct, typedesc.Structure) 47 | for pack in [None, 16 * 8, 8 * 8, 4 * 8, 2 * 8, 1 * 8]: 48 | try: 49 | _calc_packing(struct, fields, pack, isStruct) 50 | except PackingError as details: 51 | continue 52 | else: 53 | if pack is None: 54 | return None 55 | 56 | return int(pack / 8) 57 | 58 | raise PackingError(f"PACKING FAILED: {details}") 59 | 60 | 61 | class PackingError(Exception): 62 | pass 63 | 64 | 65 | # XXX These should be filtered out in gccxmlparser. 66 | dont_assert_size = set( 67 | [ 68 | "__si_class_type_info_pseudo", 69 | "__class_type_info_pseudo", 70 | ] 71 | ) 72 | 73 | 74 | def storage(t): 75 | # return the size and alignment of a type 76 | if isinstance(t, typedesc.Typedef): 77 | return storage(t.typ) 78 | elif isinstance(t, typedesc.ArrayType): 79 | s, a = storage(t.typ) 80 | return s * (int(t.max) - int(t.min) + 1), a 81 | return int(t.size), int(t.align) 82 | -------------------------------------------------------------------------------- /comtypes/stream.py: -------------------------------------------------------------------------------- 1 | from ctypes import HRESULT, POINTER, c_ubyte, c_ulong, pointer 2 | from typing import TYPE_CHECKING 3 | 4 | from comtypes import COMMETHOD, GUID, IUnknown 5 | 6 | if TYPE_CHECKING: 7 | from ctypes import Array as _CArrayType 8 | 9 | 10 | class ISequentialStream(IUnknown): 11 | """Defines methods for the stream objects in sequence.""" 12 | 13 | _iid_ = GUID("{0C733A30-2A1C-11CE-ADE5-00AA0044773D}") 14 | _idlflags_ = [] 15 | 16 | _methods_ = [ 17 | # Note that these functions are called `Read` and `Write` in Microsoft's documentation, 18 | # see https://learn.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-isequentialstream. 19 | # However, the comtypes code generation detects these as `RemoteRead` and `RemoteWrite` 20 | # for very subtle reasons, see e.g. https://stackoverflow.com/q/19820999/. We will not 21 | # rename these in this manual import for the sake of consistency. 22 | COMMETHOD( 23 | [], 24 | HRESULT, 25 | "RemoteRead", 26 | # This call only works if `pv` is pre-allocated with `cb` bytes, 27 | # which cannot be done by the high level function generated by metaclasses. 28 | # Therefore, we override the high level function to implement this behaviour 29 | # and then delegate the call the raw COM method. 30 | (["out"], POINTER(c_ubyte), "pv"), 31 | (["in"], c_ulong, "cb"), 32 | (["out"], POINTER(c_ulong), "pcbRead"), 33 | ), 34 | COMMETHOD( 35 | [], 36 | HRESULT, 37 | "RemoteWrite", 38 | (["in"], POINTER(c_ubyte), "pv"), 39 | (["in"], c_ulong, "cb"), 40 | (["out"], POINTER(c_ulong), "pcbWritten"), 41 | ), 42 | ] 43 | 44 | def RemoteRead(self, cb: int) -> tuple["_CArrayType[c_ubyte]", int]: 45 | """Reads a specified number of bytes from the stream object into memory 46 | starting at the current seek pointer. 47 | """ 48 | # Behaves as if `pv` is pre-allocated with `cb` bytes by the high level func. 49 | pv = (c_ubyte * cb)() 50 | pcb_read = pointer(c_ulong(0)) 51 | self.__com_RemoteRead(pv, c_ulong(cb), pcb_read) # type: ignore 52 | # return both `out` parameters 53 | return pv, pcb_read.contents.value 54 | 55 | if TYPE_CHECKING: 56 | 57 | def RemoteWrite(self, pv: "_CArrayType[c_ubyte]", cb: int) -> int: 58 | """Writes a specified number of bytes into the stream object starting at 59 | the current seek pointer. 60 | """ 61 | ... 62 | 63 | 64 | # fmt: off 65 | __known_symbols__ = [ 66 | 'ISequentialStream', 67 | ] 68 | # fmt: on 69 | -------------------------------------------------------------------------------- /comtypes/server/__init__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from ctypes import HRESULT, POINTER, byref 3 | from typing import TYPE_CHECKING, Any, Optional 4 | 5 | import comtypes 6 | import comtypes.client 7 | import comtypes.client.dynamic 8 | from comtypes import GUID, STDMETHOD, IUnknown 9 | from comtypes import RevokeActiveObject as RevokeActiveObject 10 | from comtypes.automation import IDispatch 11 | 12 | if TYPE_CHECKING: 13 | from ctypes import _Pointer 14 | 15 | from comtypes import hints # type: ignore 16 | 17 | 18 | ################################################################ 19 | # Interfaces 20 | class IClassFactory(IUnknown): 21 | _iid_ = GUID("{00000001-0000-0000-C000-000000000046}") 22 | _methods_ = [ 23 | STDMETHOD( 24 | HRESULT, 25 | "CreateInstance", 26 | [POINTER(IUnknown), POINTER(GUID), POINTER(ctypes.c_void_p)], 27 | ), 28 | STDMETHOD(HRESULT, "LockServer", [ctypes.c_int]), 29 | ] 30 | 31 | def CreateInstance( 32 | self, 33 | punkouter: Optional[type["_Pointer[IUnknown]"]] = None, 34 | interface: Optional[type[IUnknown]] = None, 35 | dynamic: bool = False, 36 | ) -> Any: 37 | if dynamic: 38 | if interface is not None: 39 | raise ValueError("interface and dynamic are mutually exclusive") 40 | itf = IDispatch 41 | elif interface is None: 42 | itf = IUnknown 43 | else: 44 | itf = interface 45 | obj = POINTER(itf)() 46 | self.__com_CreateInstance(punkouter, itf._iid_, byref(obj)) # type: ignore 47 | if dynamic: 48 | return comtypes.client.dynamic.Dispatch(obj) 49 | elif interface is None: 50 | # An interface was not specified, so return the best. 51 | return comtypes.client.GetBestInterface(obj) 52 | # An interface was specified and obj is already that interface. 53 | return obj 54 | 55 | if TYPE_CHECKING: 56 | 57 | def LockServer(self, fLock: int) -> hints.Hresult: ... 58 | 59 | 60 | # class IExternalConnection(IUnknown): 61 | # _iid_ = GUID("{00000019-0000-0000-C000-000000000046}") 62 | # _methods_ = [ 63 | # STDMETHOD(HRESULT, "AddConnection", [c_ulong, c_ulong]), 64 | # STDMETHOD(HRESULT, "ReleaseConnection", [c_ulong, c_ulong, c_ulong])] 65 | 66 | 67 | def RegisterActiveObject(comobj: comtypes.COMObject, weak: bool = True) -> int: 68 | """Registers a pointer as the active object for its class and returns the handle.""" 69 | punk = comobj._com_pointers_[IUnknown._iid_] 70 | clsid = comobj._reg_clsid_ 71 | flags = comtypes.ACTIVEOBJECT_WEAK if weak else comtypes.ACTIVEOBJECT_STRONG 72 | return comtypes.RegisterActiveObject(punk, clsid, flags) 73 | -------------------------------------------------------------------------------- /comtypes/test/test_QueryService.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import time 3 | import unittest 4 | from ctypes import POINTER 5 | 6 | import comtypes 7 | from comtypes import GUID 8 | from comtypes.client import CreateObject, GetModule 9 | 10 | with contextlib.redirect_stdout(None): # supress warnings 11 | GetModule("mshtml.tlb") 12 | import comtypes.gen.MSHTML as mshtml 13 | 14 | SID_SHTMLEditServices = GUID("{3050F7F9-98B5-11CF-BB82-00AA00BDCE0B}") 15 | 16 | 17 | class TestCase(unittest.TestCase): 18 | def test(self): 19 | doc = CreateObject(mshtml.HTMLDocument, interface=mshtml.IHTMLDocument2) 20 | doc.designMode = "On" 21 | doc.write("
Hello
") 22 | doc.close() 23 | while doc.readyState != "complete": 24 | time.sleep(0.01) 25 | sp = doc.QueryInterface(comtypes.IServiceProvider) 26 | # This behavior is described in Microsoft documentation: 27 | # https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa704048(v=vs.85) 28 | es = sp.QueryService(SID_SHTMLEditServices, mshtml.IHTMLEditServices) 29 | self.assertIsInstance(es, POINTER(mshtml.IHTMLEditServices)) 30 | mc = doc.QueryInterface(mshtml.IMarkupContainer) 31 | ss = es.GetSelectionServices(mc) 32 | # QueryInterface for `IHTMLDocument3` to access `getElementById`. 33 | element = doc.QueryInterface(mshtml.IHTMLDocument3).getElementById("test") 34 | self.assertEqual(element.innerHTML, "Hello") 35 | # MarkupPointer related tests: 36 | ms = doc.QueryInterface(mshtml.IMarkupServices) 37 | p_start = ms.CreateMarkupPointer() 38 | p_end = ms.CreateMarkupPointer() 39 | # QueryInterface for `IHTMLBodyElement` to access `createTextRange`. 40 | rng = doc.body.QueryInterface(mshtml.IHTMLBodyElement).createTextRange() 41 | rng.moveToElementText(element) 42 | ms.MovePointersToRange(rng, p_start, p_end) 43 | self.assertTrue(p_start.IsLeftOf(p_end)) 44 | self.assertTrue(p_end.IsRightOf(p_start)) 45 | self.assertFalse(p_start.IsEqualTo(p_end)) 46 | seg = ss.AddSegment(p_start, p_end) 47 | q_start = ms.CreateMarkupPointer() 48 | q_end = ms.CreateMarkupPointer() 49 | self.assertFalse(p_start.IsEqualTo(q_start)) 50 | self.assertFalse(p_end.IsEqualTo(q_end)) 51 | seg.GetPointers(q_start, q_end) 52 | self.assertTrue(p_start.IsEqualTo(q_start)) 53 | self.assertTrue(p_end.IsEqualTo(q_end)) 54 | ss.RemoveSegment(seg) 55 | # Verify state changes of `p_start` and `p_end`. 56 | p_start.MoveToPointer(p_end) 57 | self.assertTrue(p_start.IsEqualTo(p_end)) 58 | 59 | 60 | if __name__ == "__main__": 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /comtypes/test/test_persist.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import unittest as ut 3 | from _ctypes import COMError 4 | from pathlib import Path 5 | 6 | from comtypes import GUID, CoCreateInstance, IPersist, hresult, persist 7 | from comtypes.automation import VARIANT 8 | 9 | CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") 10 | 11 | 12 | class Test_IPersist(ut.TestCase): 13 | def test_GetClassID(self): 14 | p = CoCreateInstance(CLSID_ShellLink).QueryInterface(IPersist) 15 | self.assertEqual(p.GetClassID(), CLSID_ShellLink) 16 | 17 | 18 | class Test_IPersistFile(ut.TestCase): 19 | def setUp(self): 20 | td = tempfile.TemporaryDirectory() 21 | self.addCleanup(td.cleanup) 22 | self.tmp_dir = Path(td.name) 23 | 24 | def _create_pf(self) -> persist.IPersistFile: 25 | return CoCreateInstance(CLSID_ShellLink).QueryInterface(persist.IPersistFile) 26 | 27 | def test_load(self): 28 | pf = self._create_pf() 29 | tgt_file = (self.tmp_dir / "tgt.txt").resolve() 30 | tgt_file.touch() 31 | pf.Load(str(tgt_file), persist.STGM_DIRECT) 32 | self.assertEqual(pf.GetCurFile(), str(tgt_file)) 33 | 34 | def test_save(self): 35 | pf = self._create_pf() 36 | tgt_file = self.tmp_dir / "tgt.txt" 37 | self.assertFalse(tgt_file.exists()) 38 | pf.Save(str(tgt_file), True) 39 | self.assertEqual(pf.GetCurFile(), str(tgt_file)) 40 | self.assertTrue(tgt_file.exists()) 41 | 42 | 43 | class Test_DictPropertyBag(ut.TestCase): 44 | def create_itf_ptr(self) -> persist.IPropertyBag: 45 | # Create a DictPropertyBag instance with some initial values 46 | impl = persist.DictPropertyBag(Key1="value1", Key2=123, Key3=True) 47 | # Get the IPropertyBag interface pointer 48 | itf = impl.QueryInterface(persist.IPropertyBag) 49 | return itf 50 | 51 | def test_read_existing_properties(self): 52 | itf = self.create_itf_ptr() 53 | self.assertEqual(itf.Read("Key1", VARIANT(), None), "value1") 54 | self.assertEqual(itf.Read("Key2", VARIANT(), None), 123) 55 | self.assertEqual(itf.Read("Key3", VARIANT(), None), True) 56 | 57 | def test_write_new_property(self): 58 | itf = self.create_itf_ptr() 59 | itf.Write("Key4", "new_value") 60 | self.assertEqual(itf.Read("Key4", VARIANT(), None), "new_value") 61 | 62 | def test_update_existing_property(self): 63 | itf = self.create_itf_ptr() 64 | itf.Write("Key1", "updated_value") 65 | self.assertEqual(itf.Read("Key1", VARIANT(), None), "updated_value") 66 | 67 | def test_read_non_existent_property(self): 68 | itf = self.create_itf_ptr() 69 | with self.assertRaises(COMError) as cm: 70 | itf.Read("NonExistentProp", VARIANT(), None) 71 | self.assertEqual(cm.exception.hresult, hresult.E_INVALIDARG) 72 | -------------------------------------------------------------------------------- /comtypes/test/TestComServer.idl: -------------------------------------------------------------------------------- 1 | /* 2 | b30428b4-cc67-48ba-81db-4c6a6b3db1a3 3 | */ 4 | 5 | /* 6 | 7 | REMEMBER TO COMPILE A NEW .TLB FILE WHEN THIS CHANGES, 8 | AND ALSO TO REMOVE THE comtypes\gen DIRECTORY TO DELETE 9 | THE TYPELIB WRAPPERS! 10 | The TestServer.py should also be registered again. 11 | 12 | */ 13 | 14 | import "oaidl.idl"; 15 | import "ocidl.idl"; 16 | 17 | [ 18 | object, 19 | oleautomation, 20 | uuid(f0a241e2-25d1-4f6d-9461-c67bf262779f), 21 | helpstring("A custom event interface") 22 | ] 23 | interface ITestComServerEvents : IUnknown { 24 | [id(10)] 25 | HRESULT EvalStarted([in] BSTR what); 26 | 27 | [id(11)] 28 | HRESULT EvalCompleted([in] BSTR what, [in] VARIANT result); 29 | }; 30 | 31 | [ 32 | object, 33 | /* 34 | the oleautomation flag enables universal marshalling on non-dispatch 35 | interfaces. See Don Box, Page 220. 36 | */ 37 | oleautomation, 38 | uuid(58955c76-60a9-4eeb-8b8a-8f92e90d0fe7), 39 | helpstring("ITestComServer interface") 40 | ] 41 | interface ITestComServer : IDispatch { 42 | [propget, id(10), helpstring("returns the id of the server")] 43 | HRESULT id([out, retval] UINT *pid); 44 | 45 | [propget, id(11), helpstring("the name of the server")] 46 | HRESULT name([out, retval] BSTR *pname); 47 | 48 | [propput, id(11), helpstring("the name of the server")] 49 | HRESULT name([in] BSTR name); 50 | 51 | [id(12), helpstring("a method that receives an BSTR [in] parameter")] 52 | HRESULT SetName([in] BSTR name); 53 | 54 | [id(13), helpstring("evaluate an expression and return the result")] 55 | HRESULT eval([in] BSTR what, [out, retval] VARIANT *presult); 56 | 57 | /* Some methods that use defaultvalues */ 58 | [id(14)] 59 | HRESULT do_cy([in, defaultvalue(32.78)] CURRENCY *value); 60 | 61 | [id(15)] 62 | HRESULT do_date([in, defaultvalue(32)] DATE *value); 63 | 64 | [id(16), helpstring("execute a statement")] 65 | HRESULT Exec([in] BSTR what); 66 | 67 | [id(17), helpstring("execute a statement")] 68 | HRESULT Exec2([in] BSTR what); 69 | 70 | [id(18), helpstring("a method with [in] and [out] args in mixed order")] 71 | HRESULT MixedInOut([in] int a, [out] int *b, [in] int c, [out] int *d); 72 | }; 73 | 74 | [ 75 | uuid(5a3e1d1d-947a-44ac-9b03-5c37d5f5fffc), 76 | version(1.0), 77 | helpstring("TestComServer 1.0 Type library") 78 | ] 79 | library TestComServerLib 80 | { 81 | importlib("stdole2.tlb"); 82 | 83 | typedef 84 | [ 85 | uuid(086b7f11-aed0-4de0-b77a-f1998371da83) 86 | ] 87 | struct MYCOLOR { 88 | double red; 89 | double green; 90 | double blue; 91 | } MYCOLOR; 92 | 93 | [ 94 | uuid(1fca61d1-a1a6-464c-b3a8-e9508b4ac8f7), 95 | helpstring("TestComServer class object") 96 | ] 97 | coclass TestComServer { 98 | [default] interface ITestComServer; 99 | [default, source] interface ITestComServerEvents; 100 | }; 101 | }; 102 | -------------------------------------------------------------------------------- /comtypes/test/test_recordinfo.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import byref, pointer, sizeof 3 | 4 | from comtypes import typeinfo 5 | from comtypes.client import GetModule 6 | 7 | ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" 8 | 9 | try: 10 | GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0)) 11 | from comtypes.gen.ComtypesCppTestSrvLib import StructRecordParamTest 12 | 13 | IMPORT_FAILED = False 14 | except (ImportError, OSError): 15 | IMPORT_FAILED = True 16 | 17 | 18 | def _create_recordinfo() -> typeinfo.IRecordInfo: 19 | return typeinfo.GetRecordInfoFromGuids(*StructRecordParamTest._recordinfo_) 20 | 21 | 22 | def _create_record( 23 | question: str, answer: int, needs_clarification: bool 24 | ) -> "StructRecordParamTest": 25 | record = StructRecordParamTest() 26 | record.question = question 27 | record.answer = answer 28 | record.needs_clarification = needs_clarification 29 | return record 30 | 31 | 32 | @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") 33 | class Test_IRecordInfo(unittest.TestCase): 34 | def test_RecordCopy(self): 35 | dst_rec = StructRecordParamTest() 36 | ri = _create_recordinfo() 37 | ri.RecordCopy(pointer(_create_record("foo", 3, True)), byref(dst_rec)) 38 | self.assertEqual(dst_rec.question, "foo") 39 | self.assertEqual(dst_rec.answer, 3) 40 | self.assertEqual(dst_rec.needs_clarification, True) 41 | 42 | def test_GetGuid(self): 43 | *_, expected_guid = StructRecordParamTest._recordinfo_ 44 | self.assertEqual(str(_create_recordinfo().GetGuid()), expected_guid) 45 | 46 | def test_GetName(self): 47 | self.assertEqual( 48 | _create_recordinfo().GetName(), 49 | StructRecordParamTest.__qualname__, 50 | ) 51 | 52 | def test_GetSize(self): 53 | self.assertEqual( 54 | _create_recordinfo().GetSize(), 55 | sizeof(StructRecordParamTest), 56 | ) 57 | 58 | def test_GetTypeInfo(self): 59 | ta = _create_recordinfo().GetTypeInfo().GetTypeAttr() 60 | *_, expected_guid = StructRecordParamTest._recordinfo_ 61 | self.assertEqual(str(ta.guid), expected_guid) 62 | 63 | def test_IsMatchingType(self): 64 | ri = _create_recordinfo() 65 | ti = ri.GetTypeInfo() 66 | self.assertTrue(typeinfo.GetRecordInfoFromTypeInfo(ti).IsMatchingType(ri)) 67 | 68 | def test_RecordCreateCopy(self): 69 | ri = _create_recordinfo() 70 | actual = ri.RecordCreateCopy(byref(_create_record("foo", 3, True))) 71 | self.assertIsInstance(actual, int) 72 | dst_rec = StructRecordParamTest() 73 | ri.RecordCopy(actual, byref(dst_rec)) 74 | ri.RecordDestroy(actual) 75 | self.assertEqual(dst_rec.question, "foo") 76 | self.assertEqual(dst_rec.answer, 3) 77 | self.assertEqual(dst_rec.needs_clarification, True) 78 | -------------------------------------------------------------------------------- /comtypes/git.py: -------------------------------------------------------------------------------- 1 | """comtypes.git - access the process wide global interface table 2 | 3 | The global interface table provides a way to marshal interface pointers 4 | between different threading appartments. 5 | """ 6 | 7 | from ctypes import * 8 | from ctypes.wintypes import DWORD 9 | 10 | from comtypes import ( 11 | CLSCTX_INPROC_SERVER, 12 | COMMETHOD, 13 | GUID, 14 | HRESULT, 15 | STDMETHOD, 16 | CoCreateInstance, 17 | IUnknown, 18 | ) 19 | 20 | 21 | class IGlobalInterfaceTable(IUnknown): 22 | _iid_ = GUID("{00000146-0000-0000-C000-000000000046}") 23 | _methods_ = [ 24 | STDMETHOD( 25 | HRESULT, 26 | "RegisterInterfaceInGlobal", 27 | [POINTER(IUnknown), POINTER(GUID), POINTER(DWORD)], 28 | ), 29 | STDMETHOD(HRESULT, "RevokeInterfaceFromGlobal", [DWORD]), 30 | STDMETHOD( 31 | HRESULT, 32 | "GetInterfaceFromGlobal", 33 | [DWORD, POINTER(GUID), POINTER(POINTER(IUnknown))], 34 | ), 35 | ] 36 | 37 | def RegisterInterfaceInGlobal(self, obj, interface=IUnknown): 38 | cookie = DWORD() 39 | self.__com_RegisterInterfaceInGlobal(obj, interface._iid_, cookie) 40 | return cookie.value 41 | 42 | def GetInterfaceFromGlobal(self, cookie, interface=IUnknown): 43 | ptr = POINTER(interface)() 44 | self.__com_GetInterfaceFromGlobal(cookie, interface._iid_, ptr) 45 | return ptr 46 | 47 | def RevokeInterfaceFromGlobal(self, cookie): 48 | self.__com_RevokeInterfaceFromGlobal(cookie) 49 | 50 | 51 | # It was a pain to get this CLSID: it's neither in the registry, nor 52 | # in any header files. I had to compile a C program, and find it out 53 | # with the debugger. Apparently it is in uuid.lib. 54 | CLSID_StdGlobalInterfaceTable = GUID("{00000323-0000-0000-C000-000000000046}") 55 | 56 | git = CoCreateInstance( 57 | CLSID_StdGlobalInterfaceTable, 58 | interface=IGlobalInterfaceTable, 59 | clsctx=CLSCTX_INPROC_SERVER, 60 | ) 61 | 62 | RevokeInterfaceFromGlobal = git.RevokeInterfaceFromGlobal 63 | RegisterInterfaceInGlobal = git.RegisterInterfaceInGlobal 64 | GetInterfaceFromGlobal = git.GetInterfaceFromGlobal 65 | 66 | # fmt: off 67 | __all__ = [ 68 | "RegisterInterfaceInGlobal", "RevokeInterfaceFromGlobal", 69 | "GetInterfaceFromGlobal", 70 | ] 71 | # fmt: on 72 | 73 | 74 | if __name__ == "__main__": 75 | from comtypes.typeinfo import CreateTypeLib, ICreateTypeLib 76 | 77 | tlib = CreateTypeLib("foo.bar") # we don not save it later 78 | assert (tlib.AddRef(), tlib.Release()) == (2, 1) 79 | 80 | cookie = RegisterInterfaceInGlobal(tlib) 81 | assert (tlib.AddRef(), tlib.Release()) == (3, 2) 82 | 83 | GetInterfaceFromGlobal(cookie, ICreateTypeLib) 84 | GetInterfaceFromGlobal(cookie, ICreateTypeLib) 85 | GetInterfaceFromGlobal(cookie) 86 | assert (tlib.AddRef(), tlib.Release()) == (3, 2) 87 | RevokeInterfaceFromGlobal(cookie) 88 | assert (tlib.AddRef(), tlib.Release()) == (2, 1) 89 | -------------------------------------------------------------------------------- /comtypes/test/mytypelib.idl: -------------------------------------------------------------------------------- 1 | import "oaidl.idl"; 2 | import "ocidl.idl"; 3 | 4 | typedef 5 | [ 6 | uuid(0a411e93-aeb0-4b84-8722-b237a1b87ba1) 7 | ] 8 | struct Pair { 9 | double a; 10 | double b; 11 | } Pair; 12 | 13 | typedef 14 | [ 15 | uuid(00b7e135-f7a3-42f8-b65b-ecd106b3c17d) 16 | ] 17 | struct Point { 18 | double x; 19 | double y; 20 | } Point; 21 | 22 | [ 23 | object, 24 | /* 25 | the oleautomation flag enables universal marshalling on non-dispatch 26 | interfaces. See Don Box, Page 220. 27 | */ 28 | oleautomation, 29 | uuid(368ce4db-5f87-4927-b134-2a955c1dea1f), 30 | ] 31 | interface IMyInterface : IUnknown { 32 | [propget, id(10), helpstring("returns the id of the server")] 33 | HRESULT id([out, retval] UINT *pid); 34 | 35 | [propget, id(11), helpstring("the name of the server")] 36 | HRESULT name([out, retval] BSTR *pname); 37 | 38 | [propput, id(11), helpstring("the name of the server")] 39 | HRESULT name([in] BSTR name); 40 | 41 | [id(12), helpstring("a method that receives an BSTR [in] parameter")] 42 | HRESULT SetName([in] BSTR name); 43 | 44 | [id(13), helpstring("evaluate an expression and return the result")] 45 | HRESULT eval([in] BSTR what, [out, retval] VARIANT *presult); 46 | 47 | /* Some methods that use defaultvalues */ 48 | [id(14)] 49 | HRESULT do_cy([in, defaultvalue(32.78)] CURRENCY *value); 50 | 51 | [id(15)] 52 | HRESULT do_date([in, defaultvalue(32)] DATE *value); 53 | 54 | [id(16), helpstring("execute a statement")] 55 | HRESULT Exec([in] BSTR what); 56 | 57 | [helpstring("execute a statement")] 58 | HRESULT Exec2([in] BSTR what); 59 | 60 | [helpstring("a method with [in] and [out] args in mixed order")] 61 | HRESULT MixedInOut([in] int a, [out] int *b, [in] int c, [out] int *d); 62 | 63 | [helpstring("a method that receives and returns SAFEARRAYs of pairs")] 64 | HRESULT TestPairArray([in] SAFEARRAY(Pair) val, [out, retval] SAFEARRAY(Pair) *result); 65 | 66 | [helpstring("a method that receives and returns SAFEARRAYs of pairs")] 67 | HRESULT TestPairArray2([in] SAFEARRAY(Pair) val, [out, retval] SAFEARRAY(Pair) *result); 68 | 69 | [helpstring("a method that receives and returns SAFEARRAYs of points")] 70 | HRESULT TestPointArray([in] SAFEARRAY(Point) val, [out, retval] SAFEARRAY(Point) *result); 71 | 72 | [local, helpstring("...")] 73 | LONG Test([in] int value, [out, retval] int *result); 74 | 75 | HRESULT MultiInOutArgs([in, out] int *pa, 76 | [in, out] int *pb, 77 | [in, out] int *pc); 78 | 79 | HRESULT MultiOutArgs2([in, out] int *pa, 80 | [in, out] int *pb, 81 | [out, retval] int *pc); 82 | 83 | }; 84 | 85 | [ 86 | uuid(6a237363-015c-4ded-937e-7e4d80b0a6cf), 87 | version(1.0), 88 | ] 89 | library MyTypeLib 90 | { 91 | importlib("stdole2.tlb"); 92 | 93 | [ 94 | uuid(08420058-ef6b-4884-9c78-14e73dfaf767), 95 | ] 96 | coclass MyComServer { 97 | [default] interface IMyInterface; 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /comtypes/test/test_getactiveobj.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import time 3 | import unittest 4 | 5 | import comtypes 6 | import comtypes.client 7 | 8 | with contextlib.redirect_stdout(None): # supress warnings 9 | comtypes.client.GetModule("msvidctl.dll") 10 | from comtypes.gen import MSVidCtlLib as msvidctl 11 | 12 | try: 13 | # pass Word libUUID 14 | comtypes.client.GetModule(("{00020905-0000-0000-C000-000000000046}",)) 15 | IMPORT_WORD_FAILED = False 16 | except (ImportError, OSError): 17 | IMPORT_WORD_FAILED = True 18 | 19 | 20 | ################################################################ 21 | # 22 | # TODO: 23 | # 24 | # It seems bad that only external test like this 25 | # can verify the behavior of `comtypes` implementation. 26 | # Find a different built-in win32 API to use. 27 | # 28 | ################################################################ 29 | 30 | 31 | @unittest.skipIf(IMPORT_WORD_FAILED, "This depends on Word.") 32 | class Test_Word(unittest.TestCase): 33 | def setUp(self): 34 | try: 35 | comtypes.client.GetActiveObject("Word.Application") 36 | except OSError: 37 | pass 38 | else: 39 | # seems word is running, we cannot test this. 40 | self.fail("MSWord is running, cannot test") 41 | # create a WORD instance 42 | self.w1 = comtypes.client.CreateObject("Word.Application") 43 | 44 | def tearDown(self): 45 | if hasattr(self, "w1"): 46 | self.w1.Quit() 47 | del self.w1 48 | 49 | def test(self): 50 | # connect to the running instance 51 | w1 = self.w1 52 | w2 = comtypes.client.GetActiveObject("Word.Application") 53 | 54 | # check if they are referring to the same object 55 | self.assertEqual( 56 | w1.QueryInterface(comtypes.IUnknown), w2.QueryInterface(comtypes.IUnknown) 57 | ) 58 | 59 | w1.Quit() 60 | del self.w1 61 | 62 | time.sleep(1) 63 | 64 | with self.assertRaises(comtypes.COMError) as arc: 65 | w2.Visible 66 | 67 | err = arc.exception 68 | variables = err.hresult, err.text, err.details 69 | self.assertEqual(variables, err.args) 70 | with self.assertRaises(WindowsError): 71 | comtypes.client.GetActiveObject("Word.Application") 72 | 73 | 74 | class Test_MSVidCtlLib(unittest.TestCase): 75 | def test_register_and_revoke(self): 76 | vidctl = comtypes.client.CreateObject(msvidctl.MSVidCtl) 77 | with self.assertRaises(WindowsError): 78 | comtypes.client.GetActiveObject(msvidctl.MSVidCtl) 79 | handle = comtypes.client.RegisterActiveObject(vidctl, msvidctl.MSVidCtl) 80 | activeobj = comtypes.client.GetActiveObject(msvidctl.MSVidCtl) 81 | self.assertEqual(vidctl, activeobj) 82 | comtypes.client.RevokeActiveObject(handle) 83 | with self.assertRaises(WindowsError): 84 | comtypes.client.GetActiveObject(msvidctl.MSVidCtl) 85 | 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /comtypes/test/test_viewobject.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import unittest 3 | from ctypes.wintypes import POINT, RECT, SIZEL 4 | 5 | import comtypes.client 6 | from comtypes import IUnknown 7 | from comtypes.viewobject import ( 8 | DVASPECT_CONTENT, 9 | IAdviseSink, 10 | IViewObject, 11 | IViewObject2, 12 | IViewObjectEx, 13 | ) 14 | 15 | with contextlib.redirect_stdout(None): # supress warnings 16 | comtypes.client.GetModule("mshtml.tlb") 17 | 18 | import comtypes.gen.MSHTML as mshtml 19 | 20 | 21 | def create_html_document() -> IUnknown: 22 | return comtypes.client.CreateObject(mshtml.HTMLDocument) 23 | 24 | 25 | class Test_IViewObject(unittest.TestCase): 26 | def test_Advise_GetAdvise(self): 27 | vo = create_html_document().QueryInterface(IViewObject) 28 | # Test that we can clear any existing advise connection. 29 | vo.SetAdvise(DVASPECT_CONTENT, 0, None) 30 | # Verify that no advise connection is present. 31 | aspect, advf, sink = vo.GetAdvise() 32 | self.assertIsInstance(aspect, int) 33 | self.assertIsInstance(advf, int) 34 | self.assertIsInstance(sink, IAdviseSink) 35 | self.assertFalse(sink) # A NULL com pointer evaluates to False. 36 | 37 | def test_Freeze_Unfreeze(self): 38 | vo = create_html_document().QueryInterface(IViewObject) 39 | cookie = vo.Freeze(DVASPECT_CONTENT, -1, None) 40 | self.assertIsInstance(cookie, int) 41 | vo.Unfreeze(cookie) 42 | 43 | 44 | class Test_IViewObject2(unittest.TestCase): 45 | def test_GetExtent(self): 46 | vo = create_html_document().QueryInterface(IViewObject2) 47 | size = vo.GetExtent(DVASPECT_CONTENT, -1, None) 48 | self.assertTrue(size) 49 | self.assertIsInstance(size, SIZEL) 50 | 51 | 52 | class Test_IViewObjectEx(unittest.TestCase): 53 | def test_GetRect(self): 54 | vo = create_html_document().QueryInterface(IViewObjectEx) 55 | rect = vo.GetRect(DVASPECT_CONTENT) 56 | self.assertTrue(rect) 57 | self.assertIsInstance(rect, RECT) 58 | 59 | def test_GetViewStatus(self): 60 | vo = create_html_document().QueryInterface(IViewObjectEx) 61 | status = vo.GetViewStatus() 62 | self.assertIsInstance(status, int) 63 | 64 | def test_QueryHitPoint(self): 65 | vo = create_html_document().QueryInterface(IViewObjectEx) 66 | # It is assumed that the view is not transparent at the origin. 67 | bounds = RECT(left=0, top=0, right=100, bottom=100) 68 | loc = POINT(x=0, y=0) 69 | hit = vo.QueryHitPoint(DVASPECT_CONTENT, bounds, loc, 0) 70 | self.assertIsInstance(hit, int) 71 | 72 | def test_QueryHitRect(self): 73 | vo = create_html_document().QueryInterface(IViewObjectEx) 74 | # It is assumed that the view is not transparent at the origin. 75 | bounds = RECT(left=0, top=0, right=100, bottom=100) 76 | loc = RECT(left=0, top=0, right=1, bottom=1) 77 | hit = vo.QueryHitRect(DVASPECT_CONTENT, bounds, loc, 0) 78 | self.assertIsInstance(hit, int) 79 | -------------------------------------------------------------------------------- /comtypes/test/test_msscript.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import POINTER 3 | 4 | from comtypes import GUID 5 | from comtypes.automation import IDispatch 6 | from comtypes.client import CreateObject 7 | 8 | ##from test import test_support 9 | ##from comtypes.unittests import support 10 | 11 | try: 12 | GUID.from_progid("MSScriptControl.ScriptControl") 13 | CreateObject("MSScriptControl.ScriptControl") 14 | except OSError: 15 | # doesn't exist on Windows CE or in 64-bit. 16 | pass 17 | else: 18 | 19 | class Test(unittest.TestCase): 20 | def test_jscript(self): 21 | engine = CreateObject("MSScriptControl.ScriptControl") 22 | engine.Language = "JScript" 23 | # strange. 24 | # 25 | # engine.Eval returns a VARIANT containing a dispatch pointer. 26 | # 27 | # The dispatch pointer exposes this typeinfo (the number of 28 | # dispproperties varies, depending on the length of the list we pass 29 | # to Eval): 30 | # 31 | # class JScriptTypeInfo(comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0.IDispatch): 32 | # 'JScript Type Info' 33 | # _iid_ = GUID('{C59C6B12-F6C1-11CF-8835-00A0C911E8B2}') 34 | # _idlflags_ = [] 35 | # _methods_ = [] 36 | # JScriptTypeInfo._disp_methods_ = [ 37 | # DISPPROPERTY([dispid(9522932)], VARIANT, '0'), 38 | # DISPPROPERTY([dispid(9522976)], VARIANT, '1'), 39 | # ] 40 | # 41 | # Although the exact interface members vary, the guid stays 42 | # the same. Don't think that's allowed by COM standards - is 43 | # this a bug in the MSScriptControl? 44 | # 45 | # What's even more strange is that the returned dispatch 46 | # pointer can't be QI'd for this interface! So it seems the 47 | # typeinfo is really a temporary thing. 48 | 49 | res = engine.Eval("[1, 2, 3, 4]")._comobj 50 | 51 | # comtypes.client works around this bug, by not trying to 52 | # high-level wrap the dispatch pointer because QI for the real 53 | # interface fails. 54 | self.assertEqual(type(res), POINTER(IDispatch)) 55 | 56 | tinfo_1 = engine.Eval("[1, 2, 3]")._comobj.GetTypeInfo(0) 57 | tinfo_2 = engine.Eval("[1, 2, 3, 4]")._comobj.GetTypeInfo(0) 58 | tinfo_3 = engine.Eval("[1, 2, 3, 4, 5]")._comobj.GetTypeInfo(0) 59 | 60 | self.assertEqual(tinfo_1.GetTypeAttr().cVars, 3) 61 | self.assertEqual(tinfo_2.GetTypeAttr().cVars, 4) 62 | self.assertEqual(tinfo_3.GetTypeAttr().cVars, 5) 63 | 64 | # These tests simply describe the current behaviour ;-) 65 | self.assertEqual(tinfo_1.GetTypeAttr().guid, tinfo_1.GetTypeAttr().guid) 66 | 67 | ## print (res[0], res[1], res[2]) 68 | ## print len(res) 69 | 70 | engine.Reset() 71 | 72 | 73 | if __name__ == "__main__": 74 | unittest.main() 75 | -------------------------------------------------------------------------------- /comtypes/test/test_client_dynamic.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import unittest as ut 3 | from unittest import mock 4 | 5 | from comtypes import COMError, IUnknown, automation 6 | from comtypes.client import CreateObject, GetModule, dynamic, lazybind 7 | 8 | 9 | class Test_Dispatch_Function(ut.TestCase): 10 | # It is difficult to cause intentionally errors "in the regular way". 11 | # So `mock` is used to cover conditional branches. 12 | def test_returns_dynamic_Dispatch_if_takes_dynamic_Dispatch(self): 13 | obj = mock.MagicMock(spec=dynamic._Dispatch) 14 | self.assertIs(dynamic.Dispatch(obj), obj) 15 | 16 | def test_returns_lazybind_Dispatch_if_takes_ptrIDispatch(self): 17 | # Conditional branches that return `lazybind.Dispatch` are also covered by 18 | # `test_dyndispatch` and others. 19 | obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) 20 | self.assertIsInstance(dynamic.Dispatch(obj), lazybind.Dispatch) 21 | 22 | def test_returns_dynamic_Dispatch_if_takes_ptrIDispatch_and_raised_comerr(self): 23 | obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) 24 | obj.GetTypeInfo.side_effect = COMError(0, "test", ("", "", "", 0, 0)) 25 | self.assertIsInstance(dynamic.Dispatch(obj), dynamic._Dispatch) 26 | 27 | def test_returns_dynamic_Dispatch_if_takes_ptrIDispatch_and_raised_winerr(self): 28 | obj = mock.MagicMock(spec=ctypes.POINTER(automation.IDispatch)) 29 | obj.GetTypeInfo.side_effect = OSError() 30 | self.assertIsInstance(dynamic.Dispatch(obj), dynamic._Dispatch) 31 | 32 | def test_returns_what_is_took_if_takes_other(self): 33 | obj = object() 34 | self.assertIs(dynamic.Dispatch(obj), obj) 35 | 36 | 37 | class Test_Dispatch_Class(ut.TestCase): 38 | # `MethodCaller` and `_Collection` are indirectly covered in this. 39 | def test_dict(self): 40 | # The following conditional branches are not covered; 41 | # - not `hresult in ERRORS_BAD_CONTEXT` 42 | # - not `0 != enum.Skip(index)` 43 | # - other than `COMError` raises in `__getattr__` 44 | orig = CreateObject("Scripting.Dictionary", interface=automation.IDispatch) 45 | d = dynamic._Dispatch(orig) 46 | d.CompareMode = 42 47 | d.Item["foo"] = 1 48 | d.Item["bar"] = "spam foo" 49 | d.Item["baz"] = 3.14 50 | self.assertEqual(d[0], "foo") 51 | self.assertEqual(d.Item["foo"], 1) 52 | self.assertEqual([k for k in iter(d)], ["foo", "bar", "baz"]) 53 | self.assertIsInstance(hash(d), int) 54 | d._FlagAsMethod("_NewEnum") 55 | self.assertIs(type(d._NewEnum()), ctypes.POINTER(IUnknown)) 56 | scrrun = GetModule("scrrun.dll") 57 | scr_dict = d.QueryInterface(scrrun.IDictionary) 58 | self.assertIsInstance(scr_dict, scrrun.IDictionary) 59 | d.Item["qux"] = scr_dict 60 | with self.assertRaises(IndexError): 61 | d[4] 62 | with self.assertRaises(AttributeError): 63 | d.__foo__ 64 | 65 | 66 | if __name__ == "__main__": 67 | ut.main() 68 | -------------------------------------------------------------------------------- /.github/workflows/autotest.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: [main] 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | unit-tests: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [windows-latest] 13 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] 14 | architecture: ['x86', 'x64'] 15 | support: ['with 3rd parties', 'without 3rd parties'] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | architecture: ${{ matrix.architecture }} 23 | - name: Set up MSVC 24 | uses: ilammy/msvc-dev-cmd@v1 25 | - name: Build and register the OutProc COM server 26 | run: | 27 | cd source/CppTestSrv 28 | nmake /f Makefile 29 | ./server.exe /RegServer 30 | - name: unittest comtypes 31 | run: | 32 | if ("${{ matrix.support }}" -eq "with 3rd parties") { 33 | pip install numpy 34 | pip install pywin32 35 | } 36 | pip install coverage[toml] 37 | coverage run -m unittest discover -v -s comtypes\test -t comtypes\test 38 | - name: Upload coverage reports to Codecov 39 | uses: codecov/codecov-action@v5 40 | with: 41 | fail_ci_if_error: true 42 | network_filter: comtype 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | - name: Unregister the OutProc COM server 45 | run: | 46 | cd source/CppTestSrv 47 | ./server.exe /UnregServer 48 | 49 | install-tests: 50 | runs-on: ${{ matrix.os }} 51 | strategy: 52 | matrix: 53 | os: [windows-2025, windows-2022] 54 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] 55 | architecture: ['x86', 'x64'] 56 | steps: 57 | - uses: actions/checkout@v4 58 | - name: Set up Python 59 | uses: actions/setup-python@v5 60 | with: 61 | python-version: ${{ matrix.python-version }} 62 | architecture: ${{ matrix.architecture }} 63 | - name: install comtypes 64 | run: | 65 | pip install --upgrade build 66 | python -m pip install . 67 | pip uninstall comtypes -y 68 | python test_pip_install.py 69 | 70 | docs-source-doctest: 71 | runs-on: windows-latest 72 | steps: 73 | - uses: actions/checkout@v4 74 | - name: Set up Python 75 | uses: actions/setup-python@v5 76 | with: 77 | python-version: "3.13" 78 | architecture: x64 79 | - name: Install dependencies 80 | run: | 81 | python -m pip install --upgrade pip 82 | pip install -r docs/requirements.txt 83 | - name: Set up MSVC 84 | uses: ilammy/msvc-dev-cmd@v1 85 | - name: Compile IDL 86 | run: midl /out docs\source docs\source\mytypelib.idl 87 | - name: Run doctest 88 | run: sphinx-build -b doctest -d docs/build/doctrees docs/source docs/build/doctest 89 | working-directory: ./ 90 | -------------------------------------------------------------------------------- /comtypes/server/automation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ctypes import * 3 | 4 | from comtypes import COMObject, IUnknown 5 | from comtypes.automation import IDispatch, IEnumVARIANT 6 | from comtypes.hresult import * 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | # XXX When the COMCollection class is ready, insert it into __all__ 11 | __all__ = ["VARIANTEnumerator"] 12 | 13 | 14 | class VARIANTEnumerator(COMObject): 15 | """A universal VARIANTEnumerator class. Instantiate it with a 16 | collection of items that support the IDispatch interface.""" 17 | 18 | _com_interfaces_ = [IEnumVARIANT] 19 | 20 | def __init__(self, items): 21 | self.items = ( 22 | items # keep, so that we can restore our iterator (in Reset, and Clone). 23 | ) 24 | self.seq = iter(self.items) 25 | super().__init__() 26 | 27 | def Next(self, this, celt, rgVar, pCeltFetched): 28 | if not rgVar: 29 | return E_POINTER 30 | if not pCeltFetched: 31 | pCeltFetched = [None] 32 | pCeltFetched[0] = 0 33 | try: 34 | for index in range(celt): 35 | item = next(self.seq) 36 | p = item.QueryInterface(IDispatch) 37 | rgVar[index].value = p 38 | pCeltFetched[0] += 1 39 | except StopIteration: 40 | pass 41 | # except: 42 | # # ReportException? return E_FAIL? 43 | # import traceback 44 | # traceback.print_exc() 45 | 46 | if pCeltFetched[0] == celt: 47 | return S_OK 48 | return S_FALSE 49 | 50 | def Skip(self, this, celt): 51 | # skip some elements. 52 | try: 53 | for _ in range(celt): 54 | next(self.seq) 55 | except StopIteration: 56 | return S_FALSE 57 | return S_OK 58 | 59 | def Reset(self, this): 60 | self.seq = iter(self.items) 61 | return S_OK 62 | 63 | # Clone not implemented 64 | 65 | 66 | ################################################################ 67 | 68 | # XXX Shouldn't this be a mixin class? 69 | # And isn't this class borked anyway? 70 | 71 | 72 | class COMCollection(COMObject): 73 | """Abstract base class which implements Count, Item, and _NewEnum.""" 74 | 75 | def __init__(self, itemtype, collection): 76 | self.collection = collection 77 | self.itemtype = itemtype 78 | super().__init__() 79 | 80 | def _get_Item(self, this, pathname, pitem): 81 | if not pitem: 82 | return E_POINTER 83 | item = self.itemtype(pathname) 84 | return item.IUnknown_QueryInterface(None, pointer(pitem[0]._iid_), pitem) 85 | 86 | def _get_Count(self, this, pcount): 87 | if not pcount: 88 | return E_POINTER 89 | pcount[0] = len(self.collection) 90 | return S_OK 91 | 92 | def _get__NewEnum(self, this, penum): 93 | if not penum: 94 | return E_POINTER 95 | enum = VARIANTEnumerator(self.itemtype, self.collection) 96 | return enum.IUnknown_QueryInterface(None, pointer(IUnknown._iid_), penum) 97 | -------------------------------------------------------------------------------- /source/CppTestSrv/CUNKNOWN.CPP: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | /////////////////////////////////////////////////////////// 10 | // 11 | // CUnknown.cpp 12 | // 13 | // Implementation of IUnknown Base class 14 | // 15 | #include "CUnknown.h" 16 | #include "CFactory.h" 17 | #include "Util.h" 18 | 19 | static inline void trace(char* msg) 20 | {Util::Trace("CUnknown", msg, S_OK) ;} 21 | static inline void trace(char* msg, HRESULT hr) 22 | {Util::Trace("CUnknown", msg, hr) ;} 23 | 24 | /////////////////////////////////////////////////////////// 25 | // 26 | // Count of active objects 27 | // - Use to determine if we can unload the DLL. 28 | // 29 | long CUnknown::s_cActiveComponents = 0 ; 30 | 31 | 32 | /////////////////////////////////////////////////////////// 33 | // 34 | // Constructor 35 | // 36 | CUnknown::CUnknown(IUnknown* pUnknownOuter) 37 | : m_cRef(1) 38 | { 39 | // Set m_pUnknownOuter pointer. 40 | if (pUnknownOuter == NULL) 41 | { 42 | trace("Not aggregating; delegate to nondelegating IUnknown.") ; 43 | m_pUnknownOuter = reinterpret_cast 44 | (static_cast 45 | (this)) ; // notice cast 46 | } 47 | else 48 | { 49 | trace("Aggregating; delegate to outer IUnknown.") ; 50 | m_pUnknownOuter = pUnknownOuter ; 51 | } 52 | 53 | // Increment count of active components. 54 | ::InterlockedIncrement(&s_cActiveComponents) ; 55 | } 56 | 57 | // 58 | // Destructor 59 | // 60 | CUnknown::~CUnknown() 61 | { 62 | ::InterlockedDecrement(&s_cActiveComponents) ; 63 | 64 | // If this is an EXE server, shut it down. 65 | CFactory::CloseExe() ; 66 | } 67 | 68 | // 69 | // FinalRelease - called by Release before it deletes the component 70 | // 71 | void CUnknown::FinalRelease() 72 | { 73 | trace("Increment reference count for final release.") ; 74 | m_cRef = 1 ; 75 | } 76 | 77 | // 78 | // Nondelegating IUnknown 79 | // - Override to handle custom interfaces. 80 | // 81 | HRESULT __stdcall 82 | CUnknown::NondelegatingQueryInterface(const IID& iid, void** ppv) 83 | { 84 | // CUnknown supports only IUnknown. 85 | if (iid == IID_IUnknown) 86 | { 87 | return FinishQI(reinterpret_cast 88 | (static_cast(this)), 89 | ppv) ; 90 | } 91 | else 92 | { 93 | *ppv = NULL ; 94 | return E_NOINTERFACE ; 95 | } 96 | } 97 | 98 | // 99 | // AddRef 100 | // 101 | ULONG __stdcall CUnknown::NondelegatingAddRef() 102 | { 103 | return InterlockedIncrement(&m_cRef) ; 104 | } 105 | 106 | // 107 | // Release 108 | // 109 | ULONG __stdcall CUnknown::NondelegatingRelease() 110 | { 111 | InterlockedDecrement(&m_cRef) ; 112 | if (m_cRef == 0) 113 | { 114 | FinalRelease() ; 115 | delete this ; 116 | return 0 ; 117 | } 118 | return m_cRef ; 119 | } 120 | 121 | // 122 | // FinishQI 123 | // - Helper function to simplify overriding 124 | // NondelegatingQueryInterface 125 | // 126 | HRESULT CUnknown::FinishQI(IUnknown* pI, void** ppv) 127 | { 128 | *ppv = pI ; 129 | pI->AddRef() ; 130 | return S_OK ; 131 | } 132 | -------------------------------------------------------------------------------- /comtypes/test/test_findgendir.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import importlib 3 | import os 4 | import sys 5 | import tempfile 6 | import types 7 | import unittest 8 | from collections.abc import Iterator 9 | from unittest import mock 10 | 11 | import comtypes 12 | import comtypes.client 13 | import comtypes.gen 14 | 15 | IMGBASE = os.path.splitext(os.path.basename(sys.executable))[0] 16 | 17 | 18 | @contextlib.contextmanager 19 | def patch_empty_module_to_comtypes_gen() -> Iterator[types.ModuleType]: 20 | with mock.patch.dict(sys.modules): 21 | del sys.modules["comtypes.gen"] 22 | mod = sys.modules["comtypes.gen"] = types.ModuleType("comtypes.gen") 23 | mod.__path__ = [] 24 | with mock.patch.object(comtypes, "gen", mod): 25 | try: 26 | yield mod 27 | finally: 28 | importlib.reload(comtypes.gen) 29 | 30 | 31 | class Test(unittest.TestCase): 32 | """Test the comtypes.client._find_gen_dir() function in several 33 | simulated environments. 34 | """ 35 | 36 | def test_script(self): 37 | # %APPDATA%\Python\Python25\comtypes_cache 38 | ma, mi = sys.version_info[:2] 39 | cache = rf"$APPDATA\Python\Python{ma:d}{mi:d}\comtypes_cache" 40 | path = os.path.expandvars(cache) 41 | with patch_empty_module_to_comtypes_gen(): 42 | gen_dir = comtypes.client._find_gen_dir() 43 | self.assertEqual(path, gen_dir) 44 | 45 | # patch py2exe-attributes to `sys` modules 46 | @mock.patch.object(sys, "frozen", "dll", create=True) 47 | @mock.patch.object(sys, "frozendllhandle", sys.dllhandle, create=True) 48 | def test_frozen_dll(self): 49 | # %TEMP%\comtypes_cache\25-25 50 | # the image is python25.dll 51 | ma, mi = sys.version_info[:2] 52 | cache = rf"comtypes_cache\{IMGBASE}{ma:d}{mi:d}-{ma:d}{mi:d}" 53 | path = os.path.join(tempfile.gettempdir(), cache) 54 | with patch_empty_module_to_comtypes_gen(): 55 | gen_dir = comtypes.client._find_gen_dir() 56 | self.assertEqual(path, gen_dir) 57 | 58 | # patch py2exe-attributes to `sys` modules 59 | @mock.patch.object(sys, "frozen", "console_exe", create=True) 60 | def test_frozen_console_exe(self): 61 | # %TEMP%\comtypes_cache\-25 62 | ma, mi = sys.version_info[:2] 63 | cache = rf"comtypes_cache\{IMGBASE}-{ma:d}{mi:d}" 64 | path = os.path.join(tempfile.gettempdir(), cache) 65 | with patch_empty_module_to_comtypes_gen(): 66 | gen_dir = comtypes.client._find_gen_dir() 67 | self.assertEqual(path, gen_dir) 68 | 69 | # patch py2exe-attributes to `sys` modules 70 | @mock.patch.object(sys, "frozen", "windows_exe", create=True) 71 | def test_frozen_windows_exe(self): 72 | # %TEMP%\comtypes_cache\-25 73 | ma, mi = sys.version_info[:2] 74 | cache = rf"comtypes_cache\{IMGBASE}-{ma:d}{mi:d}" 75 | path = os.path.join(tempfile.gettempdir(), cache) 76 | with patch_empty_module_to_comtypes_gen(): 77 | gen_dir = comtypes.client._find_gen_dir() 78 | self.assertEqual(path, gen_dir) 79 | 80 | 81 | def main(): 82 | unittest.main() 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /source/CppTestSrv/CUNKNOWN.H: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | #ifndef __CUnknown_h__ 10 | #define __CUnknown_h__ 11 | 12 | #include 13 | 14 | /////////////////////////////////////////////////////////// 15 | // 16 | // Nondelegating IUnknown interface 17 | // - Nondelegating version of IUnknown 18 | // 19 | interface INondelegatingUnknown 20 | { 21 | virtual HRESULT __stdcall 22 | NondelegatingQueryInterface(const IID& iid, void** ppv) = 0 ; 23 | virtual ULONG __stdcall NondelegatingAddRef() = 0 ; 24 | virtual ULONG __stdcall NondelegatingRelease() = 0 ; 25 | } ; 26 | 27 | 28 | /////////////////////////////////////////////////////////// 29 | // 30 | // Declaration of CUnknown 31 | // - Base class for implementing IUnknown 32 | // 33 | 34 | class CUnknown : public INondelegatingUnknown 35 | { 36 | public: 37 | // Nondelegating IUnknown implementation 38 | virtual HRESULT __stdcall NondelegatingQueryInterface(const IID&, 39 | void**) ; 40 | virtual ULONG __stdcall NondelegatingAddRef() ; 41 | virtual ULONG __stdcall NondelegatingRelease() ; 42 | 43 | // Constructor 44 | CUnknown(IUnknown* pUnknownOuter) ; 45 | 46 | // Destructor 47 | virtual ~CUnknown() ; 48 | 49 | // Initialization (especially for aggregates) 50 | virtual HRESULT Init() { return S_OK ;} 51 | 52 | // Notification to derived classes that we are releasing 53 | virtual void FinalRelease() ; 54 | 55 | // Count of currently active components 56 | static long ActiveComponents() 57 | { return s_cActiveComponents ;} 58 | 59 | // Helper function 60 | HRESULT FinishQI(IUnknown* pI, void** ppv) ; 61 | 62 | protected: 63 | // Support for delegation 64 | IUnknown* GetOuterUnknown() const 65 | { return m_pUnknownOuter ;} 66 | 67 | private: 68 | // Reference count for this object 69 | long m_cRef ; 70 | 71 | // Pointer to (external) outer IUnknown 72 | IUnknown* m_pUnknownOuter ; 73 | 74 | // Count of all active instances 75 | static long s_cActiveComponents ; 76 | } ; 77 | 78 | 79 | /////////////////////////////////////////////////////////// 80 | // 81 | // Delegating IUnknown 82 | // - Delegates to the nondelegating IUnknown, or to the 83 | // outer IUnknown if the component is aggregated. 84 | // 85 | #define DECLARE_IUNKNOWN \ 86 | virtual HRESULT __stdcall \ 87 | QueryInterface(const IID& iid, void** ppv) \ 88 | { \ 89 | return GetOuterUnknown()->QueryInterface(iid,ppv) ; \ 90 | } ; \ 91 | virtual ULONG __stdcall AddRef() \ 92 | { \ 93 | return GetOuterUnknown()->AddRef() ; \ 94 | } ; \ 95 | virtual ULONG __stdcall Release() \ 96 | { \ 97 | return GetOuterUnknown()->Release() ; \ 98 | } ; 99 | 100 | 101 | /////////////////////////////////////////////////////////// 102 | 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /comtypes/test/test_hresult.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import unittest as ut 3 | 4 | import comtypes.hresult 5 | 6 | 7 | class Test_MAKE_HRESULT(ut.TestCase): 8 | def test(self): 9 | for sev, fac, code, hr in [ 10 | (0x0000, 0x0000, 0x0000, 0), 11 | (0x0001, 0x0000, 0x0000, -2147483648), 12 | (0x0000, 0x0001, 0x0000, 65536), 13 | (0x0000, 0x0000, 0x0001, 1), 14 | (0x0001, 0xFFFF, 0xFFFF, -1), 15 | (0x0000, 0xFFFF, 0xFFFF, -1), 16 | (0x0001, 0x0000, 0x0001, -2147483647), 17 | (0x0001, 0x0001, 0x0001, -2147418111), 18 | (0x0000, 0x0001, 0xFFFF, 131071), 19 | (0x0001, 0xFFFF, 0x0000, -65536), 20 | (0x0001, 0x0000, 0xFFFF, -2147418113), 21 | ]: 22 | with self.subTest(sev=sev, fac=fac, code=code, hr=hr): 23 | self.assertEqual(comtypes.hresult.MAKE_HRESULT(sev, fac, code), hr) 24 | 25 | 26 | ERROR_OUTOFMEMORY = 14 # 0xE 27 | ERROR_INVALID_PARAMETER = 87 # 0x57 28 | RPC_S_SERVER_UNAVAILABLE = 1722 # 0x6BA 29 | 30 | 31 | class Test_HRESULT_FROM_WIN32(ut.TestCase): 32 | def test(self): 33 | for w32, hr in [ 34 | (ERROR_OUTOFMEMORY, comtypes.hresult.E_OUTOFMEMORY), 35 | (ERROR_INVALID_PARAMETER, comtypes.hresult.E_INVALIDARG), 36 | (RPC_S_SERVER_UNAVAILABLE, comtypes.hresult.RPC_S_SERVER_UNAVAILABLE), 37 | (-1, -1), 38 | (0, -2147024896), 39 | (1, -2147024895), 40 | (0xFFFF - 3, -2146959364), 41 | (0xFFFF - 2, -2146959363), 42 | (0xFFFF - 1, -2146959362), 43 | (0xFFFF + 0, -2146959361), 44 | (0xFFFF + 1, -2147024896), 45 | (0xFFFF + 2, -2147024895), 46 | (0xFFFF + 3, -2147024894), 47 | ]: 48 | with self.subTest(w32=w32, hr=hr): 49 | self.assertEqual(comtypes.hresult.HRESULT_FROM_WIN32(w32), hr) 50 | 51 | 52 | class Test_signed32bithex_to_int(ut.TestCase): 53 | def test(self): 54 | for val, expected in [ 55 | ("0x00000000", comtypes.hresult.S_OK), 56 | ("0x00000001", comtypes.hresult.S_FALSE), 57 | ("0x8000FFFF", comtypes.hresult.E_UNEXPECTED), 58 | ("0x80004002", comtypes.hresult.E_NOINTERFACE), 59 | # boundary values 60 | ("0x7FFFFFFF", 2147483647), 61 | ("0x80000000", -2147483648), 62 | ("0xFFFFFFFF", -1), 63 | ]: 64 | with self.subTest(val=val, expected=expected): 65 | self.assertEqual(comtypes.hresult.signed32bithex_to_int(val), expected) 66 | 67 | 68 | class Test_int_to_signed32bithex(ut.TestCase): 69 | def test(self): 70 | for val, expected in [ 71 | (comtypes.hresult.S_OK, "0x00000000"), 72 | (comtypes.hresult.S_FALSE, "0x00000001"), 73 | (comtypes.hresult.E_UNEXPECTED, "0x8000FFFF"), 74 | (comtypes.hresult.E_NOINTERFACE, "0x80004002"), 75 | ]: 76 | with self.subTest(val=val, expected=expected): 77 | self.assertEqual(comtypes.hresult.int_to_signed32bithex(val), expected) 78 | 79 | 80 | class DocTest(ut.TestCase): 81 | def test(self): 82 | doctest.testmod( 83 | comtypes.hresult, 84 | verbose=False, 85 | optionflags=doctest.ELLIPSIS, 86 | raise_on_error=True, 87 | ) 88 | -------------------------------------------------------------------------------- /comtypes/test/test_dyndispatch.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from comtypes.automation import IDispatch 4 | from comtypes.client import CreateObject, GetModule 5 | from comtypes.client.lazybind import Dispatch 6 | 7 | # create the typelib wrapper and import it 8 | GetModule("scrrun.dll") 9 | from comtypes.gen.Scripting import IDictionary 10 | 11 | 12 | class Test(unittest.TestCase): 13 | def setUp(self): 14 | self.d = CreateObject("Scripting.Dictionary", dynamic=True) 15 | 16 | def tearDown(self): 17 | del self.d 18 | 19 | def test_type(self): 20 | self.assertTrue(isinstance(self.d, Dispatch)) 21 | 22 | def test_index_setter(self): 23 | d = self.d 24 | d.CompareMode = 42 25 | d["foo"] = 1 26 | d["bar"] = "spam foo" 27 | d["baz"] = 3.14 28 | self.assertAccessInterface(d) 29 | 30 | def test_named_property_setter(self): 31 | d = self.d 32 | d.CompareMode = 42 33 | d.Item["foo"] = 1 34 | d.Item["bar"] = "spam foo" 35 | d.Item["baz"] = 3.14 36 | self.assertAccessInterface(d) 37 | 38 | def test_reference_passing(self): 39 | d = self.d 40 | 41 | # Check reference passing 42 | d["self"] = d 43 | d[0] = "something nontrivial" 44 | dself = d["self"] 45 | dself[1] = "something else nontrivial" 46 | self.assertEqual(d, dself) 47 | self.assertEqual(d[0], "something nontrivial") 48 | self.assertEqual(dself[0], d[0]) 49 | self.assertEqual(d[1], "something else nontrivial") 50 | self.assertEqual(dself[1], d[1]) 51 | 52 | def test_query_interface(self): 53 | d = self.d 54 | d.CompareMode = 42 55 | d.Item["foo"] = 1 56 | d.Item["bar"] = "spam foo" 57 | d.Item["baz"] = 3.14 58 | 59 | # This should cast the underlying com object to an IDispatch 60 | d2 = d.QueryInterface(IDispatch) 61 | # Which can be cast to the non-dynamic type 62 | d3 = d2.QueryInterface(IDictionary) 63 | self.assertEqual(d3.CompareMode, 42) 64 | self.assertEqual(d3.Item["foo"], 1) 65 | self.assertEqual(d3.Item["bar"], "spam foo") 66 | self.assertEqual(d3.Item["baz"], 3.14) 67 | 68 | def test_named_property_no_length(self): 69 | with self.assertRaises(TypeError): 70 | len(self.d.Item) 71 | 72 | def test_named_property_not_iterable(self): 73 | with self.assertRaises(TypeError): 74 | list(self.d.Item) 75 | 76 | def assertAccessInterface(self, d): 77 | """Asserts access via indexing and named property""" 78 | self.assertEqual(d.CompareMode, 42) 79 | self.assertEqual(d["foo"], 1) 80 | self.assertEqual(d.Item["foo"], d["foo"]) 81 | self.assertEqual(d.Item("foo"), d["foo"]) 82 | self.assertEqual(d["bar"], "spam foo") 83 | self.assertEqual(d.Item("bar"), "spam foo") 84 | self.assertEqual(d["baz"], 3.14) 85 | self.assertEqual(d.Item("baz"), d["baz"]) 86 | self.assertIsNone(d["asdlfkj"]) 87 | self.assertIsNone(d.Item["asdlfkj"]) 88 | self.assertIsNone(d.Item("asdlfkj")) 89 | 90 | items = iter(d) 91 | self.assertEqual(items[0], "foo") 92 | self.assertEqual(items[1], "bar") 93 | self.assertEqual(items[2], "baz") 94 | self.assertEqual(items[3], "asdlfkj") 95 | 96 | 97 | if __name__ == "__main__": 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /comtypes/test/test_outparam.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import ( 3 | HRESULT, 4 | POINTER, 5 | OleDLL, 6 | WinDLL, 7 | byref, 8 | c_int, 9 | c_size_t, 10 | c_ulong, 11 | c_void_p, 12 | c_wchar, 13 | c_wchar_p, 14 | cast, 15 | memmove, 16 | sizeof, 17 | wstring_at, 18 | ) 19 | from ctypes.wintypes import DWORD, LPVOID 20 | from unittest.mock import patch 21 | 22 | from comtypes import COMMETHOD, GUID, IUnknown 23 | from comtypes.GUID import _CoTaskMemFree 24 | 25 | text_type = str 26 | 27 | 28 | class IMalloc(IUnknown): 29 | _iid_ = GUID("{00000002-0000-0000-C000-000000000046}") 30 | _methods_ = [ 31 | COMMETHOD([], c_void_p, "Alloc", ([], c_ulong, "cb")), 32 | COMMETHOD([], c_void_p, "Realloc", ([], c_void_p, "pv"), ([], c_ulong, "cb")), 33 | COMMETHOD([], None, "Free", ([], c_void_p, "py")), 34 | COMMETHOD([], c_ulong, "GetSize", ([], c_void_p, "pv")), 35 | COMMETHOD([], c_int, "DidAlloc", ([], c_void_p, "pv")), 36 | COMMETHOD([], None, "HeapMinimize"), # 25 37 | ] 38 | 39 | 40 | _ole32 = OleDLL("ole32") 41 | 42 | _CoGetMalloc = _ole32.CoGetMalloc 43 | _CoGetMalloc.argtypes = [DWORD, POINTER(POINTER(IMalloc))] 44 | _CoGetMalloc.restype = HRESULT 45 | 46 | _ole32_nohresult = WinDLL("ole32") 47 | 48 | SIZE_T = c_size_t 49 | _CoTaskMemAlloc = _ole32_nohresult.CoTaskMemAlloc 50 | _CoTaskMemAlloc.argtypes = [SIZE_T] 51 | _CoTaskMemAlloc.restype = LPVOID 52 | 53 | malloc = POINTER(IMalloc)() 54 | _CoGetMalloc(1, byref(malloc)) 55 | assert bool(malloc) 56 | 57 | 58 | def from_outparm(self): 59 | if not self: 60 | return None 61 | result = wstring_at(self) 62 | if not malloc.DidAlloc(self): 63 | raise ValueError("memory was NOT allocated by CoTaskMemAlloc") 64 | _CoTaskMemFree(self) 65 | return result 66 | 67 | 68 | def comstring(text, typ=c_wchar_p): 69 | text = text_type(text) 70 | size = (len(text) + 1) * sizeof(c_wchar) 71 | mem = _CoTaskMemAlloc(size) 72 | print("malloc'd 0x%x, %d bytes" % (mem, size)) 73 | ptr = cast(mem, typ) 74 | memmove(mem, text, size) 75 | return ptr 76 | 77 | 78 | class Test(unittest.TestCase): 79 | @unittest.skip("This fails for reasons I don't understand yet") 80 | # TODO untested changes; this was modified because it had global effects on other tests 81 | @patch.object(c_wchar_p, "__ctypes_from_outparam__", from_outparm) 82 | def test_c_char(self): 83 | # ptr = c_wchar_p("abc") 84 | # self.failUnlessEqual(ptr.__ctypes_from_outparam__(), 85 | # "abc") 86 | 87 | # p = BSTR("foo bar spam") 88 | 89 | x = comstring("Hello, World") 90 | y = comstring("foo bar") 91 | z = comstring("spam, spam, and spam") 92 | 93 | # (x.__ctypes_from_outparam__(), x.__ctypes_from_outparam__()) 94 | print((x.__ctypes_from_outparam__(), None)) # x.__ctypes_from_outparam__()) 95 | 96 | # print comstring("Hello, World", c_wchar_p).__ctypes_from_outparam__() 97 | # print comstring("Hello, World", c_wchar_p).__ctypes_from_outparam__() 98 | # print comstring("Hello, World", c_wchar_p).__ctypes_from_outparam__() 99 | # print comstring("Hello, World", c_wchar_p).__ctypes_from_outparam__() 100 | 101 | 102 | if __name__ == "__main__": 103 | unittest.main() 104 | -------------------------------------------------------------------------------- /comtypes/test/test_monikers.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import unittest 3 | from _ctypes import COMError 4 | from ctypes import HRESULT, POINTER, OleDLL, byref, c_wchar_p 5 | from ctypes.wintypes import DWORD 6 | 7 | from comtypes import GUID, hresult 8 | from comtypes.client import CreateObject, GetModule 9 | 10 | with contextlib.redirect_stdout(None): # supress warnings 11 | GetModule("msvidctl.dll") 12 | from comtypes.gen import MSVidCtlLib as msvidctl 13 | from comtypes.gen.MSVidCtlLib import IBindCtx, IMoniker, IRunningObjectTable 14 | 15 | MKSYS_ITEMMONIKER = 4 16 | ROTFLAGS_ALLOWANYCLIENT = 1 17 | LPOLESTR = LPCOLESTR = c_wchar_p 18 | 19 | _ole32 = OleDLL("ole32") 20 | 21 | _CreateItemMoniker = _ole32.CreateItemMoniker 22 | _CreateItemMoniker.argtypes = [LPCOLESTR, LPCOLESTR, POINTER(POINTER(IMoniker))] 23 | _CreateItemMoniker.restype = HRESULT 24 | 25 | _CreateBindCtx = _ole32.CreateBindCtx 26 | _CreateBindCtx.argtypes = [DWORD, POINTER(POINTER(IBindCtx))] 27 | _CreateBindCtx.restype = HRESULT 28 | 29 | _GetRunningObjectTable = _ole32.GetRunningObjectTable 30 | _GetRunningObjectTable.argtypes = [DWORD, POINTER(POINTER(IRunningObjectTable))] 31 | _GetRunningObjectTable.restype = HRESULT 32 | 33 | 34 | def _create_item_moniker(delim: str, item: str) -> IMoniker: 35 | mon = POINTER(IMoniker)() 36 | _CreateItemMoniker(delim, item, byref(mon)) 37 | return mon # type: ignore 38 | 39 | 40 | def _create_bctx() -> IBindCtx: 41 | bctx = POINTER(IBindCtx)() 42 | # The first parameter is reserved and must be 0. 43 | _CreateBindCtx(0, byref(bctx)) 44 | return bctx # type: ignore 45 | 46 | 47 | def _create_rot() -> IRunningObjectTable: 48 | rot = POINTER(IRunningObjectTable)() 49 | # The first parameter is reserved and must be 0. 50 | _GetRunningObjectTable(0, byref(rot)) 51 | return rot # type: ignore 52 | 53 | 54 | class Test_IMoniker(unittest.TestCase): 55 | def test_IsSystemMoniker(self): 56 | item_id = str(GUID.create_new()) 57 | mon = _create_item_moniker("!", item_id) 58 | self.assertEqual(mon.IsSystemMoniker(), MKSYS_ITEMMONIKER) 59 | 60 | 61 | class Test_IBindCtx(unittest.TestCase): 62 | def test_EnumObjectParam(self): 63 | bctx = _create_bctx() 64 | with self.assertRaises(COMError) as err_ctx: 65 | # calling `EnumObjectParam` results in a return value of E_NOTIMPL. 66 | # https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ibindctx-enumobjectparam#notes-to-callers 67 | bctx.EnumObjectParam() 68 | self.assertEqual(err_ctx.exception.hresult, hresult.E_NOTIMPL) 69 | 70 | 71 | class Test_IRunningObjectTable(unittest.TestCase): 72 | def test_register_and_revoke_item_moniker(self): 73 | vidctl = CreateObject(msvidctl.MSVidCtl, interface=msvidctl.IMSVidCtl) 74 | item_id = str(GUID.create_new()) 75 | mon = _create_item_moniker("!", item_id) 76 | rot = _create_rot() 77 | bctx = _create_bctx() 78 | self.assertEqual(mon.IsRunning(bctx, None, None), hresult.S_FALSE) 79 | dw_reg = rot.Register(ROTFLAGS_ALLOWANYCLIENT, vidctl, mon) 80 | self.assertEqual(mon.IsRunning(bctx, None, None), hresult.S_OK) 81 | self.assertEqual(f"!{item_id}", mon.GetDisplayName(bctx, None)) 82 | self.assertEqual(rot.GetObject(mon).QueryInterface(msvidctl.IMSVidCtl), vidctl) 83 | rot.Revoke(dw_reg) 84 | self.assertEqual(mon.IsRunning(bctx, None, None), hresult.S_FALSE) 85 | -------------------------------------------------------------------------------- /source/CppTestSrv/SERVER.IDL: -------------------------------------------------------------------------------- 1 | // 2 | // Server.idl 3 | // 4 | // This file will be processed by the MIDL compiler to 5 | // produce the type library (server.tlb) and marshaling code. 6 | // 7 | 8 | import "oaidl.idl" ; 9 | 10 | // Simple structure used for tests related to IRecordInfo or GetRecordInfoFromGuids functionality. 11 | // If a new test would require other fields do NOT modify this structure but add a new structure instead. 12 | typedef [uuid(00FABB0F-5691-41A6-B7C1-11606671F8E5)] 13 | struct StructRecordParamTest { 14 | BSTR question ; 15 | long answer ; 16 | VARIANT_BOOL needs_clarification ; 17 | } StructRecordParamTest ; 18 | 19 | 20 | // Interface IDualRecordParamTest 21 | [ 22 | odl, 23 | uuid(0C4E01E8-4625-46A2-BC4C-2E889A8DBBD6), 24 | dual, 25 | helpstring("Dual Interface for testing record parameters."), 26 | nonextensible, 27 | oleautomation 28 | ] 29 | interface IDualRecordParamTest : IDispatch 30 | { 31 | [id(0x00000001)] 32 | HRESULT InitRecord([in, out] StructRecordParamTest* test_record) ; 33 | [id(0x00000002)] 34 | HRESULT VerifyRecord( 35 | [in] StructRecordParamTest* test_record, 36 | [out, retval] VARIANT_BOOL* result); 37 | } ; 38 | 39 | 40 | // Interface IDispRecordParamTest 41 | [ 42 | uuid(033E4C10-0A7F-4E93-8377-499AD4B6583A), 43 | helpstring("Dispinterface for testing record parameters.") 44 | ] 45 | dispinterface IDispRecordParamTest 46 | { 47 | interface IDualRecordParamTest; 48 | } ; 49 | 50 | 51 | // Interface IDualSafearrayParamTest 52 | [ 53 | odl, 54 | uuid(1F4F3B8B-D07E-4BB6-8D2C-D79B375696DA), 55 | dual, 56 | helpstring("IDualSafearrayParamTest Interface"), 57 | nonextensible, 58 | oleautomation 59 | ] 60 | interface IDualSafearrayParamTest : IDispatch 61 | { 62 | [id(0x00000001)] 63 | HRESULT InitArray([in, out] SAFEARRAY(double)* test_array) ; 64 | [id(0x00000002)] 65 | HRESULT VerifyArray( 66 | [in] SAFEARRAY(double) test_array, 67 | [out, retval] VARIANT_BOOL* result); 68 | } ; 69 | 70 | 71 | // Interface IDispSafearrayParamTest 72 | [ 73 | uuid(4097A6D0-A111-40E2-BD0B-177B775A9496) 74 | ] 75 | dispinterface IDispSafearrayParamTest 76 | { 77 | interface IDualSafearrayParamTest; 78 | } ; 79 | 80 | 81 | // 82 | // Component and type library descriptions 83 | // 84 | [ 85 | uuid(07D2AEE5-1DF8-4D2C-953A-554ADFD25F99), 86 | version(1.0), 87 | helpstring("Comtypes C++ Test COM Server 1.0 Type Library.") 88 | ] 89 | library ComtypesCppTestSrvLib 90 | { 91 | // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046} 92 | importlib("stdole2.tlb") ; 93 | 94 | // CoComtypesDispRecordParamTest 95 | // Component that implements interfaces used for dispinterface record parameter tests. 96 | [ 97 | uuid(5E78C9A8-4C19-4285-BCD6-3FFBBA5B17A8), 98 | helpstring("Comtypes component for dispinterface record parameter tests.") 99 | ] 100 | coclass CoComtypesDispRecordParamTest 101 | { 102 | interface IDualRecordParamTest ; 103 | dispinterface IDispRecordParamTest ; 104 | } ; 105 | 106 | 107 | // CoComtypesDispSafearrayParamTest 108 | // Component that implements interfaces used for dispinterface Safearray parameter tests. 109 | [ 110 | uuid(091D762E-FF4B-4532-8B24-23807FE873C3), 111 | helpstring("Comtypes component for dispinterface Safearray parameter tests.") 112 | ] 113 | coclass CoComtypesDispSafearrayParamTest 114 | { 115 | interface IDualSafearrayParamTest ; 116 | dispinterface IDispSafearrayParamTest ; 117 | } ; 118 | } ; 119 | -------------------------------------------------------------------------------- /comtypes/server/localserver.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import queue 3 | from collections.abc import Sequence 4 | from ctypes import HRESULT, OleDLL, byref, c_ulong, c_void_p 5 | from ctypes.wintypes import DWORD, LPDWORD 6 | from typing import TYPE_CHECKING, Any, Literal, Optional 7 | 8 | import comtypes 9 | from comtypes import GUID, COMObject, IUnknown, hresult 10 | from comtypes.GUID import REFCLSID 11 | from comtypes.server import IClassFactory 12 | 13 | if TYPE_CHECKING: 14 | from ctypes import _Pointer 15 | 16 | 17 | logger = logging.getLogger(__name__) 18 | _debug = logger.debug 19 | 20 | REGCLS_SINGLEUSE = 0 # class object only generates one instance 21 | REGCLS_MULTIPLEUSE = 1 # same class object genereates multiple inst. 22 | REGCLS_MULTI_SEPARATE = 2 # multiple use, but separate control over each 23 | REGCLS_SUSPENDED = 4 # register it as suspended, will be activated 24 | REGCLS_SURROGATE = 8 # must be used when a surrogate process 25 | 26 | _ole32 = OleDLL("ole32") 27 | 28 | _CoRegisterClassObject = _ole32.CoRegisterClassObject 29 | _CoRegisterClassObject.argtypes = [REFCLSID, c_void_p, DWORD, DWORD, LPDWORD] 30 | _CoRegisterClassObject.restype = HRESULT 31 | 32 | _CoRevokeClassObject = _ole32.CoRevokeClassObject 33 | _CoRevokeClassObject.argtypes = [DWORD] 34 | _CoRevokeClassObject.restype = HRESULT 35 | 36 | 37 | def run(classes: Sequence[type[COMObject]]) -> None: 38 | classobjects = [ClassFactory(cls) for cls in classes] 39 | COMObject.__run_localserver__(classobjects) 40 | 41 | 42 | class ClassFactory(COMObject): 43 | _com_interfaces_ = [IClassFactory] 44 | _locks: int = 0 45 | _queue: Optional[queue.Queue] = None 46 | regcls: int = REGCLS_MULTIPLEUSE 47 | 48 | def __init__(self, cls: type[COMObject], *args, **kw) -> None: 49 | super().__init__() 50 | self._cls = cls 51 | self._register_class() 52 | self._args = args 53 | self._kw = kw 54 | 55 | def IUnknown_AddRef(self, this: Any) -> int: 56 | return 2 57 | 58 | def IUnknown_Release(self, this: Any) -> int: 59 | return 1 60 | 61 | def _register_class(self) -> None: 62 | regcls = getattr(self._cls, "_regcls_", self.regcls) 63 | cookie = c_ulong() 64 | ptr = self._com_pointers_[IUnknown._iid_] 65 | clsctx = self._cls._reg_clsctx_ # type: ignore 66 | clsctx &= ~comtypes.CLSCTX_INPROC # reset the inproc flags 67 | _CoRegisterClassObject( 68 | byref(GUID(self._cls._reg_clsid_)), 69 | ptr, 70 | clsctx, 71 | regcls, 72 | byref(cookie), 73 | ) 74 | self.cookie = cookie 75 | 76 | def _revoke_class(self) -> None: 77 | _CoRevokeClassObject(self.cookie) 78 | 79 | def CreateInstance( 80 | self, 81 | this: Any, 82 | punkOuter: Optional[type["_Pointer[IUnknown]"]], 83 | riid: "_Pointer[GUID]", 84 | ppv: c_void_p, 85 | ) -> int: 86 | _debug("ClassFactory.CreateInstance(%s)", riid[0]) 87 | obj = self._cls(*self._args, **self._kw) 88 | result = obj.IUnknown_QueryInterface(None, riid, ppv) 89 | _debug("CreateInstance() -> %s", result) 90 | return result 91 | 92 | def LockServer(self, this: Any, fLock: bool) -> Literal[0]: 93 | assert COMObject.__server__ is not None, "The localserver is not running yet" 94 | if fLock: 95 | COMObject.__server__.Lock() 96 | else: 97 | COMObject.__server__.Unlock() 98 | return hresult.S_OK 99 | -------------------------------------------------------------------------------- /comtypes/tools/codegenerator/comments.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from comtypes.tools import typedesc 4 | 5 | 6 | class ComInterfaceBodyImplCommentWriter: 7 | def __init__(self, stream: io.StringIO): 8 | self.stream = stream 9 | 10 | def write(self, body: typedesc.ComInterfaceBody) -> None: 11 | print( 12 | "################################################################", 13 | file=self.stream, 14 | ) 15 | print(f"# code template for {body.itf.name} implementation", file=self.stream) 16 | print(f"# class {body.itf.name}_Impl(object):", file=self.stream) 17 | 18 | methods = {} 19 | for m in body.itf.members: 20 | if isinstance(m, typedesc.ComMethod): 21 | # m.arguments is a sequence of tuples: 22 | # (argtype, argname, idlflags, docstring) 23 | # Some typelibs have unnamed method parameters! 24 | inargs = [a[1] or "" for a in m.arguments if "out" not in a[2]] 25 | outargs = [a[1] or "" for a in m.arguments if "out" in a[2]] 26 | if "propget" in m.idlflags: 27 | methods.setdefault(m.name, [0, inargs, outargs, m.doc])[0] |= 1 28 | elif "propput" in m.idlflags: 29 | methods.setdefault(m.name, [0, inargs[:-1], inargs[-1:], m.doc])[ 30 | 0 31 | ] |= 2 32 | else: 33 | methods[m.name] = [0, inargs, outargs, m.doc] 34 | 35 | for name, (typ, inargs, outargs, doc) in methods.items(): 36 | if typ == 0: # method 37 | print( 38 | f"# def {name}({', '.join(['self'] + inargs)}):", 39 | file=self.stream, 40 | ) 41 | print(f"# {(doc or '-no docstring-')!r}", file=self.stream) 42 | print(f"# #return {', '.join(outargs)}", file=self.stream) 43 | elif typ == 1: # propget 44 | print("# @property", file=self.stream) 45 | print( 46 | f"# def {name}({', '.join(['self'] + inargs)}):", 47 | file=self.stream, 48 | ) 49 | print(f"# {(doc or '-no docstring-')!r}", file=self.stream) 50 | print(f"# #return {', '.join(outargs)}", file=self.stream) 51 | elif typ == 2: # propput 52 | print( 53 | f"# def _set({', '.join(['self'] + inargs + outargs)}):", 54 | file=self.stream, 55 | ) 56 | print(f"# {(doc or '-no docstring-')!r}", file=self.stream) 57 | print( 58 | f"# {name} = property(fset = _set, doc = _set.__doc__)", 59 | file=self.stream, 60 | ) 61 | elif typ == 3: # propget + propput 62 | print( 63 | f"# def _get({', '.join(['self'] + inargs)}):", 64 | file=self.stream, 65 | ) 66 | print(f"# {(doc or '-no docstring-')!r}", file=self.stream) 67 | print(f"# #return {', '.join(outargs)}", file=self.stream) 68 | print( 69 | f"# def _set({', '.join(['self'] + inargs + outargs)}):", 70 | file=self.stream, 71 | ) 72 | print(f"# {(doc or '-no docstring-')!r}", file=self.stream) 73 | print( 74 | f"# {name} = property(_get, _set, doc = _set.__doc__)", 75 | file=self.stream, 76 | ) 77 | else: 78 | raise RuntimeError("BUG") 79 | print("#", file=self.stream) 80 | -------------------------------------------------------------------------------- /source/AvmcIfc.rc: -------------------------------------------------------------------------------- 1 | //Microsoft Developer Studio generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (U.S.) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | #ifdef _WIN32 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | #pragma code_page(1252) 22 | #endif //_WIN32 23 | 24 | #ifdef APSTUDIO_INVOKED 25 | ///////////////////////////////////////////////////////////////////////////// 26 | // 27 | // TEXTINCLUDE 28 | // 29 | 30 | 1 TEXTINCLUDE DISCARDABLE 31 | BEGIN 32 | "resource.h\0" 33 | END 34 | 35 | 2 TEXTINCLUDE DISCARDABLE 36 | BEGIN 37 | "#include ""winres.h""\r\n" 38 | "\0" 39 | END 40 | 41 | 3 TEXTINCLUDE DISCARDABLE 42 | BEGIN 43 | "1 TYPELIB ""AvmcIfc.tlb""\r\n" 44 | "\0" 45 | END 46 | 47 | #endif // APSTUDIO_INVOKED 48 | 49 | 50 | #ifndef _MAC 51 | ///////////////////////////////////////////////////////////////////////////// 52 | // 53 | // Version 54 | // 55 | 56 | VS_VERSION_INFO VERSIONINFO 57 | FILEVERSION 1,0,0,1 58 | PRODUCTVERSION 1,0,0,1 59 | FILEFLAGSMASK 0x3fL 60 | #ifdef _DEBUG 61 | FILEFLAGS 0x1L 62 | #else 63 | FILEFLAGS 0x0L 64 | #endif 65 | FILEOS 0x4L 66 | FILETYPE 0x2L 67 | FILESUBTYPE 0x0L 68 | BEGIN 69 | BLOCK "StringFileInfo" 70 | BEGIN 71 | BLOCK "040904B0" 72 | BEGIN 73 | VALUE "CompanyName", "\0" 74 | VALUE "FileDescription", "AvmcIfc Module\0" 75 | VALUE "FileVersion", "1, 0, 0, 1\0" 76 | VALUE "InternalName", "AvmcIfc\0" 77 | VALUE "LegalCopyright", "Copyright 2006\0" 78 | VALUE "OriginalFilename", "AvmcIfc.DLL\0" 79 | VALUE "ProductName", "AvmcIfc Module\0" 80 | VALUE "ProductVersion", "1, 0, 0, 1\0" 81 | VALUE "OLESelfRegister", "\0" 82 | END 83 | END 84 | BLOCK "VarFileInfo" 85 | BEGIN 86 | VALUE "Translation", 0x409, 1200 87 | END 88 | END 89 | 90 | #endif // !_MAC 91 | 92 | 93 | ///////////////////////////////////////////////////////////////////////////// 94 | // 95 | // String Table 96 | // 97 | 98 | STRINGTABLE DISCARDABLE 99 | BEGIN 100 | IDS_PROJNAME "AvmcIfc" 101 | IDS_AVMC_DESC "Avmc Class" 102 | END 103 | 104 | #endif // English (U.S.) resources 105 | ///////////////////////////////////////////////////////////////////////////// 106 | 107 | 108 | ///////////////////////////////////////////////////////////////////////////// 109 | // Unknown language: 0xD, 0x1 resources 110 | 111 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HEB) 112 | #ifdef _WIN32 113 | LANGUAGE 0xD, 0x1 114 | #pragma code_page(1255) 115 | #endif //_WIN32 116 | 117 | ///////////////////////////////////////////////////////////////////////////// 118 | // 119 | // REGISTRY 120 | // 121 | 122 | IDR_Avmc REGISTRY DISCARDABLE "Avmc.rgs" 123 | #endif // Unknown language: 0xD, 0x1 resources 124 | ///////////////////////////////////////////////////////////////////////////// 125 | 126 | 127 | 128 | #ifndef APSTUDIO_INVOKED 129 | ///////////////////////////////////////////////////////////////////////////// 130 | // 131 | // Generated from the TEXTINCLUDE 3 resource. 132 | // 133 | 1 TYPELIB "AvmcIfc.tlb" 134 | 135 | ///////////////////////////////////////////////////////////////////////////// 136 | #endif // not APSTUDIO_INVOKED 137 | 138 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comtypes" 3 | description = "Pure Python COM package" 4 | readme = "README.md" 5 | requires-python = ">=3.9" 6 | license = "MIT" 7 | license-files = ["LICENSE.txt"] 8 | authors = [ 9 | { name = "Thomas Heller", email = "theller@python.net" }, 10 | ] 11 | classifiers = [ 12 | "Development Status :: 5 - Production/Stable", 13 | "Intended Audience :: Developers", 14 | "Operating System :: Microsoft :: Windows", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | ] 19 | dynamic = ["version"] 20 | 21 | [project.scripts] 22 | clear_comtypes_cache = "comtypes.clear_cache:main" 23 | 24 | [project.urls] 25 | Source = "https://github.com/enthought/comtypes" 26 | Download = "https://github.com/enthought/comtypes/releases" 27 | Issues = "https://github.com/enthought/comtypes/issues" 28 | 29 | [build-system] 30 | requires = ["setuptools>=77.0.0"] 31 | build-backend = "setuptools.build_meta" 32 | 33 | [tool.setuptools] 34 | packages = [ 35 | "comtypes", 36 | "comtypes._post_coinit", 37 | "comtypes.client", 38 | "comtypes.server", 39 | "comtypes.tools", 40 | "comtypes.tools.codegenerator", 41 | "comtypes.test", 42 | ] 43 | 44 | [tool.setuptools.dynamic] 45 | version = { attr = "comtypes.__version__" } 46 | 47 | [tool.setuptools.package-data] 48 | "comtypes.test" = [ 49 | "TestComServer.idl", 50 | "TestComServer.tlb", 51 | "TestDispServer.idl", 52 | "TestDispServer.tlb", 53 | "mytypelib.idl", 54 | "mylib.idl", 55 | "mylib.tlb", 56 | "urlhist.tlb", 57 | "test_jscript.js", 58 | ] 59 | "comtypes" = ["hints.pyi"] 60 | 61 | [tool.ruff.lint] 62 | extend-select = ["I"] 63 | ignore = ["E402"] 64 | 65 | [tool.ruff.lint.per-file-ignores] 66 | # production 67 | "comtypes/_npsupport.py" = ["F401"] 68 | "comtypes/_vtbl.py" = ["E722"] 69 | "comtypes/automation.py" = ["F401", "F403", "F405"] 70 | "comtypes/git.py" = ["F401", "F403", "F405"] 71 | "comtypes/viewobject.py" = ["F403", "F405"] 72 | "comtypes/client/_constants.py" = ["F401"] 73 | "comtypes/server/automation.py" = ["F403", "F405"] 74 | "comtypes/server/connectionpoints.py" = ["F401", "F403", "F405"] 75 | "comtypes/server/inprocserver.py" = ["E722"] 76 | "comtypes/server/register.py" = ["E713"] 77 | "comtypes/tools/codegenerator/packing.py" = ["F821", "F841"] 78 | "comtypes/tools/typedesc.py" = ["F403", "F405"] 79 | "comtypes/tools/typedesc_base.py" = ["F401"] 80 | # gen directory 81 | "comtypes/gen/*" = ["E", "F", "I"] 82 | # stub 83 | "comtypes/hints.pyi" = ["I"] 84 | # tests 85 | "comtypes/test/TestDispServer.py" = ["E401"] 86 | "comtypes/test/find_memleak.py" = ["E401", "F401", "F403", "F405"] 87 | "comtypes/test/setup.py" = ["F401"] 88 | "comtypes/test/test_agilent.py" = ["F401", "F841"] 89 | "comtypes/test/test_client.py" = ["F401"] 90 | "comtypes/test/test_dict.py" = ["F841"] 91 | "comtypes/test/test_eventinterface.py" = ["F841"] 92 | "comtypes/test/test_outparam.py" = ["F841"] 93 | "comtypes/test/test_sapi.py" = ["E401"] 94 | "comtypes/test/test_server.py" = ["F401", "F841"] 95 | "comtypes/test/test_subinterface.py" = ["E401", "F401", "F403", "F405"] 96 | "comtypes/test/test_urlhistory.py" = ["E401", "F401", "F403", "F405", "F841"] 97 | "comtypes/test/test_variant.py" = ["F401", "F821", "F841"] 98 | 99 | [tool.coverage.run] 100 | # Specify the source directory to avoid tracking temporary files created by "test_client_regenerate_modules.py". 101 | # Without this, coverage track these temporary files, leading to error when trying to generate a report. 102 | source = ["comtypes"] 103 | omit = ["comtypes/gen/*"] 104 | 105 | [tool.coverage.report] 106 | exclude_also = [ 107 | "if __name__ == .__main__.:", 108 | "if TYPE_CHECKING:", 109 | ] 110 | -------------------------------------------------------------------------------- /source/CppTestSrv/CFACTORY.H: -------------------------------------------------------------------------------- 1 | /* 2 | This code is based on example code to the book: 3 | Inside COM 4 | by Dale E. Rogerson 5 | Microsoft Press 1997 6 | ISBN 1-57231-349-8 7 | */ 8 | 9 | #ifndef __CFactory_h__ 10 | #define __CFactory_h__ 11 | 12 | #include "CUnknown.h" 13 | /////////////////////////////////////////////////////////// 14 | 15 | // Forward reference 16 | class CFactoryData ; 17 | 18 | // Global data used by CFactory 19 | extern CFactoryData g_FactoryDataArray[] ; 20 | extern int g_cFactoryDataEntries ; 21 | 22 | ////////////////////////////////////////////////////////// 23 | // 24 | // Component creation function 25 | // 26 | class CUnknown ; 27 | 28 | typedef HRESULT (*FPCREATEINSTANCE)(IUnknown*, CUnknown**) ; 29 | 30 | /////////////////////////////////////////////////////////// 31 | // 32 | // CFactoryData 33 | // - Information CFactory needs to create a component 34 | // supported by the DLL 35 | // 36 | class CFactoryData 37 | { 38 | public: 39 | // The class ID for the component 40 | const CLSID* m_pCLSID ; 41 | 42 | // Pointer to the function that creates it 43 | FPCREATEINSTANCE CreateInstance ; 44 | 45 | // Name of the component to register in the registry 46 | LPCWSTR m_RegistryName ; 47 | 48 | // ProgID 49 | LPCWSTR m_szProgID ; 50 | 51 | // Version-independent ProgID 52 | LPCWSTR m_szVerIndProgID ; 53 | 54 | // Helper function for finding the class ID 55 | BOOL IsClassID(const CLSID& clsid) const 56 | { return (*m_pCLSID == clsid) ;} 57 | 58 | // Type Library ID 59 | const GUID* m_pLIBID ; 60 | 61 | // 62 | // Out of process server support 63 | // 64 | 65 | // Pointer to running class factory for this component 66 | IClassFactory* m_pIClassFactory ; 67 | 68 | // Magic cookie to identify running object 69 | DWORD m_dwRegister ; 70 | } ; 71 | 72 | 73 | /////////////////////////////////////////////////////////// 74 | // 75 | // Class Factory 76 | // 77 | class CFactory : public IClassFactory 78 | { 79 | public: 80 | // IUnknown 81 | virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; 82 | virtual ULONG __stdcall AddRef() ; 83 | virtual ULONG __stdcall Release() ; 84 | 85 | // IClassFactory 86 | virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, 87 | const IID& iid, 88 | void** ppv) ; 89 | virtual HRESULT __stdcall LockServer(BOOL bLock) ; 90 | 91 | // Constructor - Pass pointer to data of component to create. 92 | CFactory(const CFactoryData* pFactoryData) ; 93 | 94 | // Destructor 95 | ~CFactory() { } 96 | 97 | // 98 | // Static FactoryData support functions 99 | // 100 | 101 | // DllGetClassObject support 102 | static HRESULT GetClassObject(const CLSID& clsid, 103 | const IID& iid, 104 | void** ppv) ; 105 | 106 | // Helper function for DllCanUnloadNow 107 | static BOOL IsLocked() 108 | { return (s_cServerLocks > 0) ;} 109 | 110 | // Functions to [un]register all components 111 | static HRESULT RegisterAll() ; 112 | static HRESULT UnregisterAll() ; 113 | 114 | // Function to determine if component can be unloaded 115 | static HRESULT CanUnloadNow() ; 116 | 117 | 118 | // 119 | // Out-of-process server support 120 | // 121 | 122 | static BOOL StartFactories() ; 123 | static void StopFactories() ; 124 | 125 | static DWORD s_dwThreadID ; 126 | 127 | // Shut down the application. 128 | static void CloseExe() 129 | { 130 | if (CanUnloadNow() == S_OK) 131 | { 132 | ::PostThreadMessage(s_dwThreadID, WM_QUIT, 0, 0) ; 133 | } 134 | } 135 | 136 | public: 137 | // Reference Count 138 | LONG m_cRef ; 139 | 140 | // Pointer to information about class this factory creates 141 | const CFactoryData* m_pFactoryData ; 142 | 143 | // Count of locks 144 | static LONG s_cServerLocks ; 145 | 146 | // Module handle 147 | static HMODULE s_hModule ; 148 | } ; 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /comtypes/GUID.py: -------------------------------------------------------------------------------- 1 | """comtypes.GUID module""" 2 | 3 | from ctypes import HRESULT, POINTER, OleDLL, Structure, WinDLL, byref, c_wchar_p 4 | from ctypes.wintypes import BYTE, DWORD, LPVOID, WORD 5 | from typing import TYPE_CHECKING, Any 6 | 7 | if TYPE_CHECKING: 8 | from comtypes import hints # type: ignore 9 | 10 | 11 | def binary(obj: "GUID") -> bytes: 12 | return bytes(obj) 13 | 14 | 15 | # Note: Comparing GUID instances by comparing their buffers 16 | # is slightly faster than using ole32.IsEqualGUID. 17 | 18 | 19 | class GUID(Structure): 20 | """Globally unique identifier structure.""" 21 | 22 | _fields_ = [("Data1", DWORD), ("Data2", WORD), ("Data3", WORD), ("Data4", BYTE * 8)] 23 | 24 | def __init__(self, name=None): 25 | if name is not None: 26 | _CLSIDFromString(str(name), byref(self)) 27 | 28 | def __repr__(self): 29 | return f'GUID("{str(self)}")' 30 | 31 | def __str__(self) -> str: 32 | p = c_wchar_p() 33 | _StringFromCLSID(byref(self), byref(p)) 34 | result = p.value 35 | _CoTaskMemFree(p) 36 | # stringified `GUID_null` would be '{00000000-0000-0000-0000-000000000000}' 37 | # Should we do `assert result is not None`? 38 | return result # type: ignore 39 | 40 | def __bool__(self) -> bool: 41 | return self != GUID_null 42 | 43 | def __eq__(self, other) -> bool: 44 | return isinstance(other, GUID) and binary(self) == binary(other) 45 | 46 | def __hash__(self) -> int: 47 | # We make GUID instances hashable, although they are mutable. 48 | return hash(binary(self)) 49 | 50 | def copy(self) -> "GUID": 51 | return GUID(str(self)) 52 | 53 | @classmethod 54 | def from_progid(cls, progid: Any) -> "hints.Self": 55 | """Get guid from progid, ...""" 56 | if hasattr(progid, "_reg_clsid_"): 57 | progid = progid._reg_clsid_ 58 | if isinstance(progid, cls): 59 | return progid 60 | elif isinstance(progid, str): 61 | if progid.startswith("{"): 62 | return cls(progid) 63 | inst = cls() 64 | _CLSIDFromProgID(str(progid), byref(inst)) 65 | return inst 66 | else: 67 | raise TypeError(f"Cannot construct guid from {progid!r}") 68 | 69 | def as_progid(self) -> str: 70 | """Convert a GUID into a progid""" 71 | progid = c_wchar_p() 72 | _ProgIDFromCLSID(byref(self), byref(progid)) 73 | result = progid.value 74 | _CoTaskMemFree(progid) 75 | # Should we do `assert result is not None`? 76 | return result # type: ignore 77 | 78 | @classmethod 79 | def create_new(cls) -> "hints.Self": 80 | """Create a brand new guid""" 81 | guid = cls() 82 | _CoCreateGuid(byref(guid)) 83 | return guid 84 | 85 | 86 | REFCLSID = POINTER(GUID) 87 | LPOLESTR = LPCOLESTR = c_wchar_p 88 | LPCLSID = POINTER(GUID) 89 | 90 | _ole32_nohresult = WinDLL("ole32") 91 | _ole32 = OleDLL("ole32") 92 | 93 | _StringFromCLSID = _ole32.StringFromCLSID 94 | _StringFromCLSID.argtypes = [REFCLSID, POINTER(LPOLESTR)] 95 | _StringFromCLSID.restype = HRESULT 96 | 97 | _CoTaskMemFree = _ole32_nohresult.CoTaskMemFree 98 | _CoTaskMemFree.argtypes = [LPVOID] 99 | _CoTaskMemFree.restype = None 100 | 101 | _ProgIDFromCLSID = _ole32.ProgIDFromCLSID 102 | _ProgIDFromCLSID.argtypes = [REFCLSID, POINTER(LPOLESTR)] 103 | _ProgIDFromCLSID.restype = HRESULT 104 | 105 | _CLSIDFromString = _ole32.CLSIDFromString 106 | _CLSIDFromString.argtypes = [LPCOLESTR, LPCLSID] 107 | _CLSIDFromString.restype = HRESULT 108 | 109 | _CLSIDFromProgID = _ole32.CLSIDFromProgID 110 | _CLSIDFromProgID.argtypes = [LPCOLESTR, LPCLSID] 111 | _CLSIDFromProgID.restype = HRESULT 112 | 113 | _CoCreateGuid = _ole32.CoCreateGuid 114 | _CoCreateGuid.argtypes = [POINTER(GUID)] 115 | _CoCreateGuid.restype = HRESULT 116 | 117 | GUID_null = GUID() 118 | 119 | __all__ = ["GUID"] 120 | -------------------------------------------------------------------------------- /source/Avmc.cpp: -------------------------------------------------------------------------------- 1 | // Avmc.cpp : Implementation of CAvmcIfcApp and DLL registration. 2 | 3 | #include "stdafx.h" 4 | #include "AvmcIfc.h" 5 | #include "Avmc.h" 6 | #include 7 | 8 | ///////////////////////////////////////////////////////////////////////////// 9 | // 10 | 11 | const IID DEVICE_INFO_IID = {0x6C7A25CB, 0x7938, 0x4BE0, {0xA2, 0x85, 0x12, 0xC6, 0x16, 0x71, 0x7F, 0xDD} }; 12 | 13 | STDMETHODIMP Avmc::InterfaceSupportsErrorInfo(REFIID riid) 14 | { 15 | static const IID* arr[] = 16 | { 17 | &IID_IAvmc, 18 | }; 19 | 20 | for (int i=0;iRelease(); // Release the interface 81 | if( *avmcList == NULL ) { 82 | HRESULT hr = Error( _T("Can not create array of Device Info structures") ); 83 | return( hr ); 84 | } 85 | 86 | /////////////////////////////////////////////////////////////////// 87 | 88 | #ifdef DEBUG_NOW 89 | strstream s2; 90 | s2.clear(); 91 | for (i = 0; i < numDevs; i++) { 92 | s2 << "Dev " << i << endl; 93 | s2 << " Flags = 0x" << hex << devInfo[i].Flags << endl; 94 | s2 << " Type = 0x" << hex << devInfo[i].Type << endl; 95 | s2 << " ID = 0x" << hex << devInfo[i].ID << endl; 96 | s2 << " LocId = 0x" << hex << devInfo[i].LocId << endl; 97 | s2 << " SerialNumber = " << devInfo[i].SerialNumber << endl; 98 | s2 << " Description = " << devInfo[i].Description << endl; 99 | s2 << " ftHandle = 0x" << hex << devInfo[i].ftHandle << endl; 100 | s2 << "---" << endl; 101 | } 102 | s2 << ends; 103 | MessageBox (0, s2.str(), "Device List", 0); 104 | #endif 105 | DeviceInfo HUGEP *pD = NULL; 106 | hr = SafeArrayAccessData (*avmcList, (void HUGEP **)&pD); 107 | 108 | for (i = 0; i < numDevs; i++) { 109 | pD[i].Flags = (ULONG) devInfo[i].Flags; 110 | pD[i].Type = (ULONG) devInfo[i].Type; 111 | pD[i].ID = (ULONG) devInfo[i].ID; 112 | pD[i].LocId = (ULONG) devInfo[i].LocId; 113 | pD[i].SerialNumber = _com_util::ConvertStringToBSTR(devInfo[i].SerialNumber); 114 | pD[i].Description = _com_util::ConvertStringToBSTR(devInfo[i].Description); 115 | pD[i].ftHandle = (ULONG)devInfo[i].ftHandle; 116 | } 117 | 118 | hr = SafeArrayUnaccessData(*avmcList); 119 | } 120 | 121 | return S_OK; 122 | } -------------------------------------------------------------------------------- /docs/source/npsupport.rst: -------------------------------------------------------------------------------- 1 | ############# 2 | NumPy interop 3 | ############# 4 | 5 | NumPy provides the *de facto* array standard for Python. Though NumPy 6 | is not required to use |comtypes|, |comtypes| provides various options for 7 | NumPy interoperability. NumPy version 1.7 or greater is required to access 8 | all of these features. 9 | 10 | 11 | .. contents:: 12 | 13 | NumPy Arrays as Input Arguments 14 | ******************************* 15 | 16 | NumPy arrays can be passed as ``VARIANT`` arrays arguments. The array is 17 | converted to a SAFEARRAY according to its type. The type conversion 18 | is defined by the ``numpy.ctypeslib`` module. The following table 19 | shows type conversions that can be performed quickly by (nearly) direct 20 | conversion of a numpy array to a SAFEARRAY. Arrays with type that do not 21 | appear in this table, including object arrays, can still be converted to 22 | SAFEARRAYs on an item-by-item basis. 23 | 24 | +------------------------------------------------+---------------+ 25 | | NumPy type | VARIANT type | 26 | +================================================+===============+ 27 | | ``int8`` | VT_I1 | 28 | +------------------------------------------------+---------------+ 29 | | ``int16``, ``short`` | VT_I2 | 30 | +------------------------------------------------+---------------+ 31 | | ``int32``, ``int``, ``intc``, ``int_`` | VT_I4 | 32 | +------------------------------------------------+---------------+ 33 | | ``int64``, ``long``, ``longlong``, ``intp`` | VT_I8 | 34 | +------------------------------------------------+---------------+ 35 | | ``uint8``, ``ubyte`` | VT_UI1 | 36 | +------------------------------------------------+---------------+ 37 | | ``uint16``, ``ushort`` | VT_UI2 | 38 | +------------------------------------------------+---------------+ 39 | | ``uint32``, ``uint``, ``uintc`` | VT_UI4 | 40 | +------------------------------------------------+---------------+ 41 | | ``uint64``, ``ulonglong``, ``uintp`` | VT_UI8 | 42 | +------------------------------------------------+---------------+ 43 | | ``float32`` | VT_R4 | 44 | +------------------------------------------------+---------------+ 45 | | ``float64``, ``float_`` | VT_R8 | 46 | +------------------------------------------------+---------------+ 47 | | ``datetime64`` | VT_DATE | 48 | +------------------------------------------------+---------------+ 49 | 50 | NumPy Arrays as Output Arguments 51 | ******************************** 52 | 53 | By default, |comtypes| converts SAFEARRAY output arguments to tuples of 54 | python objects on an item-by-item basis. When dealing with large 55 | SAFEARRAYs, this conversion can be costly. Comtypes provides a the 56 | ``safearray_as_ndarray`` context manager (from ``comtypes.safearray``) 57 | for modifying this behavior to return a NumPy array. This altered 58 | behavior is to put an ndarray over a copy of the SAFEARRAY's memory, 59 | which is faster than calling into python for each item. When this fails, 60 | a NumPy array can still be created on an item-by-item basis. The context 61 | manager is thread-safe, in that usage of the context manager on one 62 | thread does not affect behavior on other threads. 63 | 64 | This is a hypothetical example of using the context manager. The context 65 | manager can be used around any property or method call to retrieve a 66 | NumPy array rather than a tuple. 67 | 68 | 69 | .. sourcecode:: python 70 | 71 | """Sample demonstrating use of safearray_as_ndarray context manager """ 72 | 73 | from comtypes.safearray import safearray_as_ndarray 74 | 75 | # Hypothetically, this returns a SAFEARRAY as a tuple 76 | data1 = some_interface.some_property 77 | 78 | # This will return a NumPy array, and will be faster for basic types. 79 | with safearray_as_ndarray: 80 | data2 = some_interface.some_property 81 | 82 | 83 | .. |comtypes| replace:: ``comtypes`` 84 | -------------------------------------------------------------------------------- /comtypes/test/test_dispifc_safearrays.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import byref, c_double, pointer 3 | 4 | import comtypes 5 | import comtypes.safearray 6 | from comtypes import CLSCTX_LOCAL_SERVER 7 | from comtypes.client import CreateObject, GetModule 8 | 9 | ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" 10 | 11 | try: 12 | GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0)) 13 | import comtypes.gen.ComtypesCppTestSrvLib as ComtypesCppTestSrvLib 14 | 15 | IMPORT_FAILED = False 16 | except (ImportError, OSError): 17 | IMPORT_FAILED = True 18 | 19 | 20 | @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") 21 | class Test_DispMethods(unittest.TestCase): 22 | """Test dispmethods with safearray and safearray pointer parameters.""" 23 | 24 | UNPACKED_ZERO_VALS = tuple(0.0 for _ in range(10)) 25 | UNPACKED_CONSECUTIVE_VALS = tuple(float(i) for i in range(10)) 26 | 27 | def _create_dispifc(self) -> "ComtypesCppTestSrvLib.IDispSafearrayParamTest": 28 | # Explicitely ask for the dispinterface of the component. 29 | return CreateObject( 30 | "Comtypes.DispSafearrayParamTest", 31 | clsctx=CLSCTX_LOCAL_SERVER, 32 | interface=ComtypesCppTestSrvLib.IDispSafearrayParamTest, 33 | ) 34 | 35 | def _create_zero_array(self): 36 | return comtypes.safearray._midlSAFEARRAY(c_double).create( 37 | [c_double() for _ in range(10)] 38 | ) 39 | 40 | def _create_consecutive_array(self): 41 | return comtypes.safearray._midlSAFEARRAY(c_double).create( 42 | [c_double(i) for i in range(10)] 43 | ) 44 | 45 | def test_inout_byref(self): 46 | dispifc = self._create_dispifc() 47 | # Passing a safearray by reference to a method that has declared the parameter 48 | # as [in, out] we expect modifications of the safearray on the server side to 49 | # also change the safearray on the client side. 50 | test_array = self._create_zero_array() 51 | self.assertEqual(test_array.unpack(), self.UNPACKED_ZERO_VALS) 52 | dispifc.InitArray(byref(test_array)) 53 | self.assertEqual(test_array.unpack(), self.UNPACKED_CONSECUTIVE_VALS) 54 | 55 | def test_inout_pointer(self): 56 | dispifc = self._create_dispifc() 57 | # Passing a safearray pointer to a method that has declared the parameter 58 | # as [in, out] we expect modifications of the safearray on the server side to 59 | # also change the safearray on the client side. 60 | test_array = self._create_zero_array() 61 | self.assertEqual(test_array.unpack(), self.UNPACKED_ZERO_VALS) 62 | dispifc.InitArray(pointer(test_array)) 63 | self.assertEqual(test_array.unpack(), self.UNPACKED_CONSECUTIVE_VALS) 64 | 65 | def test_in_safearray(self): 66 | # Passing a safearray to a method that has declared the parameter just as [in] 67 | # we expect modifications of the safearray on the server side NOT to change 68 | # the safearray on the client side. 69 | # We also need to test if the safearray gets properly passed to the method on 70 | # the server side. For this, the 'VerifyArray' method returns 'True' if 71 | # the safearray items have values equal to the initialization values 72 | # provided by 'InitArray'. 73 | for sa, expected, unpacked_content in [ 74 | (self._create_consecutive_array(), True, self.UNPACKED_CONSECUTIVE_VALS), 75 | # Also perform the inverted test. For this, create a safearray with zeros. 76 | (self._create_zero_array(), False, self.UNPACKED_ZERO_VALS), 77 | ]: 78 | with self.subTest(expected=expected, unpacked_content=unpacked_content): 79 | # Perform the check on initialization values. 80 | self.assertEqual(self._create_dispifc().VerifyArray(sa), expected) 81 | # Check if the safearray is unchanged although the method 82 | # modifies the safearray on the server side. 83 | self.assertEqual(sa.unpack(), unpacked_content) 84 | 85 | 86 | if __name__ == "__main__": 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /comtypes/test/TestDispServer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | 5 | logging.basicConfig() 6 | ##logging.basicConfig(level=logging.DEBUG) 7 | ##logger = logging.getLogger(__name__) 8 | 9 | # Add comtypes to sys.path (if this is run from a SVN checkout) 10 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), r"..\.."))) 11 | 12 | import comtypes 13 | import comtypes.connectionpoints 14 | import comtypes.server.connectionpoints 15 | from comtypes.hresult import S_OK 16 | 17 | ################################################################ 18 | 19 | # Create the wrapper in the comtypes.gen package, it will be named 20 | # TestComServerLib; the name is derived from the 'library ' statement 21 | # in the IDL file 22 | if not hasattr(sys, "frozen"): 23 | import comtypes.client 24 | 25 | # pathname of the type library file 26 | tlbfile = os.path.join(os.path.dirname(__file__), "TestDispServer.tlb") 27 | # if running as frozen app (dll or exe), the wrapper should be in 28 | # the library archive, so we don't need to generate it. 29 | comtypes.client.GetModule(tlbfile) 30 | 31 | # Import the wrapper 32 | from comtypes.gen import TestDispServerLib 33 | 34 | ################################################################ 35 | 36 | 37 | # Implement the CoClass by defining a subclass of the 38 | # TestDispServerLib.TestDispServer class in the wrapper file. The 39 | # COMObject base class provides default implementations of the 40 | # IUnknown, IDispatch, IPersist, IProvideClassInfo, 41 | # IProvideClassInfo2, and ISupportErrorInfo interfaces. 42 | # 43 | # The ConnectableObjectMixin class provides connectionpoints (events). 44 | class TestDispServer( 45 | TestDispServerLib.TestDispServer, # the coclass from the typelib wrapper 46 | comtypes.server.connectionpoints.ConnectableObjectMixin, 47 | ): 48 | # The default interface from the typelib MUST be the first 49 | # interface, other interfaces can follow 50 | 51 | _com_interfaces_ = TestDispServerLib.TestDispServer._com_interfaces_ + [ 52 | comtypes.connectionpoints.IConnectionPointContainer 53 | ] 54 | 55 | # registry entries 56 | _reg_threading_ = "Both" 57 | _reg_progid_ = "TestDispServerLib.TestDispServer.1" 58 | _reg_novers_progid_ = "TestDispServerLib.TestDispServer" 59 | _reg_desc_ = "comtypes COM server sample for testing" 60 | _reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER | comtypes.CLSCTX_LOCAL_SERVER 61 | 62 | ################################ 63 | # DTestDispServer methods 64 | 65 | def DTestDispServer_eval(self, this, expr, presult): 66 | self.Fire_Event(0, "EvalStarted", expr) 67 | # The following two are equivalent, but the former is more generic: 68 | presult[0] = eval(expr) 69 | ##presult[0].value = eval(expr) 70 | self.Fire_Event(0, "EvalCompleted", expr, presult[0].value) 71 | return S_OK 72 | 73 | def DTestDispServer_eval2(self, expr): 74 | self.Fire_Event(0, "EvalStarted", expr) 75 | result = eval(expr) 76 | self.Fire_Event(0, "EvalCompleted", expr, result) 77 | return result 78 | 79 | def DTestDispServer__get_id(self, this, pid): 80 | pid[0] = id(self) 81 | return S_OK 82 | 83 | def DTestDispServer_Exec(self, this, what): 84 | exec(what) 85 | return S_OK 86 | 87 | def DTestDispServer_Exec2(self, what): 88 | exec(what) 89 | 90 | _name = "spam, spam, spam" 91 | 92 | # Implementation of the DTestDispServer::Name propget 93 | def DTestDispServer__get_name(self, this, pname): 94 | pname[0] = self._name 95 | return S_OK 96 | 97 | # Implementation of the DTestDispServer::Name propput 98 | def DTestDispServer__set_name(self, this, name): 99 | self._name = name 100 | return S_OK 101 | 102 | # Implementation of the DTestDispServer::SetName method 103 | def DTestDispServer_sEtNaMe(self, name): 104 | self._name = name 105 | 106 | 107 | if __name__ == "__main__": 108 | from comtypes.server.register import UseCommandLine 109 | 110 | UseCommandLine(TestDispServer) 111 | -------------------------------------------------------------------------------- /comtypes/_meta.py: -------------------------------------------------------------------------------- 1 | # comtypes._meta helper module 2 | import sys 3 | from ctypes import POINTER, c_void_p, cast 4 | 5 | import comtypes 6 | 7 | ################################################################ 8 | # metaclass for CoClass (in comtypes/__init__.py) 9 | 10 | 11 | def _wrap_coclass(self): 12 | # We are an IUnknown pointer, represented as a c_void_p instance, 13 | # but we really want this interface: 14 | itf = self._com_interfaces_[0] 15 | punk = cast(self, POINTER(itf)) 16 | result = punk.QueryInterface(itf) 17 | result.__dict__["__clsid"] = str(self._reg_clsid_) 18 | return result 19 | 20 | 21 | def _coclass_from_param(cls, obj): 22 | if isinstance(obj, (cls._com_interfaces_[0], cls)): 23 | return obj 24 | raise TypeError(obj) 25 | 26 | 27 | # 28 | # The mro() of a POINTER(App) type, where class App is a subclass of CoClass: 29 | # 30 | # POINTER(App) 31 | # App 32 | # CoClass 33 | # c_void_p 34 | # _SimpleCData 35 | # _CData 36 | # object 37 | 38 | 39 | class _coclass_meta(type): 40 | # metaclass for CoClass 41 | # 42 | # When a CoClass subclass is created, create a POINTER(...) type 43 | # for that class, with bases and c_void_p. Also, the 44 | # POINTER(...) type gets a __ctypes_from_outparam__ method which 45 | # will QueryInterface for the default interface: the first one on 46 | # the coclass' _com_interfaces_ list. 47 | def __new__(cls, name, bases, namespace): 48 | self = type.__new__(cls, name, bases, namespace) 49 | if bases == (object,): 50 | # HACK: Could this conditional branch be removed since it is never reached? 51 | # Since definition is `class CoClass(COMObject, metaclass=_coclass_meta)`, 52 | # the `bases` parameter passed to the `_coclass_meta.__new__` would be 53 | # `(COMObject,)`. 54 | # Moreover, since the `COMObject` derives from `object` and does not specify 55 | # a metaclass, `(object,)` will not be passed as the `bases` parameter 56 | # to the `_coclass_meta.__new__`. 57 | # The reason for this implementation might be a remnant of the differences 58 | # in how metaclasses work between Python 3.x and Python 2.x. 59 | # If there are no problems with the versions of Python that `comtypes` 60 | # supports, this removal could make the process flow easier to understand. 61 | return self 62 | # XXX We should insist that a _reg_clsid_ is present. 63 | if "_reg_clsid_" in namespace: 64 | clsid = namespace["_reg_clsid_"] 65 | comtypes.com_coclass_registry[str(clsid)] = self # type: ignore 66 | 67 | # `_coclass_pointer_meta` is a subclass inherited from `_coclass_meta`. 68 | # In other words, when the `__new__` method of this metaclass is called, an 69 | # instance of `_coclass_pointer_meta` might be created and assigned to `self`. 70 | if isinstance(self, _coclass_pointer_meta): 71 | # `self` is the `_coclass_pointer_meta` type or a `POINTER(coclass)` type. 72 | # Prevent creating/registering a pointer to a pointer (to a pointer...), 73 | # or specifying the metaclass type instance in the `bases` parameter when 74 | # instantiating it, which would lead to infinite recursion. 75 | # Depending on a version or revision of Python, this may be essential. 76 | return self 77 | 78 | p = _coclass_pointer_meta( 79 | f"POINTER({self.__name__})", 80 | (self, c_void_p), 81 | { 82 | "__ctypes_from_outparam__": _wrap_coclass, 83 | "from_param": classmethod(_coclass_from_param), 84 | }, 85 | ) 86 | if sys.version_info >= (3, 14): 87 | self.__pointer_type__ = p 88 | else: 89 | from ctypes import _pointer_type_cache # type: ignore 90 | 91 | _pointer_type_cache[self] = p 92 | 93 | return self 94 | 95 | 96 | # will not work if we change the order of the two base classes! 97 | class _coclass_pointer_meta(type(c_void_p), _coclass_meta): 98 | # metaclass for CoClass pointer 99 | 100 | pass # no functionality, but needed to avoid a metaclass conflict 101 | -------------------------------------------------------------------------------- /comtypes/test/test_dispifc_records.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import byref, pointer 3 | 4 | from comtypes import CLSCTX_LOCAL_SERVER 5 | from comtypes.client import CreateObject, GetModule 6 | 7 | ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" 8 | 9 | try: 10 | GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0)) 11 | import comtypes.gen.ComtypesCppTestSrvLib as ComtypesCppTestSrvLib 12 | 13 | IMPORT_FAILED = False 14 | except (ImportError, OSError): 15 | IMPORT_FAILED = True 16 | 17 | 18 | @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") 19 | class Test_DispMethods(unittest.TestCase): 20 | """Test dispmethods with record and record pointer parameters.""" 21 | 22 | EXPECTED_INITED_QUESTIONS = "The meaning of life, the universe and everything?" 23 | 24 | def _create_dispifc(self) -> "ComtypesCppTestSrvLib.IDispRecordParamTest": 25 | # Explicitely ask for the dispinterface of the component. 26 | return CreateObject( 27 | "Comtypes.DispRecordParamTest", 28 | clsctx=CLSCTX_LOCAL_SERVER, 29 | interface=ComtypesCppTestSrvLib.IDispRecordParamTest, 30 | ) 31 | 32 | def test_inout_byref(self): 33 | dispifc = self._create_dispifc() 34 | # Passing a record by reference to a method that has declared the parameter 35 | # as [in, out] we expect modifications of the record on the server side to 36 | # also change the record on the client side. 37 | test_record = ComtypesCppTestSrvLib.StructRecordParamTest() 38 | self.assertEqual(test_record.question, None) 39 | self.assertEqual(test_record.answer, 0) 40 | self.assertEqual(test_record.needs_clarification, False) 41 | dispifc.InitRecord(byref(test_record)) 42 | self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS) 43 | self.assertEqual(test_record.answer, 42) 44 | self.assertEqual(test_record.needs_clarification, True) 45 | 46 | def test_inout_pointer(self): 47 | dispifc = self._create_dispifc() 48 | # Passing a record pointer to a method that has declared the parameter 49 | # as [in, out] we expect modifications of the record on the server side to 50 | # also change the record on the client side. 51 | test_record = ComtypesCppTestSrvLib.StructRecordParamTest() 52 | self.assertEqual(test_record.question, None) 53 | self.assertEqual(test_record.answer, 0) 54 | self.assertEqual(test_record.needs_clarification, False) 55 | dispifc.InitRecord(pointer(test_record)) 56 | self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS) 57 | self.assertEqual(test_record.answer, 42) 58 | self.assertEqual(test_record.needs_clarification, True) 59 | 60 | def test_in_record(self): 61 | # Passing a record to a method that has declared the parameter just as [in] 62 | # we expect modifications of the record on the server side NOT to change 63 | # the record on the client side. 64 | # We also need to test if the record gets properly passed to the method on 65 | # the server side. For this, the 'VerifyRecord' method returns 'True' if 66 | # all record fields have values equivalent to the initialization values 67 | # provided by 'InitRecord'. 68 | inited_record = ComtypesCppTestSrvLib.StructRecordParamTest() 69 | inited_record.question = self.EXPECTED_INITED_QUESTIONS 70 | inited_record.answer = 42 71 | inited_record.needs_clarification = True 72 | for rec, expected, (q, a, nc) in [ 73 | (inited_record, True, (self.EXPECTED_INITED_QUESTIONS, 42, True)), 74 | # Also perform the inverted test. For this, create a blank record. 75 | (ComtypesCppTestSrvLib.StructRecordParamTest(), False, (None, 0, False)), 76 | ]: 77 | with self.subTest(expected=expected, q=q, a=a, nc=nc): 78 | # Perform the check on initialization values. 79 | self.assertEqual(self._create_dispifc().VerifyRecord(rec), expected) 80 | self.assertEqual(rec.question, q) 81 | # Check if the 'answer' field is unchanged although the method 82 | # modifies this field on the server side. 83 | self.assertEqual(rec.answer, a) 84 | self.assertEqual(rec.needs_clarification, nc) 85 | 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /comtypes/test/test_midl_safearray_create.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ctypes import HRESULT, POINTER, c_int, pointer 3 | 4 | import comtypes 5 | import comtypes.safearray 6 | import comtypes.typeinfo 7 | from comtypes import CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER 8 | from comtypes.client import CreateObject, GetModule 9 | 10 | GetModule("UIAutomationCore.dll") 11 | from comtypes.gen.UIAutomationClient import CUIAutomation, IUIAutomation 12 | 13 | ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" 14 | 15 | try: 16 | GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0)) 17 | from comtypes.gen.ComtypesCppTestSrvLib import ( 18 | IDispSafearrayParamTest, 19 | StructRecordParamTest, 20 | ) 21 | 22 | IMPORT_FAILED = False 23 | except (ImportError, OSError): 24 | IMPORT_FAILED = True 25 | 26 | 27 | class Test_midlSAFEARRAY_create(unittest.TestCase): 28 | def test_iunk(self): 29 | extra = pointer(IUIAutomation._iid_) 30 | iuia = CreateObject( 31 | CUIAutomation().IPersist_GetClassID(), 32 | interface=IUIAutomation, 33 | clsctx=CLSCTX_INPROC_SERVER, 34 | ) 35 | sa_type = comtypes.safearray._midlSAFEARRAY(POINTER(IUIAutomation)) 36 | for ptn, sa in [ 37 | ("with extra", sa_type.create([iuia], extra=extra)), 38 | ("without extra", sa_type.create([iuia])), 39 | ]: 40 | with self.subTest(ptn=ptn): 41 | (unpacked,) = sa.unpack() 42 | self.assertIsInstance(unpacked, POINTER(IUIAutomation)) 43 | 44 | @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") 45 | def test_idisp(self): 46 | extra = pointer(IDispSafearrayParamTest._iid_) 47 | idisp = CreateObject( 48 | "Comtypes.DispSafearrayParamTest", 49 | clsctx=CLSCTX_LOCAL_SERVER, 50 | interface=IDispSafearrayParamTest, 51 | ) 52 | sa_type = comtypes.safearray._midlSAFEARRAY(POINTER(IDispSafearrayParamTest)) 53 | for ptn, sa in [ 54 | ("with extra", sa_type.create([idisp], extra=extra)), 55 | ("without extra", sa_type.create([idisp])), 56 | ]: 57 | with self.subTest(ptn=ptn): 58 | (unpacked,) = sa.unpack() 59 | self.assertIsInstance(unpacked, POINTER(IDispSafearrayParamTest)) 60 | 61 | @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") 62 | def test_record(self): 63 | extra = comtypes.typeinfo.GetRecordInfoFromGuids( 64 | *StructRecordParamTest._recordinfo_ 65 | ) 66 | record = StructRecordParamTest() 67 | record.question = "The meaning of life, the universe and everything?" 68 | record.answer = 42 69 | record.needs_clarification = True 70 | sa_type = comtypes.safearray._midlSAFEARRAY(StructRecordParamTest) 71 | for ptn, sa in [ 72 | ("with extra", sa_type.create([record], extra=extra)), 73 | ("without extra", sa_type.create([record])), 74 | ]: 75 | with self.subTest(ptn=ptn): 76 | (unpacked,) = sa.unpack() 77 | self.assertIsInstance(unpacked, StructRecordParamTest) 78 | self.assertEqual( 79 | unpacked.question, 80 | "The meaning of life, the universe and everything?", 81 | ) 82 | self.assertEqual(unpacked.answer, 42) 83 | self.assertEqual(unpacked.needs_clarification, True) 84 | 85 | def test_HRESULT(self): 86 | hr = HRESULT(1) 87 | sa_type = comtypes.safearray._midlSAFEARRAY(HRESULT) 88 | with self.assertRaises(TypeError): 89 | sa_type.create([hr], extra=None) 90 | with self.assertRaises(TypeError): 91 | sa_type.create([hr]) 92 | 93 | def test_ctype(self): 94 | extra = None 95 | cdata = c_int(1) 96 | sa_type = comtypes.safearray._midlSAFEARRAY(c_int) 97 | for ptn, sa in [ 98 | ("with extra", sa_type.create([cdata], extra=extra)), 99 | ("without extra", sa_type.create([cdata])), 100 | ]: 101 | with self.subTest(ptn=ptn): 102 | (unpacked,) = sa.unpack() 103 | self.assertIsInstance(unpacked, int) 104 | self.assertEqual(unpacked, 1) 105 | 106 | 107 | if __name__ == "__main__": 108 | unittest.main() 109 | --------------------------------------------------------------------------------