diff --git a/doc/cross_module.html b/doc/cross_module.html new file mode 100644 index 00000000..08c39bfe --- /dev/null +++ b/doc/cross_module.html @@ -0,0 +1,336 @@ + + +Cross-extension-module dependencies + +
+ +c++boost.gif (8819 bytes) + +
+

Cross-extension-module dependencies

+ +It is good programming practice to organize large projects as modules +that interact with each other via well defined interfaces. With +Boost.Python it is possible to reflect this organization at the C++ +level at the Python level. This is, each logical C++ module can be +organized as a separate Python extension module. + +

+At first sight this might seem natural and straightforward. However, it +is a fairly complex problem to establish cross-extension-module +dependencies while maintaining the same ease of use Boost.Python +provides for classes that are wrapped in the same extension module. To +a large extent this complexity can be hidden from the author of a +Boost.Python extension module, but not entirely. + +


+

The recipe

+ +Suppose there is an extension module that exposes certain instances of +the C++ std::vector template library such that it can be used +from Python in the following manner: + +
+import std_vector
+v = std_vector.double([1, 2, 3, 4])
+v.push_back(5)
+v.size()
+
+ +Suppose the std_vector module is done well and reflects all +C++ functions that are useful at the Python level, for all C++ built-in +data types (std_vector.int, std_vector.long, etc.). + +

+Suppose further that there is statistic module with a C++ class that +has constructors or member functions that use or return a +std::vector. For example: + +

+class xy {
+  public:
+    xy(const std::vector<double>& x, const std::vector<double>& y) : m_x(x), m_y(y) {}
+    const std::vector<double>& x() const { return m_x; }
+    const std::vector<double>& y() const { return m_y; }
+    double correlation();
+  private:
+    std::vector<double> m_x;
+    std::vector<double> m_y;
+}
+
+ +What is more natural than reusing the std_vector extension +module to expose these constructors or functions to Python? + +

+Unfortunately, what seems natural needs a little work in both the +std_vector and the statistics module. + +

+In the std_vector extension module, +std::vector<double> is exposed to Python in the usual +way with the class_builder<> template. To also enable the +automatic conversion of std::vector<double> function +arguments or return values in other Boost.Python C++ modules, the +converters that convert a std::vector<double> C++ object +to a Python object and vice versa (i.e. the to_python() and +from_python() template functions) have to be exported. For +example: + +

+  #include <boost/python/cross_module.hpp>
+  //...
+  class_builder<std::vector<double> > v_double(std_vector_module, "double");
+  export_converters(v_double);
+
+ +In the extension module that wraps class xy we can now import +these converters with the import_converters<> template. +For example: + +
+  #include <boost/python/cross_module.hpp>
+  //...
+  import_converters<std::vector<double> > v_double_converters("std_vector", "double");
+
+ +That is all. All the attributes that are defined for +std_vector.double in the std_vector Boost.Python +module will be available for the returned objects of xy.x() +and xy.y(). Similarly, the constructor for xy will +accept objects that were created by the std_vectormodule. + +
+

Placement of import_converters<> template instantiations

+ +import_converts<> can be viewed as a drop-in replacement +for class_wrapper<>, and the recommendations for the +placement of class_wrapper<> template instantiations +also apply to to import_converts<>. In particular, it is +important that an instantiation of class_wrapper<> is +visible to any code which wraps a C++ function with a T, +T*, const T&, etc. parameter or return value. +Therefore you may want to group all class_wrapper<> and +import_converts<> instantiations at the top of your +module's init function, then def() the member functions later +to avoid problems with inter-class dependencies. + +
+

Non-copyable types

+ +export_converters() instantiates C++ template functions that +invoke the copy constructor of the wrapped type. For a type that is +non-copyable this will result in compile-time error messages. In such a +case, export_converters_noncopyable() can be used to export +the converters that do not involve the copy constructor of the wrapped +type. For example: + +
+class_builder<store> py_store(your_module, "store");
+export_converters_noncopyable(py_store);
+
+ +The corresponding import_converters<> statement does not +need any special attention: + +
+import_converters<store> py_store("noncopyable_export", "store");
+
+ +
+

Python module search path

+ +The std_vector and statistics modules can now be used +in the following way: + +
+import std_vector
+import statistics
+x = std_vector.double([1, 2, 3, 4])
+y = std_vector.double([2, 4, 6, 8])
+xy = statistics.xy(x, y)
+xy.correlation()
+
+ +In this example it is clear that Python has to be able to find both the +std_vector and the statistics extension module. In +other words, both extension modules need to be in the Python module +search path (sys.path). + +

+The situation is not always this obvious. Suppose the +statistics module has a random() function that +returns a vector of random numbers with a given length: + +

