portacom 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- portacom-0.1.0/LICENSE +21 -0
- portacom-0.1.0/PKG-INFO +75 -0
- portacom-0.1.0/README.md +48 -0
- portacom-0.1.0/pyproject.toml +37 -0
- portacom-0.1.0/setup.cfg +4 -0
- portacom-0.1.0/src/portacom/__init__.py +244 -0
- portacom-0.1.0/src/portacom/_abi.py +196 -0
- portacom-0.1.0/src/portacom/_activation.py +178 -0
- portacom-0.1.0/src/portacom/_dispatch.py +638 -0
- portacom-0.1.0/src/portacom/_errors.py +213 -0
- portacom-0.1.0/src/portacom/_interface.py +328 -0
- portacom-0.1.0/src/portacom/_loader.py +127 -0
- portacom-0.1.0/src/portacom/_marshal.py +764 -0
- portacom-0.1.0/src/portacom/_memory.py +408 -0
- portacom-0.1.0/src/portacom/_platform.py +60 -0
- portacom-0.1.0/src/portacom/_safearray.py +477 -0
- portacom-0.1.0/src/portacom/_server.py +327 -0
- portacom-0.1.0/src/portacom/_types.py +470 -0
- portacom-0.1.0/src/portacom/_variant.py +248 -0
- portacom-0.1.0/src/portacom/_vtable.py +44 -0
- portacom-0.1.0/src/portacom/codegen.py +129 -0
- portacom-0.1.0/src/portacom.egg-info/PKG-INFO +75 -0
- portacom-0.1.0/src/portacom.egg-info/SOURCES.txt +23 -0
- portacom-0.1.0/src/portacom.egg-info/dependency_links.txt +1 -0
- portacom-0.1.0/src/portacom.egg-info/top_level.txt +1 -0
portacom-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mahmoud Komaiha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
portacom-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: portacom
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Call classic COM interfaces through ctypes on Windows, Linux and macOS
|
|
5
|
+
Author-email: Mahmoud Komaiha <mkomaiha@umich.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mkomaiha/portacom
|
|
8
|
+
Project-URL: Repository, https://github.com/mkomaiha/portacom
|
|
9
|
+
Project-URL: Issues, https://github.com/mkomaiha/portacom/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/mkomaiha/portacom/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: com,ctypes,winmd,iunknown,ffi,cross-platform
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# portacom
|
|
29
|
+
|
|
30
|
+
The runtime for bindings generated by
|
|
31
|
+
[winmd2py](https://pypi.org/project/winmd2py/): call classic (IUnknown-based)
|
|
32
|
+
COM interfaces through ctypes on Windows, Linux and macOS, and implement them
|
|
33
|
+
in Python. Pure Python, no dependencies.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install portacom
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Generate bindings from a `.winmd` with `winmd2py`, then (here for MiniCOM's
|
|
40
|
+
example library on Linux, whose `BOOL` and BSTRs differ from Windows'):
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import ctypes, portacom
|
|
44
|
+
|
|
45
|
+
minicom = portacom.Abi("minicom", typedefs={"BOOL": ctypes.c_long}, allocator=portacom.PlainAllocator())
|
|
46
|
+
portacom.use_abi("my_bindings", minicom) # before importing the bindings
|
|
47
|
+
import my_bindings.Example as example
|
|
48
|
+
import my_bindings.Registry as api
|
|
49
|
+
|
|
50
|
+
registry = portacom.co_create_instance(api.Registry, api.IRegistry, library="libExampleLib.so")
|
|
51
|
+
calc = registry.NewCalculator() # [out] arguments are returned
|
|
52
|
+
print(calc.Add(1, 2)) # 3
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Callback(portacom.ComObject): # implement an interface in Python
|
|
56
|
+
_interfaces_ = (example.ICalcCb,)
|
|
57
|
+
|
|
58
|
+
def Message(self, text):
|
|
59
|
+
print("C++ says", text)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
calc.SetCallback(Callback())
|
|
63
|
+
calc.QueryInterface(example.ICalc2).GetValue2() # 43; C++ calls back: "C++ says GetValue2 called"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A failed HRESULT raises `portacom.COMError`. BSTRs, VARIANTs, SAFEARRAYs,
|
|
67
|
+
GUIDs and enums convert to and from Python values, and `portacom.Dispatch`
|
|
68
|
+
calls automation (IDispatch) objects by name.
|
|
69
|
+
|
|
70
|
+
For ready-made DirectX Shader Compiler bindings, see
|
|
71
|
+
[portacom-dxc](https://pypi.org/project/portacom-dxc/). Calling conventions,
|
|
72
|
+
ABI profiles and limitations are in the
|
|
73
|
+
[project README](https://github.com/mkomaiha/portacom#readme).
|
|
74
|
+
|
|
75
|
+
MIT license.
|
portacom-0.1.0/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# portacom
|
|
2
|
+
|
|
3
|
+
The runtime for bindings generated by
|
|
4
|
+
[winmd2py](https://pypi.org/project/winmd2py/): call classic (IUnknown-based)
|
|
5
|
+
COM interfaces through ctypes on Windows, Linux and macOS, and implement them
|
|
6
|
+
in Python. Pure Python, no dependencies.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install portacom
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Generate bindings from a `.winmd` with `winmd2py`, then (here for MiniCOM's
|
|
13
|
+
example library on Linux, whose `BOOL` and BSTRs differ from Windows'):
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import ctypes, portacom
|
|
17
|
+
|
|
18
|
+
minicom = portacom.Abi("minicom", typedefs={"BOOL": ctypes.c_long}, allocator=portacom.PlainAllocator())
|
|
19
|
+
portacom.use_abi("my_bindings", minicom) # before importing the bindings
|
|
20
|
+
import my_bindings.Example as example
|
|
21
|
+
import my_bindings.Registry as api
|
|
22
|
+
|
|
23
|
+
registry = portacom.co_create_instance(api.Registry, api.IRegistry, library="libExampleLib.so")
|
|
24
|
+
calc = registry.NewCalculator() # [out] arguments are returned
|
|
25
|
+
print(calc.Add(1, 2)) # 3
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Callback(portacom.ComObject): # implement an interface in Python
|
|
29
|
+
_interfaces_ = (example.ICalcCb,)
|
|
30
|
+
|
|
31
|
+
def Message(self, text):
|
|
32
|
+
print("C++ says", text)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
calc.SetCallback(Callback())
|
|
36
|
+
calc.QueryInterface(example.ICalc2).GetValue2() # 43; C++ calls back: "C++ says GetValue2 called"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A failed HRESULT raises `portacom.COMError`. BSTRs, VARIANTs, SAFEARRAYs,
|
|
40
|
+
GUIDs and enums convert to and from Python values, and `portacom.Dispatch`
|
|
41
|
+
calls automation (IDispatch) objects by name.
|
|
42
|
+
|
|
43
|
+
For ready-made DirectX Shader Compiler bindings, see
|
|
44
|
+
[portacom-dxc](https://pypi.org/project/portacom-dxc/). Calling conventions,
|
|
45
|
+
ABI profiles and limitations are in the
|
|
46
|
+
[project README](https://github.com/mkomaiha/portacom#readme).
|
|
47
|
+
|
|
48
|
+
MIT license.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "portacom"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Call classic COM interfaces through ctypes on Windows, Linux and macOS"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Mahmoud Komaiha", email = "mkomaiha@umich.edu" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = []
|
|
15
|
+
keywords = ["com", "ctypes", "winmd", "iunknown", "ffi", "cross-platform"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Operating System :: Microsoft :: Windows",
|
|
25
|
+
"Operating System :: POSIX :: Linux",
|
|
26
|
+
"Operating System :: MacOS",
|
|
27
|
+
"Topic :: Software Development :: Libraries",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/mkomaiha/portacom"
|
|
32
|
+
Repository = "https://github.com/mkomaiha/portacom"
|
|
33
|
+
Issues = "https://github.com/mkomaiha/portacom/issues"
|
|
34
|
+
Changelog = "https://github.com/mkomaiha/portacom/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
portacom-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""portacom: classic COM from Python, through ctypes, on Windows, Linux and macOS.
|
|
2
|
+
|
|
3
|
+
Bindings generated by winmd2py import this runtime. Using them:
|
|
4
|
+
|
|
5
|
+
import ctypes, portacom
|
|
6
|
+
import my_bindings.My.Namespace as api
|
|
7
|
+
|
|
8
|
+
obj = api.CreateThing(api.CLSID_Thing, api.IThing) # riid/ppv: pass the class
|
|
9
|
+
value = obj.GetValue() # [out] arguments are returned
|
|
10
|
+
other = obj.query(api.IOther) # None if not implemented
|
|
11
|
+
|
|
12
|
+
class Callback(portacom.ComObject): # implementing an interface
|
|
13
|
+
_interfaces_ = (api.ICallback,)
|
|
14
|
+
|
|
15
|
+
def Notify(self, message):
|
|
16
|
+
print(message)
|
|
17
|
+
|
|
18
|
+
Errors raise portacom.COMError.
|
|
19
|
+
|
|
20
|
+
A .winmd describes the Windows ABI. Where a library built for Linux or macOS
|
|
21
|
+
differs (BOOL or LONG width, BSTR allocation), declare it in the bindings'
|
|
22
|
+
__init__.py, before their modules are imported (Windows keeps the metadata's):
|
|
23
|
+
|
|
24
|
+
portacom.use_abi(__name__, portacom.Abi("mylib", library="mylib.dll", typedefs={"BOOL": ctypes.c_long}))
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from ._abi import WINDOWS, Abi, abi_for, use_abi
|
|
30
|
+
from ._activation import (
|
|
31
|
+
CLSCTX_ALL,
|
|
32
|
+
CLSCTX_INPROC_SERVER,
|
|
33
|
+
CoClass,
|
|
34
|
+
clsid_from_progid,
|
|
35
|
+
co_create_instance,
|
|
36
|
+
co_initialize,
|
|
37
|
+
create_instance,
|
|
38
|
+
exported_factory,
|
|
39
|
+
)
|
|
40
|
+
from ._dispatch import (
|
|
41
|
+
DISPATCH_METHOD,
|
|
42
|
+
DISPATCH_PROPERTYGET,
|
|
43
|
+
DISPATCH_PROPERTYPUT,
|
|
44
|
+
DISPATCH_PROPERTYPUTREF,
|
|
45
|
+
DISPID_NEWENUM,
|
|
46
|
+
DISPID_PROPERTYPUT,
|
|
47
|
+
DISPID_UNKNOWN,
|
|
48
|
+
DISPID_VALUE,
|
|
49
|
+
DISPPARAMS,
|
|
50
|
+
EXCEPINFO,
|
|
51
|
+
Dispatch,
|
|
52
|
+
DispatchObject,
|
|
53
|
+
EnumVARIANT,
|
|
54
|
+
IDispatch,
|
|
55
|
+
IEnumVARIANT,
|
|
56
|
+
)
|
|
57
|
+
from ._errors import (
|
|
58
|
+
E_ABORT,
|
|
59
|
+
E_ACCESSDENIED,
|
|
60
|
+
E_BOUNDS,
|
|
61
|
+
E_FAIL,
|
|
62
|
+
E_INVALIDARG,
|
|
63
|
+
E_NOINTERFACE,
|
|
64
|
+
E_NOT_SET,
|
|
65
|
+
E_NOTIMPL,
|
|
66
|
+
E_OUTOFMEMORY,
|
|
67
|
+
E_POINTER,
|
|
68
|
+
E_UNEXPECTED,
|
|
69
|
+
REGDB_E_CLASSNOTREG,
|
|
70
|
+
S_FALSE,
|
|
71
|
+
S_OK,
|
|
72
|
+
COMError,
|
|
73
|
+
check,
|
|
74
|
+
failed,
|
|
75
|
+
hresult_text,
|
|
76
|
+
set_callback_error_handler,
|
|
77
|
+
succeeded,
|
|
78
|
+
)
|
|
79
|
+
from ._interface import Function, IUnknown
|
|
80
|
+
from ._loader import library_candidates, load_library, register_library
|
|
81
|
+
from ._marshal import COMOUTPTR, CONST, IN, OPTIONAL, OUT, RETVAL, Param, Unsupported
|
|
82
|
+
from ._memory import (
|
|
83
|
+
Allocator,
|
|
84
|
+
LibraryAllocator,
|
|
85
|
+
OleAllocator,
|
|
86
|
+
PlainAllocator,
|
|
87
|
+
WindowsLayoutAllocator,
|
|
88
|
+
default_allocator,
|
|
89
|
+
free_task_memory,
|
|
90
|
+
)
|
|
91
|
+
from ._platform import ARCH, FUNCTYPE, NATIVE_COM, WCHAR_SIZE, by_arch, wchar_count
|
|
92
|
+
from ._safearray import SAFEARRAY, SafeArray
|
|
93
|
+
from ._server import ComObject
|
|
94
|
+
from ._types import (
|
|
95
|
+
GUID,
|
|
96
|
+
HRESULT,
|
|
97
|
+
PSTR,
|
|
98
|
+
PWSTR,
|
|
99
|
+
VARIANT_BOOL,
|
|
100
|
+
Array,
|
|
101
|
+
BSTRBase,
|
|
102
|
+
Enum,
|
|
103
|
+
Flags,
|
|
104
|
+
LazyModule,
|
|
105
|
+
Ptr,
|
|
106
|
+
Structure,
|
|
107
|
+
Union,
|
|
108
|
+
bitfields,
|
|
109
|
+
finish_layouts,
|
|
110
|
+
layout,
|
|
111
|
+
set_fields,
|
|
112
|
+
)
|
|
113
|
+
from ._variant import (
|
|
114
|
+
VARIANT,
|
|
115
|
+
VT_ARRAY,
|
|
116
|
+
VT_BOOL,
|
|
117
|
+
VT_BSTR,
|
|
118
|
+
VT_CY,
|
|
119
|
+
VT_DATE,
|
|
120
|
+
VT_DISPATCH,
|
|
121
|
+
VT_EMPTY,
|
|
122
|
+
VT_ERROR,
|
|
123
|
+
VT_I1,
|
|
124
|
+
VT_I2,
|
|
125
|
+
VT_I4,
|
|
126
|
+
VT_I8,
|
|
127
|
+
VT_INT,
|
|
128
|
+
VT_NULL,
|
|
129
|
+
VT_R4,
|
|
130
|
+
VT_R8,
|
|
131
|
+
VT_UI1,
|
|
132
|
+
VT_UI2,
|
|
133
|
+
VT_UI4,
|
|
134
|
+
VT_UI8,
|
|
135
|
+
VT_UINT,
|
|
136
|
+
VT_UNKNOWN,
|
|
137
|
+
VT_VARIANT,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
__version__ = "0.1.0"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# The API for using bindings. What generated code needs is in portacom.codegen;
|
|
144
|
+
# its names are still importable from here (portacom.layout, ...) for one
|
|
145
|
+
# release, but not part of the API.
|
|
146
|
+
__all__ = [
|
|
147
|
+
# using bindings
|
|
148
|
+
"IUnknown",
|
|
149
|
+
"ComObject",
|
|
150
|
+
"Dispatch",
|
|
151
|
+
"DispatchObject",
|
|
152
|
+
"EnumVARIANT",
|
|
153
|
+
"IDispatch",
|
|
154
|
+
"IEnumVARIANT",
|
|
155
|
+
"DISPPARAMS",
|
|
156
|
+
"EXCEPINFO",
|
|
157
|
+
"DISPATCH_METHOD",
|
|
158
|
+
"DISPATCH_PROPERTYGET",
|
|
159
|
+
"DISPATCH_PROPERTYPUT",
|
|
160
|
+
"DISPATCH_PROPERTYPUTREF",
|
|
161
|
+
"DISPID_VALUE",
|
|
162
|
+
"DISPID_UNKNOWN",
|
|
163
|
+
"DISPID_PROPERTYPUT",
|
|
164
|
+
"DISPID_NEWENUM",
|
|
165
|
+
"COMError",
|
|
166
|
+
"GUID",
|
|
167
|
+
"VARIANT",
|
|
168
|
+
"SafeArray",
|
|
169
|
+
"SAFEARRAY",
|
|
170
|
+
"PWSTR",
|
|
171
|
+
"PSTR",
|
|
172
|
+
"BSTRBase",
|
|
173
|
+
"CoClass",
|
|
174
|
+
"create_instance",
|
|
175
|
+
"exported_factory",
|
|
176
|
+
"co_create_instance",
|
|
177
|
+
"co_initialize",
|
|
178
|
+
"clsid_from_progid",
|
|
179
|
+
"CLSCTX_ALL",
|
|
180
|
+
"CLSCTX_INPROC_SERVER",
|
|
181
|
+
"free_task_memory",
|
|
182
|
+
"set_callback_error_handler",
|
|
183
|
+
"check",
|
|
184
|
+
"succeeded",
|
|
185
|
+
"failed",
|
|
186
|
+
"hresult_text",
|
|
187
|
+
"S_OK",
|
|
188
|
+
"S_FALSE",
|
|
189
|
+
"E_ABORT",
|
|
190
|
+
"E_ACCESSDENIED",
|
|
191
|
+
"E_BOUNDS",
|
|
192
|
+
"E_FAIL",
|
|
193
|
+
"E_INVALIDARG",
|
|
194
|
+
"E_NOINTERFACE",
|
|
195
|
+
"E_NOTIMPL",
|
|
196
|
+
"E_NOT_SET",
|
|
197
|
+
"E_OUTOFMEMORY",
|
|
198
|
+
"E_POINTER",
|
|
199
|
+
"E_UNEXPECTED",
|
|
200
|
+
"REGDB_E_CLASSNOTREG",
|
|
201
|
+
"VT_EMPTY",
|
|
202
|
+
"VT_NULL",
|
|
203
|
+
"VT_I1",
|
|
204
|
+
"VT_I2",
|
|
205
|
+
"VT_I4",
|
|
206
|
+
"VT_I8",
|
|
207
|
+
"VT_UI1",
|
|
208
|
+
"VT_UI2",
|
|
209
|
+
"VT_UI4",
|
|
210
|
+
"VT_UI8",
|
|
211
|
+
"VT_INT",
|
|
212
|
+
"VT_UINT",
|
|
213
|
+
"VT_R4",
|
|
214
|
+
"VT_R8",
|
|
215
|
+
"VT_CY",
|
|
216
|
+
"VT_DATE",
|
|
217
|
+
"VT_BSTR",
|
|
218
|
+
"VT_DISPATCH",
|
|
219
|
+
"VT_ERROR",
|
|
220
|
+
"VT_BOOL",
|
|
221
|
+
"VT_VARIANT",
|
|
222
|
+
"VT_UNKNOWN",
|
|
223
|
+
"VT_ARRAY",
|
|
224
|
+
# libraries and their ABI
|
|
225
|
+
"register_library",
|
|
226
|
+
"load_library",
|
|
227
|
+
"Abi",
|
|
228
|
+
"use_abi",
|
|
229
|
+
"WINDOWS",
|
|
230
|
+
"Ptr", # for Abi overrides of pointer types
|
|
231
|
+
"Array",
|
|
232
|
+
"Allocator",
|
|
233
|
+
"OleAllocator",
|
|
234
|
+
"WindowsLayoutAllocator",
|
|
235
|
+
"PlainAllocator",
|
|
236
|
+
"LibraryAllocator",
|
|
237
|
+
"default_allocator",
|
|
238
|
+
# the platform
|
|
239
|
+
"NATIVE_COM",
|
|
240
|
+
"WCHAR_SIZE",
|
|
241
|
+
"ARCH",
|
|
242
|
+
"by_arch",
|
|
243
|
+
"wchar_count",
|
|
244
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""ABI profiles: what a library was compiled with that the .winmd cannot say.
|
|
2
|
+
|
|
3
|
+
A .winmd describes the Windows ABI, and the format has no way to say that a
|
|
4
|
+
library built for Linux or macOS differs. So winmd2py generates ABI-neutral
|
|
5
|
+
bindings, and whoever knows the library declares its differences with an Abi
|
|
6
|
+
before importing them (in the bindings' own __init__.py, which winmd2py
|
|
7
|
+
creates once for this):
|
|
8
|
+
|
|
9
|
+
typedefs {typedef name: ctypes type}, for every use of a named typedef
|
|
10
|
+
from the metadata (BOOL, BSTR, ...)
|
|
11
|
+
overrides {"Interface.Method": restype, "Interface.Method.param": type,
|
|
12
|
+
"Function.param": type, "Struct.field": type}, for uses that
|
|
13
|
+
the metadata does not name (LONG, and an IDL "long", are just
|
|
14
|
+
Int32)
|
|
15
|
+
allocator how BSTRs, VARIANTs, task memory and SAFEARRAYs are allocated
|
|
16
|
+
and freed
|
|
17
|
+
library the shared library behind the bindings ("Foo.dll"): where
|
|
18
|
+
co_create_instance() finds CoCreateInstance, and whose own
|
|
19
|
+
exported SysAllocStringLen & co. are used where it has them
|
|
20
|
+
|
|
21
|
+
An Abi describes the library as built for Linux and macOS. On Windows the
|
|
22
|
+
metadata is the truth, so use_abi() binds WINDOWS there, unless the Abi was
|
|
23
|
+
made with on_windows=True. Each generated module asks abi_for() for its Abi
|
|
24
|
+
when it is imported; see use_abi() for how the registration is chosen.
|
|
25
|
+
Without one it is WINDOWS.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from ._memory import Allocator, LazyAllocator, LibraryAllocator, default_allocator
|
|
29
|
+
from ._platform import NATIVE_COM
|
|
30
|
+
from ._types import HRESULT, PSTR, PWSTR, VARIANT_BOOL, BSTRBase
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Abi:
|
|
34
|
+
"""How a library built for Linux and macOS differs from the Windows ABI
|
|
35
|
+
that its .winmd describes.
|
|
36
|
+
|
|
37
|
+
On Windows the library follows the metadata, so use_abi() binds WINDOWS
|
|
38
|
+
instead; on_windows=True is for the rare library that deviates on
|
|
39
|
+
Windows too, and makes use_abi() bind this Abi there as well.
|
|
40
|
+
|
|
41
|
+
library: the shared library behind the bindings, named as the metadata
|
|
42
|
+
names it ("Foo.dll", which is libFoo.so or libFoo.dylib off Windows; see
|
|
43
|
+
load_library), or a path or a loaded ctypes.CDLL. co_create_instance()
|
|
44
|
+
calls its exported CoCreateInstance when not given a library (off
|
|
45
|
+
Windows; on Windows OLE32's is used), and its own exported
|
|
46
|
+
SysAllocStringLen, SysFreeString, SysStringLen, CoTaskMemAlloc,
|
|
47
|
+
CoTaskMemFree and SafeArray* functions allocate the BSTRs and memory, in
|
|
48
|
+
each family that it exports whole (a LibraryAllocator). It is loaded on
|
|
49
|
+
first use, so making an Abi loads nothing.
|
|
50
|
+
|
|
51
|
+
allocator: how BSTRs, task memory and SAFEARRAYs are allocated. With
|
|
52
|
+
library too, it is the fallback for what the library does not export;
|
|
53
|
+
alone, it is used outright. Without either, the default allocator (OLE
|
|
54
|
+
on Windows, the Windows layout on malloc/free elsewhere).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, name, typedefs=None, overrides=None, allocator=None, library=None, on_windows=False):
|
|
58
|
+
self.name = name
|
|
59
|
+
self.typedefs = dict(typedefs or {})
|
|
60
|
+
self.overrides = dict(overrides or {})
|
|
61
|
+
if allocator is not None and not isinstance(allocator, Allocator):
|
|
62
|
+
raise TypeError("allocator must be a portacom.Allocator")
|
|
63
|
+
self.library = library
|
|
64
|
+
self.on_windows = on_windows
|
|
65
|
+
self._fallback = allocator
|
|
66
|
+
self._allocator = allocator if library is None else None
|
|
67
|
+
self._default_allocator = allocator is None and library is None
|
|
68
|
+
# what this ABI's types allocate with, resolved on first use
|
|
69
|
+
self._lazy = LazyAllocator(lambda: self.allocator)
|
|
70
|
+
self._bstr = None
|
|
71
|
+
self._safearray = None
|
|
72
|
+
self._variant = None
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def allocator(self):
|
|
76
|
+
if self._allocator is None:
|
|
77
|
+
if self.library is not None:
|
|
78
|
+
self._allocator = LibraryAllocator(self.library, fallback=self._fallback)
|
|
79
|
+
else:
|
|
80
|
+
self._allocator = default_allocator()
|
|
81
|
+
return self._allocator
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def BSTR(self):
|
|
85
|
+
"""The BSTR type of this ABI, which allocates with its allocator."""
|
|
86
|
+
if self._bstr is None:
|
|
87
|
+
self._bstr = type("BSTR", (BSTRBase,), {"_allocator_": self._lazy, "__qualname__": f"BSTR[{self.name}]"})
|
|
88
|
+
return self._bstr
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def SAFEARRAY(self):
|
|
92
|
+
"""The SAFEARRAY type of this ABI, which allocates with its allocator."""
|
|
93
|
+
if self._safearray is None:
|
|
94
|
+
from ._safearray import SAFEARRAY
|
|
95
|
+
|
|
96
|
+
self._safearray = type(f"SAFEARRAY[{self.name}]", (SAFEARRAY,), {"_allocator_": self._lazy})
|
|
97
|
+
return self._safearray
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def VARIANT(self):
|
|
101
|
+
"""The VARIANT type of this ABI, whose BSTRs and arrays use its allocator."""
|
|
102
|
+
if self._variant is None:
|
|
103
|
+
from ._variant import VARIANT, variant_type
|
|
104
|
+
|
|
105
|
+
if self._default_allocator:
|
|
106
|
+
self._variant = VARIANT # the default allocator, as portacom.VARIANT has
|
|
107
|
+
else:
|
|
108
|
+
self._variant = variant_type(self._lazy, self.name)
|
|
109
|
+
return self._variant
|
|
110
|
+
|
|
111
|
+
def typedef(self, name, default):
|
|
112
|
+
"""The type for a use of the named typedef."""
|
|
113
|
+
if name in self.typedefs:
|
|
114
|
+
return self.typedefs[name]
|
|
115
|
+
special = _SPECIAL.get(name)
|
|
116
|
+
if special is not None:
|
|
117
|
+
return special(self)
|
|
118
|
+
return default
|
|
119
|
+
|
|
120
|
+
def override(self, key, default):
|
|
121
|
+
return self.overrides.get(key, default)
|
|
122
|
+
|
|
123
|
+
def __repr__(self):
|
|
124
|
+
return f"<Abi {self.name!r}>"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
_SPECIAL = {
|
|
128
|
+
"BSTR": lambda abi: abi.BSTR,
|
|
129
|
+
"SAFEARRAY": lambda abi: abi.SAFEARRAY,
|
|
130
|
+
"PWSTR": lambda abi: PWSTR,
|
|
131
|
+
"PCWSTR": lambda abi: PWSTR,
|
|
132
|
+
"PSTR": lambda abi: PSTR,
|
|
133
|
+
"PCSTR": lambda abi: PSTR,
|
|
134
|
+
"HRESULT": lambda abi: HRESULT,
|
|
135
|
+
"VARIANT_BOOL": lambda abi: VARIANT_BOOL,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# The Windows ABI, as the metadata describes it
|
|
139
|
+
WINDOWS = Abi("windows")
|
|
140
|
+
|
|
141
|
+
_registered = {} # key (module path or namespace) -> Abi
|
|
142
|
+
_bound = {} # (module path, namespace) -> Abi, for modules already imported
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _matches(key, name):
|
|
146
|
+
return name == key or name.startswith(key + ".")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _longest(registry, name):
|
|
150
|
+
keys = [key for key in registry if _matches(key, name)]
|
|
151
|
+
return registry[max(keys, key=len)] if keys else None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _resolve(registry, namespace, module):
|
|
155
|
+
"""A key matching the module path wins; then a key matching the namespace."""
|
|
156
|
+
found = _longest(registry, module) if module else None
|
|
157
|
+
if found is None:
|
|
158
|
+
found = _longest(registry, namespace)
|
|
159
|
+
return found or WINDOWS
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def use_abi(key, abi):
|
|
163
|
+
"""Use abi for the generated bindings that key names. Call it before
|
|
164
|
+
importing them (in the bindings' __init__.py): a module's Abi is fixed
|
|
165
|
+
when it is imported.
|
|
166
|
+
|
|
167
|
+
An Abi describes the library as built for Linux and macOS. On Windows
|
|
168
|
+
the metadata is the truth, so there the bindings get WINDOWS, unless abi
|
|
169
|
+
was made with on_windows=True.
|
|
170
|
+
|
|
171
|
+
key is a generated package's import path ("my_bindings", or
|
|
172
|
+
"my_bindings.Windows.Win32"), or a metadata namespace ("Windows.Win32").
|
|
173
|
+
For a module, the longest key that matches its import path wins; only if
|
|
174
|
+
none does, the longest key that matches its namespace. So a package that
|
|
175
|
+
ships bindings (portacom_dxc registers "portacom_dxc._bindings") is not
|
|
176
|
+
affected by a namespace registered for other bindings of that namespace.
|
|
177
|
+
"""
|
|
178
|
+
if not isinstance(abi, Abi):
|
|
179
|
+
raise TypeError(f"use_abi() takes a portacom.Abi, not {type(abi).__name__}")
|
|
180
|
+
if NATIVE_COM and not abi.on_windows:
|
|
181
|
+
abi = WINDOWS
|
|
182
|
+
trial = {**_registered, key: abi}
|
|
183
|
+
for (module, namespace), used in _bound.items():
|
|
184
|
+
if _resolve(trial, namespace, module) is not used:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"{module} was already imported with {used!r}; call portacom.use_abi({key!r}, ...) "
|
|
187
|
+
f"before importing {module}"
|
|
188
|
+
)
|
|
189
|
+
_registered[key] = abi
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def abi_for(namespace, module=None):
|
|
193
|
+
"""The Abi for a generated module, called by the module when imported."""
|
|
194
|
+
abi = _resolve(_registered, namespace, module)
|
|
195
|
+
_bound[(module or namespace, namespace)] = abi
|
|
196
|
+
return abi
|