Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources]

David Ascher, Alex Martelli, Anna Ravenscroft

نسخه متنی -صفحه : 394/ 326
نمايش فراداده

Recipe 17.4. Calling Functions from a Windows DLL

Credit: Stefano Spinucci

Problem

You want to avoid writing a Python extension in C, by directly calling from Python functions that already exist in a Windows DLL.

Solution

The third-party ctypes extension makes this task pretty easy:

from ctypes import windll, c_int, c_string, byref
# load 'Ehllapi.dll' (from current dir),
 and function 'hllapi' from the DLL
Ehllap32 = windll.ehllapi
hllapi = Ehllap32.hllapi
# prepare the arguments with types and initial values
h_func = c_int(1)
h_text = c_string('A')
h_len = c_int(1)
h_ret = c_int(999)
# call the function
hllapi(byref(h_func), h_text, byref(h_len), byref(h_ret))
# print the resulting values of all arguments after the call
print h_func.value, h_text.value, h_len.value, h_ret.value

Discussion

I needed the code in this recipe specifically to call a C function whose prototype is:

void FAR PASCAL hllapi(int FAR *, char FAR *, int FAR *, int FAR *);

from a DLL named Ehllapi.DLL (an implementation of the IBM 3270 HLLAPI for an Italian 3270 terminal emulator, as it happens). Thomas Heller's ctypes extension, found at http://sourceforge.net/projects/ctypes, made the task very easy. In particular, ctypes makes mincemeat of problems related to representing function arguments that must belong to a certain C type and possibly get passed "by reference" (i.e., via a pointer).

In the past, I used another extension, known as calldll, which was (and still is) available from http://www.niare.com/softwarel. While once very useful, calldll cannot rely on some of the modern techniques that ctypes uses internally, because these possibilities were introduced only in relatively recent versions of Python. calldll, using a single membuf Python type to represent all possible C types, tends to be much more cumbersome than ctypes when they are both used to perform the same tasks.

Judge for yourself: here is a working calldll version of the same script that I just showed how to code with ctypes:

import calldll, struct
# some helpful auxiliary functions
def myPrintLong(vVar):
''' print a long contained in a membuf '''
print calldll.read_long(vVar.address( ))
def myPrintString(vVar):
''' print a string contained in a membuf '''
a = calldll.read_string(vVar.address( ))
print a, len(a)
def mySetLong(vMemBuf, vValueToSet):
''' set to an unsigned long the value of a membuf with len == 4 '''
vMemBuf.write(struct.pack('L', vValueToSet))
def mySetString(vMemBuf, vValueToSet):
''' set to a string (with \0 terminator) the value of a membuf '''
pack_format = "%ds" % 1+len(vValueToSet)# +1 for the \0
string_packed = struct.pack(pack_format, vValueToSet)
 # pack( ) adds the \0
vMemBuf.write(string_packed)
# load 'Ehllapi.dll' (from current dir), 
and function 'hllapi' from the DLL
dll_handle = calldll.load_library ('.\\Ehllapi')
function_address = calldll.get_proc_address (dll_handle, 'HLLAPI')
# allocate and init three membufs with the size
 to hold an unsigned long
Lsize = struct.calcsize('L')
vFunction = calldll.membuf(Lsize)
mySetLong(vFunction, 1)
vTextLen = calldll.membuf(Lsize)
vResult = calldll.membuf(Lsize)
mySetLong(vResult, 1)
# allocate a membuf as large as the DLL requires;
 in this case, space
# for 24 x 80 characters + 1 for a \0 terminator
vText = calldll.membuf(1921)
# init the text and text-length variables based on string of interest
string_value_to_write = 'A'
mySetString(vText, string_value_to_write)
mySetLong(vTextLen, len(string_value_to_write))
# call the function, print the results, and clean up
calldll.call_foreign_function(function_address, 'llll', 'l',
(vFunction.address( ), vText.address( ),
 vTextLen.address( ), vResult.address( )))
myPrintLong(vResult)
myPrintString(vText)
calldll.free_library(dll_handle)

To be honest, I can't quite be sure whether all of these gyrations are truly indispensable to making this calldll-based version work. Whenever I try to simplify this version a bit, something or other always breaks noisily, so I've stopped messing with it. One reason the ctypes-based version is cleaner and simpler is that ctypes has never given me trouble, so I've been encouraged to continue working on that version to improve it.

See Also

ctypes is at http://sourceforge.net/projects/ctypes; calldll is at http://www.niare.com/softwarel.