"""
A wrapper around a 32-bit C++ library, :ref:`cpp_lib32 <cpp-lib>`.
Example of a server that loads a 32-bit shared library, :ref:`cpp_lib <cpp-lib>`,
in a 32-bit Python interpreter to host the library. The corresponding :mod:`~.cpp64` module
can be executed by a 64-bit Python interpreter and the :class:`~.cpp64.Cpp64` class can send
a request to the :class:`~.cpp32.Cpp32` class which calls the 32-bit library to execute the
request and then return the response from the library.
"""
import ctypes
import math
import os
from msl.loadlib import Server32
[docs]class Cpp32(Server32):
def __init__(self, host, port, **kwargs):
"""A wrapper around the 32-bit C++ library, :ref:`cpp_lib32 <cpp-lib>`.
This class demonstrates how to send/receive various data types to/from a
32-bit C++ library via :mod:`ctypes`.
Parameters
----------
host : :class:`str`
The IP address of the server.
port : :class:`int`
The port to open on the server.
Note
----
Any class that is a subclass of :class:`~msl.loadlib.server32.Server32` **MUST**
provide two arguments in its constructor: `host` and `port` (in that order)
and `**kwargs`. Otherwise the ``server32`` executable, see
:class:`~msl.loadlib.start_server32`, cannot create an instance of the
:class:`~msl.loadlib.server32.Server32` subclass.
"""
# By not specifying the extension of the library file the server will open
# the appropriate file based on the operating system.
super(Cpp32, self).__init__(os.path.join(os.path.dirname(__file__), 'cpp_lib32'),
'cdll', host, port)
[docs] def add(self, a, b):
"""Add two integers.
The corresponding C++ code is
.. code-block:: cpp
int add(int a, int b) {
return a + b;
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.add` method.
Parameters
----------
a : :class:`int`
The first integer.
b : :class:`int`
The second integer.
Returns
-------
:class:`int`
The sum of `a` and `b`.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.add.restype = ctypes.c_int32
self.lib.add.argtypes = [ctypes.c_int32, ctypes.c_int32]
return self.lib.add(a, b)
[docs] def subtract(self, a, b):
"""Subtract two floating-point numbers *('float' refers to the C++ data type)*.
The corresponding C++ code is
.. code-block:: cpp
float subtract(float a, float b) {
return a - b;
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.subtract` method.
Parameters
----------
a : :class:`float`
The first floating-point number.
b : :class:`float`
The second floating-point number.
Returns
-------
:class:`float`
The difference between `a` and `b`.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.subtract.restype = ctypes.c_float
self.lib.subtract.argtypes = [ctypes.c_float, ctypes.c_float]
return self.lib.subtract(a, b)
[docs] def add_or_subtract(self, a, b, do_addition):
"""Add or subtract two double-precision numbers *('double' refers to the C++ data type)*.
The corresponding C++ code is
.. code-block:: cpp
double add_or_subtract(double a, double b, bool do_addition) {
if (do_addition) {
return a + b;
} else {
return a - b;
}
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.add_or_subtract` method.
Parameters
----------
a : :class:`float`
The first double-precision number.
b : :class:`float`
The second double-precision number.
do_addition : :class:`bool`
Whether to **add** the numbers.
Returns
-------
:class:`float`
Either `a` + `b` if `do_addition` is :data:`True` else `a` - `b`.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.add_or_subtract.restype = ctypes.c_double
self.lib.add_or_subtract.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_bool]
return self.lib.add_or_subtract(a, b, do_addition)
[docs] def scalar_multiply(self, a, xin):
"""Multiply each element in an array by a number.
The corresponding C++ code is
.. code-block:: cpp
void scalar_multiply(double a, double* xin, int n, double* xout) {
for (int i = 0; i < n; i++) {
xout[i] = a * xin[i];
}
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.scalar_multiply` method.
Parameters
----------
a : :class:`float`
The scalar value.
xin : :class:`list` of :class:`float`
The array to modify.
Returns
-------
:class:`list` of :class:`float`
A new array with each element in `xin` multiplied by `a`.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.scalar_multiply.restype = None
self.lib.scalar_multiply.argtypes = [
ctypes.c_double,
ctypes.POINTER(ctypes.c_double),
ctypes.c_int32,
ctypes.POINTER(ctypes.c_double),
]
n = len(xin)
c_xin = (ctypes.c_double * n)(*xin) # convert input array to ctypes
c_xout = (ctypes.c_double * n)() # allocate memory for output array
self.lib.scalar_multiply(a, c_xin, n, c_xout)
return [value for value in c_xout]
[docs] def reverse_string_v1(self, original):
"""Reverse a string (version 1).
In this method Python allocates the memory for the reversed string and
passes the string to C++.
The corresponding C++ code is
.. code-block:: cpp
void reverse_string_v1(const char* original, int n, char* reversed) {
for (int i = 0; i < n; i++) {
reversed[i] = original[n-i-1];
}
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.reverse_string_v1` method.
Parameters
----------
original : :class:`str`
The original string.
Returns
-------
:class:`str`
The string reversed.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.reverse_string_v1.restype = None
self.lib.reverse_string_v1.argtypes = [ctypes.c_char_p, ctypes.c_int32, ctypes.c_char_p]
n = len(original)
rev = ctypes.create_string_buffer(n)
self.lib.reverse_string_v1(original.encode(), n, rev)
return rev.raw.decode()
[docs] def reverse_string_v2(self, original):
"""Reverse a string (version 2).
In this method C++ allocates the memory for the reversed string and passes
the string to Python.
The corresponding C++ code is
.. code-block:: cpp
char* reverse_string_v2(char* original, int n) {
char* reversed = new char[n];
for (int i = 0; i < n; i++) {
reversed[i] = original[n - i - 1];
}
return reversed;
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.reverse_string_v2` method.
Parameters
----------
original : :class:`str`
The original string.
Returns
-------
:class:`str`
The string reversed.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.reverse_string_v2.restype = ctypes.c_char_p
self.lib.reverse_string_v2.argtypes = [ctypes.c_char_p, ctypes.c_int32]
n = len(original)
rev = self.lib.reverse_string_v2(original.encode(), n)
return ctypes.string_at(rev, n).decode()
[docs] def distance_4_points(self, four_points):
"""Calculates the total distance connecting 4 :class:`~.Point`\'s.
The corresponding C++ code is
.. code-block:: cpp
double distance_4_points(FourPoints p) {
double d = distance(p.points[0], p.points[3]);
for (int i = 1; i < 4; i++) {
d += distance(p.points[i], p.points[i-1]);
}
return d;
}
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.distance_4_points` method.
Parameters
----------
four_points : :class:`.FourPoints`
The points to use to calculate the total distance.
Returns
-------
:class:`float`
The total distance connecting the 4 :class:`~.Point`\'s.
"""
self.lib.distance_4_points.restype = ctypes.c_double
return self.lib.distance_4_points(four_points)
[docs] def circumference(self, radius, n):
"""Estimates the circumference of a circle.
This method calls the ``distance_n_points`` function in :ref:`cpp_lib32 <cpp-lib>`.
See the corresponding 64-bit :meth:`~.cpp64.Cpp64.circumference` method.
The corresponding C++ code uses the :class:`.NPoints` struct as the input
parameter to sum the distance between adjacent points on the circle.
.. code-block:: cpp
double distance_n_points(NPoints p) {
if (p.n < 2) {
return 0.0;
}
double d = distance(p.points[0], p.points[p.n-1]);
for (int i = 1; i < p.n; i++) {
d += distance(p.points[i], p.points[i-1]);
}
return d;
}
Parameters
----------
radius : :class:`float`
The radius of the circle.
n : :class:`int`
The number of points to use to estimate the circumference.
Returns
-------
:class:`float`
The estimated circumference of the circle.
"""
# restype and argtypes could be defined in the __init__ method
self.lib.distance_n_points.restype = ctypes.c_double
self.lib.distance_n_points.argtypes = [NPoints]
theta = 0.0
delta = (2.0*math.pi)/float(n) if n != 0 else 0
pts = NPoints()
pts.n = n
pts.points = (Point * n)()
for i in range(n):
pts.points[i] = Point(radius*math.cos(theta), radius*math.sin(theta))
theta += delta
return self.lib.distance_n_points(pts)
[docs]class Point(ctypes.Structure):
"""C++ struct that is a fixed size in memory.
This object can be :mod:`pickle`\'d.
.. code-block:: cpp
struct Point {
double x;
double y;
};
"""
_fields_ = [
('x', ctypes.c_double),
('y', ctypes.c_double),
]
[docs]class FourPoints(ctypes.Structure):
_fields_ = [
('points', (Point * 4)),
]
def __init__(self, point1, point2, point3, point4):
"""C++ struct that is a fixed size in memory.
This object can be :mod:`pickle`\'d.
.. code-block:: cpp
struct FourPoints {
Point points[4];
};
Parameters
----------
point1 : :class:`tuple` of :class:`int`
The first point as an (x, y) ordered pair.
point2 : :class:`tuple` of :class:`int`
The second point as an (x, y) ordered pair.
point3 : :class:`tuple` of :class:`int`
The third point as an (x, y) ordered pair.
point4 : :class:`tuple` of :class:`int`
The fourth point as an (x, y) ordered pair.
"""
super(FourPoints, self).__init__()
self.points = (Point * 4)(point1, point2, point3, point4)
[docs]class NPoints(ctypes.Structure):
"""C++ struct that is **not** a fixed size in memory.
This object cannot be :mod:`pickle`\'d because it contains a pointer.
A 32-bit process and a 64-bit process cannot share a pointer.
.. code-block:: cpp
struct NPoints {
int n;
Point *points;
};
"""
_fields_ = [
('n', ctypes.c_int),
('points', ctypes.POINTER(Point)),
]