+import statistics
+x = statistics.random(5)
+y = statistics.random(5)
+xy = statistics.xy(x, y)
+xy.correlation()
+
+ +A naive user will not easily anticipate that the std_vector +module is used to pass the x and y vectors around. If +the std_vector module is in the Python module search path, +this form of ignorance is of no harm. On the contrary, we are glad +that we do not have to bother the user with details like this. + +

+If the std_vector module is not in the Python module search +path, a Python exception will be raised: + +

+Traceback (innermost last):
+  File "foo.py", line 2, in ?
+    x = statistics.random(5)
+ImportError: No module named std_vector
+
+ +As is the case with any system of a non-trivial complexity, it is +important that the setup is consistent and complete. + +
+

Two-way module dependencies

+ +Boost.Python supports two-way module dependencies. This is best +illustrated by a simple example. + +

+Suppose there is a module ivect that implements vectors of +integers, and a similar module dvect that implements vectors +of doubles. We want to be able do convert an integer vector to a double +vector and vice versa. For example: + +

+import ivect
+iv = ivect.ivect((1,2,3,4,5))
+dv = iv.as_dvect()
+
+ +The last expression will implicitly import the dvect module in +order to enable the conversion of the C++ representation of +dvect to a Python object. The analogous is possible for a +dvect: + +
+import dvect
+dv = dvect.dvect((1,2,3,4,5))
+iv = dv.as_ivect()
+
+ +Now the ivect module is imported implicitly. + +

+Note that the two-way dependencies are possible because the +dependencies are resolved only when needed. This is, the initialization +of the ivect module does not rely on the dvect +module, and vice versa. Only if as_dvect() or +as_ivect() is actually invoked will the corresponding module +be implicitly imported. This also means that, for example, the +dvect module does not have to be available at all if +as_dvect() is never used. + +


+

Clarification of compile-time and link-time dependencies

+ +Boost.Python's support for resolving cross-module dependencies at +runtime does not imply that compile-time dependencies are eliminated. +For example, the statistics extension module in the example above will +need to #include <vector>. This is immediately obvious +from the definition of class xy. + +

+If a library is wrapped that consists of both header files and compiled +components (e.g. libdvect.a, dvect.lib, etc.), both +the Boost.Python extension module with the +export_converters() statement and the module with the +import_converters<> statement need to be linked against +the object library. Ideally one would build a shared library (e.g. +libdvect.so, dvect.dll, etc.). However, this +introduces the issue of having to configure the search path for the +dynamic loading correctly. For small libraries it is therefore often +more convenient to ignore the fact that the object files are loaded +into memory more than once. + +


+

Summary of motivation for cross-module support

+ +The main purpose of Boost.Python's cross-module support is to allow for +a modular system layout. With this support it is straightforward to +reflect C++ code organization at the Python level. Without the +cross-module support, a multi-purpose module like std_vector +would be impractical because the entire wrapper code would somehow have +to be duplicated in all extension modules that use it, making them +harder to maintain and harder to build. + +

+Another motivation for the cross-module support is that two extension +modules that wrap the same class cannot both be imported into Python. +For example, if there are two modules A and B that +both wrap a given class X, this will work: + +

+import A
+x = A.X()
+
+ +This will also work: + +
+import B
+x = B.X()
+
+ +However, this will fail: + +
+import A
+import B
+python: /net/cci/rwgk/boost/boost/python/detail/extension_class.hpp:866:
+static void boost::python::detail::class_registry<X>::register_class(boost::python::detail::extension_class_base *):
+Assertion `static_class_object == 0' failed.
+Abort
+
+ +A good solution is to wrap class X only once. Depending on the +situation, this could be done by module A or B, or an +additional small extension module that only wraps and exports +class X. + +

+Finally, there can be important psychological or political reasons for +using the cross-module support. If a group of classes is lumped +together with many others in a huge module, the authors will have +difficulties in being identified with their work. The situation is +much more transparent if the work is represented by a module with a +recognizable name. This is not just a question of strong egos, but also +of getting credit and funding. + +


+

Why not use export_converters() universally?

+ +There is some overhead associated with the Boost.Python cross-module +support. Depending on the platform, the size of the code generated by +export_converters() is roughly 10%-20% of that generated +by class_builder<>. For a large extension module with +many wrapped classes, this could mean a significant difference. +Therefore the general recommendation is to use +export_converters() only for classes that are likely to +be used as function arguments or return values in other modules. + +
+© Copyright Ralf W. Grosse-Kunstleve 2001. Permission to copy, +use, modify, sell and distribute this document is granted provided this +copyright notice appears in all copies. This document is provided "as +is" without express or implied warranty, and with no claim as to its +suitability for any purpose. + +

+Updated: April 2001 + +