Load a 32-bit C++ library in 64-bit Python
This example shows how to access a 32-bit C++ library from 64-bit Python.
Cpp32
is the 32-bit server and
Cpp64
is the 64-bit client.
The source code of the C++ program is available here.
Note
If you have issues running the example please make sure that you have the prerequisites installed for your operating system.
Important
By default ctypes
expects that a ctypes.c_int
data type is
returned from the library call. If the returned value from the library is
not a ctypes.c_int
then you must redefine the ctypes
restype value to be the appropriate
data type. The Cpp32
class shows
various examples of redefining the restype
value.
Create a Cpp64
client to communicate
with the 32-bit cpp_lib32 library from 64-bit Python
>>> from msl.examples.loadlib import Cpp64
>>> cpp = Cpp64()
Add two integers, see add()
>>> cpp.add(3, 14)
17
Subtract two C++ floating-point numbers, see
subtract()
>>> cpp.subtract(43.2, 3.2)
40.0
Add or subtract two C++ double-precision numbers, see
add_or_subtract()
>>> cpp.add_or_subtract(1.0, 2.0, True)
3.0
>>> cpp.add_or_subtract(1.0, 2.0, False)
-1.0
Arrays
Multiply a 1D array by a number, see scalar_multiply()
Attention
The scalar_multiply()
function takes
a pointer to an array as an input argument, see cpp_lib.h.
One cannot pass pointers from Client64
to
Server32
because a 64-bit process cannot share the
same memory space as a 32-bit process. All 32-bit pointers must be created (using
ctypes
) in the class that is a subclass of Server32
and only the value that is stored at that address can be returned to
Client64
for use in the 64-bit program.
>>> a = [float(val) for val in range(10)]
>>> cpp.scalar_multiply(2.0, a)
[0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0]
If you have a numpy.ndarray
in 64-bit Python then you cannot pass the
ndarray object to Server32
because the 32-bit
server would need to load the ndarray in a 32-bit version of numpy (which is
not included by default in the 32-bit server, but could be – see Create a custom 32-bit server
for more details). To simplify the procedure you could convert the ndarray to a
list
using the numpy.ndarray.tolist()
method
>>> import numpy as np
>>> a = np.arange(9.)
>>> cpp.scalar_multiply(3.1, a.tolist())
[0.0, 3.1, 6.2, 9.3, 12.4, 15.5, 18.6, 21.7, 24.8]
or you could use the builtin array.array
class
>>> from array import array
>>> b = array('d', a.tobytes())
>>> cpp.scalar_multiply(3.1, b)
[0.0, 3.1, 6.2, 9.3, 12.4, 15.5, 18.6, 21.7, 24.8]
If you want the returned value from scalar_multiply to be a numpy ndarray then use
>>> np.array(cpp.scalar_multiply(3.1, b))
array([ 0. , 3.1, 6.2, 9.3, 12.4, 15.5, 18.6, 21.7, 24.8])
Strings
In this example the memory for the reversed string is allocated in Python,
see reverse_string_v1()
>>> cpp.reverse_string_v1('hello world!')
'!dlrow olleh'
In this example the memory for the reversed string is allocated in C++,
see reverse_string_v2()
>>> cpp.reverse_string_v2('uncertainty')
'ytniatrecnu'
Structs
It is possible to pickle
a ctypes.Structure
and pass the
struct object between Cpp64
and
Cpp32
provided that the struct is
a fixed size in memory (i.e., the struct does not contain any pointers).
If the struct contains pointers then you must create the struct within
Cpp32
and you can only pass the
values of the struct back to Cpp64
.
Attention
The following will only work if Cpp64
is run using Python 3 because Cpp32
is running on Python 3 and there are issues with ctypes
and pickle
when mixing Python 2 (client) and Python 3 (server).
The cpp_lib32 library contains the following structs
struct Point {
double x;
double y;
};
struct FourPoints {
Point points[4];
};
struct NPoints {
int n;
Point *points;
};
The distance_4_points()
method uses the
FourPoints
struct to calculate the total
distance connecting 4 Point
structs. Since
the FourPoints
struct is a fixed size it
can be created in 64-bit Python, pickled and then unpickled in
Cpp32
>>> from msl.examples.loadlib import FourPoints
>>> fp = FourPoints((0, 0), (0, 1), (1, 1), (1, 0))
>>> cpp.distance_4_points(fp)
4.0
The Cpp32.circumference
method uses the NPoints
struct to calculate
the circumference of a circle using n Point
structs. Since the NPoints
struct is
not a fixed size it must be created in the
Cpp32.circumference
method.
The Cpp64.circumference
method takes the values of the radius and n as input arguments to pass to the
Cpp32.circumference
method.
>>> for i in range(16):
... print(cpp.circumference(0.5, 2**i))
...
0.0
2.0
2.828427124746...
3.061467458920...
3.121445152258...
3.136548490545...
3.140331156954...
3.141277250932...
3.141513801144...
3.141572940367...
3.141587725277...
3.141591421511...
3.141592345569...
3.141592576584...
3.141592634337...
3.141592648775...
Shutdown the 32-bit server when you are done communicating with the 32-bit library
>>> stdout, stderr = cpp.shutdown_server32()