-
Notifications
You must be signed in to change notification settings - Fork 2
/
CLibrary.py
501 lines (427 loc) · 20.7 KB
/
CLibrary.py
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# -*- coding: utf-8 -*-
"""
CLibrary.py - Provides CLibrary class
Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
Proxy to both CHeader and ctypes, allowing automatic type conversion and
function calling based on C header definitions.
"""
from ctypes import *
import sys
class CLibrary:
"""The CLibrary class is intended to automate much of the work in using ctypes by integrating
header file definitions from CParser. Ths class serves as a proxy to a ctypes, adding
a few features:
- allows easy access to values defined via CParser
- automatic type conversions for function calls using CParser function signatures
- creates ctype classes based on type definitions from CParser
Initialize using a ctypes shared object and a CParser:
headers = CParser.winDefs()
lib = CLibrary(windll.User32, headers)
There are 3 ways to access library elements:
lib(type, name) - type can be one of 'values', 'functions', 'types', 'structs', 'unions', or 'enums'.
Returns an object matching name. For values, the value from the headers is
returned. For functions, a callable object is returned that handles automatic
type conversion for arguments and return values. for structs, types, and enums,
a ctypes class is returned matching the type specified.
lib.name - searches in order through values, functions, types, structs, unions, and enums from
header definitions and returns an object for the first match found. The object
returned is the same as returned by lib(type, name). This is the preferred way to access
elements from CLibrary, but may not work in some situations (for example, if
a struct and variable share the same name).
lib[type] - Accesses the header definitions directly, returns definition dictionaries
based on the type requested. This is equivalent to headers.defs[type].
"""
Null = object()
cTypes = {
'char': c_char,
'wchar': c_wchar,
'unsigned char': c_ubyte,
'short': c_short,
'short int': c_short,
'unsigned short': c_ushort,
'unsigned short int': c_ushort,
'int': c_int,
'unsigned': c_uint,
'unsigned int': c_uint,
'long': c_long,
'long int': c_long,
'unsigned long': c_ulong,
'unsigned long int': c_ulong,
'__int64': c_longlong,
'long long': c_longlong,
'long long int': c_longlong,
'unsigned __int64': c_ulonglong,
'unsigned long long': c_ulonglong,
'unsigned long long int': c_ulonglong,
'float': c_float,
'double': c_double,
'long double': c_longdouble
}
cPtrTypes = {
'char': c_char_p,
'wchar': c_wchar_p,
'void': c_void_p
}
def __init__(self, lib, headers, prefix=None):
## name everything using underscores to avoid name collisions with library
self._lib_ = lib
self._headers_ = headers
self._defs_ = headers.defs
if prefix is None:
self._prefix_ = []
elif type(prefix) is list:
self._prefix_ = prefix
else:
self._prefix_ = [prefix]
self._objs_ = {}
for k in ['values', 'functions', 'types', 'structs', 'unions', 'enums']:
self._objs_[k] = {}
self._allObjs_ = {}
self._structs_ = {}
self._unions_ = {}
def __call__(self, typ, name):
if typ not in self._objs_:
typs = self._objs_.keys()
raise Exception("Type must be one of %s" % str(typs))
if name not in self._objs_[typ]:
self._objs_[typ][name] = self._mkObj_(typ, name)
return self._objs_[typ][name]
def _allNames_(self, name):
return [name] + [p + name for p in self._prefix_]
def _mkObj_(self, typ, name):
names = self._allNames_(name)
for n in names:
if n in self._objs_:
return self._objs_[n]
for n in names: ## try with and without prefix
if n not in self._defs_[typ] and not (typ in ['structs', 'unions', 'enums'] and n in self._defs_['types']):
continue
if typ == 'values':
return self._defs_[typ][n]
elif typ == 'functions':
return self._getFunction(n)
elif typ == 'types':
obj = self._defs_[typ][n]
return self._ctype(obj)
elif typ == 'structs':
return self._cstruct('structs', n)
elif typ == 'unions':
return self._cstruct('unions', n)
elif typ == 'enums':
## Allow automatic resolving of typedefs that alias enums
if n not in self._defs_['enums']:
if n not in self._defs_['types']:
raise Exception('No enums named "%s"' % n)
typ = self._headers_.evalType([n])[0]
if typ[:5] != 'enum ':
raise Exception('No enums named "%s"' % n)
n = self._defs_['types'][typ][1] ## look up internal name of enum
obj = self._defs_['enums'][n]
return obj
else:
raise Exception("Unknown type %s" % typ)
raise NameError(name)
def __getattr__(self, name):
"""Used to retrieve any type of definition from the headers. Searches for the name in this order:
values, functions, types, structs, unions, enums."""
if name not in self._allObjs_:
names = self._allNames_(name)
for k in ['values', 'functions', 'types', 'structs', 'unions', 'enums', None]:
if k is None:
raise NameError(name)
obj = None
for n in names:
if n in self._defs_[k]:
obj = self(k, n)
break
if obj is not None:
break
self._allObjs_[name] = obj
return self._allObjs_[name]
def __getitem__(self, name):
"""Used to retrieve a specific dictionary from the headers."""
return self._defs_[name]
def __repr__(self):
return "<CLibrary instance: %s>" % str(self._lib_)
def _getFunction(self, funcName):
try:
func = getattr(self._lib_, funcName)
except:
raise Exception("Function name '%s' appears in headers but not in library!" % func)
#print "create function %s," % (funcName), self._defs_['functions'][funcName]
return CFunction(self, func, self._defs_['functions'][funcName], funcName)
def _ctype(self, typ, pointers=True):
"""return a ctype object representing the named type.
If pointers is True, the class returned includes all pointer/array specs provided.
Otherwise, the class returned is just the base type with no pointers."""
try:
typ = self._headers_.evalType(typ)
mods = typ[1:][:]
## Create the initial type
## Some types like ['char', '*'] have a specific ctype (c_char_p)
## (but only do this if pointers == True)
if pointers and len(typ) > 1 and typ[1] == '*' and typ[0] in CLibrary.cPtrTypes:
cls = CLibrary.cPtrTypes[typ[0]]
mods = typ[2:]
## If the base type is in the list of existing ctypes:
elif typ[0] in CLibrary.cTypes:
cls = CLibrary.cTypes[typ[0]]
## structs, unions, enums:
elif typ[0][:7] == 'struct ':
cls = self._cstruct('structs', self._defs_['types'][typ[0]][1])
elif typ[0][:6] == 'union ':
cls = self._cstruct('unions', self._defs_['types'][typ[0]][1])
elif typ[0][:5] == 'enum ':
cls = c_int
## void
elif typ[0] == 'void':
cls = None
else:
#print typ
raise Exception("Can't find base type for %s" % str(typ))
if not pointers:
return cls
## apply pointers and arrays
while len(mods) > 0:
m = mods.pop(0)
if isinstance(m, basestring): ## pointer or reference
if m[0] == '*' or m[0] == '&':
for i in m:
cls = POINTER(cls)
elif type(m) is list: ## array
for i in m:
if i == -1: ## -1 indicates an 'incomplete type' like "int variable[]"
cls = POINTER(cls) ## which we should interpret like "int *variable"
else:
cls = cls * i
elif type(m) is tuple: ## Probably a function pointer
## Find pointer and calling convention
isPtr = False
conv = '__cdecl'
if len(mods) == 0:
raise Exception("Function signature with no pointer:", m, mods)
for i in [0,1]:
if len(mods) < 1:
break
if mods[0] == '*':
mods.pop(0)
isPtr = True
elif mods[0] in ['__stdcall', '__cdecl']:
conv = mods.pop(0)
else:
break
if not isPtr:
raise Exception("Not sure how to handle type (function without single pointer): %s" % str(typ))
if conv == '__stdcall':
mkfn = WINFUNCTYPE
else:
mkfn = CFUNCTYPE
#print "Create function pointer (%s)" % conv
args = [self._ctype(arg[1]) for arg in m]
cls = mkfn(cls, *args)
else:
raise Exception("Not sure what to do with this type modifier: '%s'" % str(p))
return cls
except:
print "Error while processing type", typ
raise
def _cstruct(self, strType, strName):
if strName not in self._structs_:
## Resolve struct name--typedef aliases allowed.
if strName not in self._defs_[strType]:
if strName not in self._defs_['types']:
raise Exception('No struct/union named "%s"' % strName)
typ = self._headers_.evalType([strName])[0]
if typ[:7] != 'struct ' and typ[:6] != 'union ':
raise Exception('No struct/union named "%s"' % strName)
strName = self._defs_['types'][typ][1]
## Pull struct definition
defn = self._defs_[strType][strName]
## create ctypes class
defs = defn['members'][:]
if strType == 'structs':
class s(Structure):
def __repr__(self):
return "<ctypes struct '%s'>" % strName
elif strType == 'unions':
class s(Union):
def __repr__(self):
return "<ctypes union '%s'>" % strName
## must register struct here to allow recursive definitions.
self._structs_[strName] = s
if defn['pack'] is not None:
s._pack_ = defn['pack']
## assign names to anonymous members
members = []
anon = []
for i in range(len(defs)):
if defs[i][0] is None:
c = 0
while True:
name = 'anon_member%d' % c
if name not in members:
defs[i][0] = name
anon.append(name)
break
members.append(defs[i][0])
s._anonymous_ = anon
s._fields_ = [(m[0], self._ctype(m[1])) for m in defs]
s._defaults_ = [m[2] for m in defs]
return self._structs_[strName]
class CFunction:
def __init__(self, lib, func, sig, name):
self.lib = lib
self.func = func
#print sig
self.sig = list(sig) # looks like [return_type, [(argName, type, default), (argName, type, default), ...]]
self.sig[1] = [s for s in sig[1] if s[1] != ['void']] ## remove void args from list
for conv in ['__stdcall', '__cdecl']:
if conv in self.sig[0]:
self.sig[0].remove(conv)
self.name = name
self.restype = lib._ctype(self.sig[0])
#func.restype = self.restype
self.argTypes = [lib._ctype(s[1]) for s in self.sig[1]]
func.argtypes = self.argTypes
self.reqArgs = [x[0] for x in self.sig[1] if x[2] is None]
self.argInds = dict([(self.sig[1][i][0], i) for i in range(len(self.sig[1]))]) ## mapping from argument names to indices
#print "created func", self, sig, self.argTypes
def argCType(self, arg):
"""Return the ctype required for the specified argument.
arg can be either an integer or the name of the argument.
"""
if isinstance(arg, basestring):
arg = self.argInds[arg]
return self.lib._ctype(self.sig[1][arg][1])
def __call__(self, *args, **kwargs):
"""Invoke the SO or dll function referenced, converting all arguments to the correct type.
Keyword arguments are allowed as long as the header specifies the argument names.
Arguments which are passed byref may be omitted entirely, and will be automaticaly generated.
To pass a NULL pointer, give None as the argument.
Returns the return value of the function call as well as all of the arguments (so that objects passed by reference can be retrieved)"""
#print "CALL: %s(%s)" % (self.name, ", ".join(map(str, args) + ["%s=%s" % (k, str(kwargs[k])) for k in kwargs]))
#print " sig:", self.sig
argList = [None] * max(len(self.reqArgs), len(args)) ## We'll need at least this many arguments.
## First fill in args
for i in range(len(args)):
#argList[i] = self.argTypes[i](args[i])
if args[i] is None:
argList[i] = self.lib.Null
else:
argList[i] = args[i]
## Next fill in kwargs
for k in kwargs:
#print " kw:", k
if k not in self.argInds:
print "Function signature:", self.prettySignature()
raise Exception("Function signature has no argument named '%s'" % k)
ind = self.argInds[k]
if ind >= len(argList): ## stretch argument list if needed
argList += [None] * (ind - len(argList) + 1)
#argList[ind] = self.coerce(kwargs[k], self.argTypes[ind])
if kwargs[k] is None:
argList[ind] = self.lib.Null
else:
argList[ind] = kwargs[k]
guessedArgs = []
## Finally, fill in remaining arguments if they are pointers to int/float/void*/struct values
## (we assume these are to be modified by the function and their initial value is not important)
for i in range(len(argList)):
if argList[i] is None or argList[i] is self.lib.Null:
try:
sig = self.sig[1][i][1]
argType = self.lib._headers_.evalType(sig)
if argList[i] is self.lib.Null: ## request to build a null pointer
if len(argType) < 2:
raise Exception("Can not create NULL for non-pointer argument type: %s" % str(argType))
argList[i] = self.lib._ctype(sig)()
#elif argType == ['char', '*']: ## pass null pointer if none was specified. This is a little dangerous, but some functions will expect it.
#argList[i] = c_char_p() ## On second thought: let's just require the user to explicitly ask for a NULL pointer.
else:
if argType == ['void', '**'] or argType == ['void', '*', '*']:
cls = c_void_p
else:
assert len(argType) == 2 and argType[1] == '*' ## Must be 2-part type, second part must be '*'
cls = self.lib._ctype(sig, pointers=False)
argList[i] = pointer(cls(0))
guessedArgs.append(i)
except:
if sys.exc_info()[0] is not AssertionError:
raise
#sys.excepthook(*sys.exc_info())
print "Function signature:", self.prettySignature()
raise Exception("Function call '%s' missing required argument %d '%s'. (See above for signature)" % (self.name, i, self.sig[1][i][0]))
#print " args:", argList
try:
res = self.func(*argList)
except:
print "Function call failed. Signature is:", self.prettySignature()
print "Arguments:", argList
print "Argtypes:", self.func.argtypes
raise
#print " result:", res
cr = CallResult(res, argList, self.sig, guessed=guessedArgs)
return cr
def prettySignature(self):
return "%s %s(%s)" % (''.join(self.sig[0]), self.name, ', '.join(["%s %s" % ("".join(map(str, s[1])), s[0]) for s in self.sig[1]]))
class CallResult:
"""Class for bundling results from C function calls. Allows access to the function
return value as well as all of the arguments, since the function call will often return
extra values via these arguments.
- Original ctype objects can be accessed via result.rval or result.args
- Python values carried by these objects can be accessed using ()
To access values:
- The return value: ()
- The nth argument passed: [n]
- The argument by name: ['name']
- All values that were auto-generated: .auto()
The class can also be used as an iterator, so that tuple unpacking is possible:
ret, arg1, arg2 = lib.runSomeFunction(...)
"""
def __init__(self, rval, args, sig, guessed):
self.rval = rval ## return value of function call
self.args = args ## list of arguments to function call
self.sig = sig ## function signature
self.guessed = guessed ## list of arguments that were generated automatically (usually byrefs)
def __call__(self):
#print "Clibrary:", type(self.rval), self.mkVal(self.rval)
if self.sig[0] == ['void']:
return None
return self.mkVal(self.rval)
def __getitem__(self, n):
if type(n) is int:
return self.mkVal(self.args[n])
elif type(n) is str:
ind = self.findArg(n)
return self.mkVal(self.args[ind])
else:
raise Exception("Index must be int or str.")
def __setitem__(self, n, val):
if type(n) is int:
self.args[n] = val
elif type(n) is str:
ind = self.findArg(n)
self.args[ind] = val
else:
raise Exception("Index must be int or str.")
def mkVal(self, obj):
while not hasattr(obj, 'value'):
if not hasattr(obj, 'contents'):
return obj
try:
obj = obj.contents
except ValueError:
return None
return obj.value
def findArg(self, arg):
for i in range(len(self.sig[1])):
if self.sig[1][i][0] == arg:
return i
raise Exception("Can't find argument '%s' in function signature. Arguments are: %s" % (arg, str([a[0] for a in self.sig[1]])))
def __iter__(self):
yield self()
for i in range(len(self.args)):
yield(self[i])
def auto(self):
return [self[n] for n in self.guessed]