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()