forked from naksyn/PythonMemoryModule
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcom.py
More file actions
355 lines (297 loc) · 12.1 KB
/
com.py
File metadata and controls
355 lines (297 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import sys
import struct
import ctypes
import functools
from ctypes import HRESULT, byref, cast
import windows
from windows import winproxy
from windows.generated_def.winstructs import *
import windows.generated_def as gdef
from windows.generated_def import RPC_C_IMP_LEVEL_IMPERSONATE, CLSCTX_INPROC_SERVER
from windows.generated_def import interfaces
from windows.generated_def.interfaces import generate_IID, IID
from windows.pycompat import int_types, basestring
# We have windows.com.COMImplementation
# So we need windows.com.COMInterface
COMInterface = interfaces.COMInterface
# Simple raw -> UUID
# "-".join("{:02X}".format(c) for c in struct.unpack("<IHHHBBBBBB", x))
def init():
"""Init COM with some default parameters"""
try:
t = winproxy.CoInitializeEx()
except WindowsError as e:
t = e.winerror
if t:
return t & 0xffffffff
return initsecurity()
def initsecurity(): # Should take some parameters..
return winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
class Dispatch(interfaces.IDispatch):
def TypeInfoCount(self):
count = gdef.UINT()
self.GetTypeInfoCount(count)
return count
def type_info(self, idx):
type_info = TypeInfo()
self.GetTypeInfo(idx, 0, type_info)
return type_info
class TypeInfo(interfaces.ITypeInfo):
def func(self, idx):
res = gdef.LPFUNCDESC()
self.GetFuncDesc(idx, res)
return res
def attr(self):
res = gdef.LPTYPEATTR()
self.GetTypeAttr(res)
return res
def names(self, memid):
size = gdef.UINT()
x = (gdef.BSTR * 10)(*tuple(gdef.BSTR() for i in range(10)))
self.GetNames(memid, x, 10, size)
return x[:size.value]
def docu(self, id):
res = gdef.BSTR()
self.GetDocumentation(id, res, None, None, None)
return res
def create_instance(clsiid, targetinterface, custom_iid=None, context=CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER):
"""A simple wrapper around ``CoCreateInstance <https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx>``"""
if custom_iid is None:
custom_iid = targetinterface.IID
if isinstance(clsiid, basestring):
clsiid = IID.from_string(clsiid)
winproxy.CoCreateInstance(byref(clsiid), None, context, byref(custom_iid), byref(targetinterface))
return targetinterface
def resolve_progid(progid):
clsid = CLSID()
winproxy.CLSIDFromProgID(progid, clsid)
# We just filed the CLSID: refresh the __repr__
clsid.update_strid()
return clsid
# Improved COM object
# Todo: ctypes_generation extended struct ?
class SafeArray(SAFEARRAY):
@classmethod
def of_type(cls, addr, t):
self = cls.from_address(addr)
self.elt_type = t
return self
@classmethod
def from_PSAFEARRAY(self, psafearray):
res = cast(psafearray, POINTER(SafeArray))[0]
return res
def to_list(self, t=None):
if t is None:
if hasattr(self, "elt_type"):
t = self.elt_type
else:
raise ValueError("Missing type of the array")
if self.cDims != 1:
raise NotImplementedError("tagSAFEARRAY if dims != 1")
nb_element = self.rgsabound[0].cElements
llbound = self.rgsabound[0].lLbound
if self.cbElements != ctypes.sizeof(t):
raise ValueError("Size of elements != sizeof(type)")
data = [t.from_address(self.pvData + (i + llbound) * ctypes.sizeof(t)).value for i in range(nb_element)]
return data
#VT_VALUE_TO_TYPE = {
#VT_I2 : SHORT,
#VT_I4 : LONG,
#VT_BSTR : BSTR,
#VT_VARIANT : VARIANT,
#VT_UI1 : UCHAR,
#VT_UI2 : USHORT,
#VT_UI4 : DWORD,
#VT_I8 : LONGLONG,
#VT_UI8 : ULONG64,
#VT_INT : INT,
#VT_UINT : UINT,
#VT_HRESULT : HRESULT,
#VT_PTR : PVOID,
#VT_LPSTR : LPCSTR,
#VT_LPWSTR : LPWSTR,
#}
# VARIANT type checker
# Allow to guess a VARIANT_TYPE og a python value
def never_match(value):
return False
def check_type_null(value):
return value is None
def check_type_i4(value):
# 31 ? as we may want to keep sign :)
return isinstance(value, int_types) and (value).bit_length() <= 32
def check_type_i8(value):
# 63 ? as we may want to keep sign :)
return isinstance(value, int_types) and (value).bit_length() <= 64
def check_type_bstr(value):
return isinstance(value, basestring)
def check_type_bool(value):
return isinstance(value, bool)
def check_type_array(value):
return True
VARIAN_NAME_3_TYPE = [f[1] for f in VARIANT._fields_ if f[0] == "_VARIANT_NAME_3"][0]
empty = object()
class Variant(VARIANT):
def __init__(self, value=empty, type=None):
if type is not None:
self.set_value_and_type(value, type)
return
elif value is empty:
self.vt = VT_EMPTY
return
self.guess_type_and_set_value(value)
# Copy raw-ctypes fields which is a descriptor :)
rawvt = VARIANT.vt
# Most of the value in the colunm[1]
# are attribute of the sub-union _VARIANT_NAME_3
# This union must be ctypes-anonymous for this code to works
# We want to access these directly from the VARIANT
# to allow custom descriptor for complexe type to be referenced here
CHECK_TYPE = [
# Order is important
# as VT_I4 check may match VT_BOOL values
# VT_BOOL check must be before VT_I4 one
(VT_BOOL, "boolVal", check_type_bool),
(VT_I4, "lVal", check_type_i4),
(VT_I8, "llVal", check_type_i8),
(VT_BSTR, "bstrVal", check_type_bstr),
(VT_NULL, None, check_type_null),
(VT_EMPTY, None, never_match),
(VT_DISPATCH, "pdispVal", never_match), # I cannot recognize DISPATCH ptr for now
(VT_UNKNOWN, "punkVal", never_match), # recognise PFW ComInterface ?
# Test: do not allow auto-creation of small int values
# I don't know but a feel it may confuse some API expecting VT_I4
(VT_I2, "iVal", never_match),
(VT_UI1, "bVal", never_match),
]
VARIANT_TYPE_BY_NAME = {f[0]: f[1] for f in VARIAN_NAME_3_TYPE._fields_}
QUICK_CHECK_TYPE = {x: y for x,y, _ in CHECK_TYPE}
def get_vt(self):
rawvt = super(Variant, self).vt
return gdef.VARENUM.mapper[self.rawvt]
def set_vt(self, value):
self.rawvt = value
vt = property(get_vt, set_vt)
def set_value_and_type(self, value, type):
attr = self.QUICK_CHECK_TYPE[type]
# No check: user must be careful about non-match value&type
setattr(self, attr, value)
self.vt = type
def get_value_based_on_type(self):
rawvt = self.rawvt
if rawvt & VT_ARRAY:
realtype = rawvt & ~VT_ARRAY
attr = self.QUICK_CHECK_TYPE[realtype]
attrtype = self.VARIANT_TYPE_BY_NAME[attr]
array = SafeArray.from_PSAFEARRAY(self._VARIANT_NAME_3.parray)
return array.to_list(attrtype)
attr = self.QUICK_CHECK_TYPE[rawvt]
if attr is None:
return None
if attr == "punkVal":
# Quick hack for COM interface type
# Do something clean with CHECK_TYPE ?
x = gdef.IUnknown(self.punkVal)
x.AddRef()
return x
return getattr(self, attr)
def guess_type_and_set_value(self, value):
for t, attr, check in self.CHECK_TYPE:
try:
checkres = check(value)
except TypeError as e:
continue
if checkres:
self.vt = t
if attr is not None:
setattr(self, attr, value)
return True
raise ValueError("Could not guess VT_TYPE for <{0}> of type <{1}>".format(value, type(value)))
value = property(get_value_based_on_type, guess_type_and_set_value)
# quick_check: bypass python lookup-limitation
def generate_getter(vt_type, transfo=(lambda x:x), quick_check=QUICK_CHECK_TYPE):
attr = quick_check[vt_type]
@property
def getter(self):
if not self.rawvt == vt_type:
raise ValueError("Invalid vt-type for attribute expected <{0}> got <{1}>".format(vt_type, self.vt))
return transfo(getattr(self, attr))
return getter
asbstr = generate_getter(VT_BSTR)
aslong = generate_getter(VT_I4)
asbool = generate_getter(VT_BOOL)
asdispatch = generate_getter(VT_DISPATCH, transfo=interfaces.IDispatch)
asshort = generate_getter(VT_I2)
asbyte = generate_getter(VT_UI1)
asunknown = generate_getter(VT_UNKNOWN)
def __repr__(self):
return """<{0} of type {1}>""".format(type(self).__name__, self.vt)
# Deprecated: remove me when test pass :)
# ImprovedVariant.MAPPER = {
# VT_UI1: ImprovedVariant.asbyte.fget,
# VT_I2: ImprovedVariant.asshort.fget,
# VT_DISPATCH: ImprovedVariant.asdispatch.fget,
# VT_BOOL: ImprovedVariant.asbool.fget,
# VT_I4: ImprovedVariant.aslong.fget,
# VT_BSTR: ImprovedVariant.asbstr.fget,
# VT_EMPTY: (lambda x: None),
# VT_NULL: (lambda x: None),
# VT_UNKNOWN: ImprovedVariant.asunknown.fget,
# (VT_ARRAY | VT_BSTR): ImprovedVariant.asbstr_array.fget,
# (VT_ARRAY | VT_I4): ImprovedVariant.aslong_array.fget,
# (VT_ARRAY | VT_UI1): ImprovedVariant.asbyte_array.fget,
# (VT_ARRAY | VT_BOOL): ImprovedVariant.asbool_array.fget
# }
class COMImplementation(object):
"""The base class to implements COM object respecting a given interface"""
IMPLEMENT = None
def get_index_of_method(self, method):
# This code is horrible but not totally my fault
# the PyCFuncPtrObject->index is not exposed to Python..
# repr is: '<COM method offset 2: WinFunctionType at 0x035DDBE8>'
rpr = repr(method)
if not rpr.startswith("<COM method offset ") or ":" not in rpr:
raise ValueError("Could not extract offset of {0}".format(rpr))
return int(rpr[len("<COM method offset "): rpr.index(":")])
def extract_methods_order(self, interface):
index_and_method = sorted((self.get_index_of_method(m),name, m) for name, m in interface._functions_.items())
return index_and_method
def verify_implem(self, interface):
for func_name in interface._functions_:
implem = getattr(self, func_name, None)
if implem is None:
raise ValueError("<{0}> implementing <{1}> has no method <{2}>".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
if not callable(implem):
raise ValueError("{0} implementing <{1}>: <{2}> is not callable".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
return True
def _create_vtable(self, interface):
implems = []
names = []
for index, name, method in self.extract_methods_order(interface):
func_implem = getattr(self, name)
#'this' is a COM-interface of the type we are implementing
types = [method.restype, interface] + list(method.argtypes)
implems.append(ctypes.WINFUNCTYPE(*types)(func_implem))
names.append(name)
class Vtable(ctypes.Structure):
_fields_ = [(name, ctypes.c_void_p) for name in names]
return Vtable(*[ctypes.cast(x, ctypes.c_void_p) for x in implems]), implems
def __init__(self):
self.verify_implem(self.IMPLEMENT)
vtable, implems = self._create_vtable(self.IMPLEMENT)
self.vtable = vtable
self.implems = implems
self.vtable_pointer = ctypes.pointer(self.vtable)
self._as_parameter_ = ctypes.addressof(self.vtable_pointer)
def QueryInterface(self, this, piid, result):
"""Default ``QueryInterface`` implementation that returns ``self`` if piid is the implemented interface"""
if piid[0] in (gdef.IUnknown.IID, self.IMPLEMENT.IID):
result[0] = this
return 1
return E_NOINTERFACE
def AddRef(self, *args):
"""Default ``AddRef`` implementation that returns ``1``"""
return 1
def Release(self, *args):
"""Default ``Release`` implementation that returns ``1``"""
return 0