使用SIP对C++类进行Python封装

本文来自于:  http://pyqt.sourceforge.net/Docs/sip4/using.html#a-simple-c-example

本人翻译,欢迎转载,赠人玫瑰,手留余香。


Using SIP

Bindings are generated by the SIP code generator from a number of specification files, typically with a .sip extension.  Specification files look very similar to C and C++ header files, but often with additional information (in the form of a directive or an annotation) and code so that the bindings generated can be finely tuned.

SIP可以从多个参数文件创建C/C++的Python绑定代码,参数文件以*.sip命名,类似于C/C++的.H头文件,原则上与C/C++的模块或头文件对应。

A Simple C++ Example

We start with a simple example.  Let’s say you have a (fictional) C++ library that implements a single class called Word.  The class has one constructor that takes a \0 terminated character string as its single argument.  The class has one method called reverse() which takes no arguments and returns a \0 terminated character string.  The interface to the class is defined in a header file called word.h which might look something like this:

开始一个C++的类,非常简单,实现字符的顺序翻转(接受一个字符串指针,包含的字符串必须以‘\0‘结尾),如下所示:

// Define the interface to the word library.

class Word {
    const char *the_word;

public:
    Word(const char *w);

    char *reverse() const;
};

The corresponding SIP specification file would then look something like this:

对应的SIP文件如下:

// Define the SIP wrapper to the word library.

%Module word

class Word {

%TypeHeaderCode
#include <word.h>
%End

public:
    Word(const char *w);
    char *reverse() const;
};

Obviously a SIP specification file looks very much like a C++ (or C) header file, but SIP does not include a full C++ parser.  Let’s look at the differences between the two files.

SIP文件与C/C++头文件非常类似,但是不包括完整的C++语法解析支持。另外,还有一些特殊的指示符。包括:

  • The %Module directive has been added [1].  This is used to name the Python module that is being created, word in this example.

  • %Module 指示Python模块的名称,该名称在Python中以import name的方式使用。

  • The %TypeHeaderCode directive has been added.  The text between this and the following %End directive is included literally in the code that SIP generates.  Normally it is used, as in this case, to #include the corresponding C++ (or C) header file [2].

  • %TypeHeaderCode和%End之间是SIP创建时加入的包含文件指示。

  • The declaration of the private variable this_word has been removed. SIP does not support access to either private or protected instance variables.

  • 该C++类在Python中的接口定义,已经移除了私有变量和保护变量等定义。

If we want to we can now generate the C++ code in the current directory by running the following command:

现在可以使用sip 创建出接口文件,如下:

sip -c . word.sip

However, that still leaves us with the task of compiling the generated code and linking it against all the necessary libraries.  It’s much easier to use theSIP build system to do the whole thing.

下一步,我们还需要对创建的代码进行编译。可以使用SIP的Build System来同时完成创建接口文件和库构建的过程。

Using the SIP build system is simply a matter of writing a small Python script. In this simple example we will assume that the word library we are wrapping and it’s header file are installed in standard system locations and will be found by the compiler and linker without having to specify any additional flags.  In a more realistic example your Python script may take command line options, or search a set of directories to deal with different configurations and installations.

创建SIP的构建系统主要是编写一个Python的脚本,非常简单。我们假设所有的库已经安装缺省位置安装(在更复杂的例子中,再解释如何处理不同的目录问题。)

This is the simplest script (conventionally called configure.py):

import os
import sipconfig

# The name of the SIP build file generated by SIP and used by the build
# system.
build_file = "word.sbf"

# Get the SIP configuration information.
config = sipconfig.Configuration()

# Run SIP to generate the code.
os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "word.sip"]))

# Create the Makefile.
makefile = sipconfig.SIPModuleMakefile(config, build_file)

# Add the library we are wrapping.  The name doesn‘t include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_libs = ["word"]

# Generate the Makefile itself.
makefile.generate()

Hopefully this script is self-documenting.  The key parts are theConfiguration and SIPModuleMakefile classes.  The build system contains other Makefile classes, for example to build programs or to call other Makefiles in sub-directories.

After running the script (using the Python interpreter the extension module is being created for) the generated C++ code and Makefile will be in the current directory.

To compile and install the extension module, just run the following commands [3]:

make
make install

That’s all there is to it.

See Building Your Extension with distutils for an example of how to build this example using distutils.

[1] All SIP directives start with a % as the first non-whitespace character of a line.
[2] SIP includes many code directives like this.  They differ in where the supplied code is placed by SIP in the generated code.
[3] On Windows you might run nmake or mingw32-make instead.

A Simple C Example

Let’s now look at a very similar example of wrapping a fictional C library:

/* Define the interface to the word library. */

struct Word {
    const char *the_word;
};

struct Word *create_word(const char *w);
char *reverse(struct Word *word);

The corresponding SIP specification file would then look something like this:

/* Define the SIP wrapper to the word library. */

%Module(name=word, language="C")

struct Word {

%TypeHeaderCode
#include <word.h>
%End

    const char *the_word;
};

struct Word *create_word(const char *w) /Factory/;
char *reverse(struct Word *word);

Again, let’s look at the differences between the two files.

  • The %Module directive specifies that the library being wrapped is implemented in C rather than C++.  Because we are now supplying an optional argument to the directive we must also specify the module name as an argument.

  • The %TypeHeaderCode directive has been added.

  • The Factory annotation has been added to the create_word()function.  This tells SIP that a newly created structure is being returned and it is owned by Python.

The configure.py build system script described in the previous example can be used for this example without change.

A More Complex C++ Example

In this last example we will wrap a fictional C++ library that contains a class that is derived from a Qt class.  This will demonstrate how SIP allows a class hierarchy to be split across multiple Python extension modules, and will introduce SIP’s versioning system.

The library contains a single C++ class called Hello which is derived from Qt’s QLabel class.  It behaves just like QLabel except that the text in the label is hard coded to be Hello World.  To make the example more interesting we’ll also say that the library only supports Qt v4.2 and later, and also includes a function called setDefault() that is not implemented in the Windows version of the library.

The hello.h header file looks something like this:

// Define the interface to the hello library.

#include <qlabel.h>
#include <qwidget.h>
#include <qstring.h>

class Hello : public QLabel {
    // This is needed by the Qt Meta-Object Compiler.
    Q_OBJECT

public:
    Hello(QWidget *parent = 0);

private:
    // Prevent instances from being copied.
    Hello(const Hello &);
    Hello &operator=(const Hello &);
};

#if !defined(Q_OS_WIN)
void setDefault(const QString &def);
#endif

The corresponding SIP specification file would then look something like this:

// Define the SIP wrapper to the hello library.

%Module hello

%Import QtGui/QtGuimod.sip

%If (Qt_4_2_0 -)

class Hello : public QLabel {

%TypeHeaderCode
#include <hello.h>
%End

public:
    Hello(QWidget *parent /TransferThis/ = 0);

private:
    Hello(const Hello &);
};

%If (!WS_WIN)
void setDefault(const QString &def);
%End

%End

Again we look at the differences, but we’ll skip those that we’ve looked at in previous examples.

  • The %Import directive has been added to specify that we are extending the class hierarchy defined in the file QtGui/QtGuimod.sip. This file is part of PyQt4.  The build system will take care of finding the file’s exact location.

  • The %If directive has been added to specify that everything[4] up to the matching %End directive only applies to Qt v4.2 and later.  Qt_4_2_0 is a tag defined in QtCoremod.sip[5] using the %Timeline directive.  %Timelineis used to define a tag for each version of a library’s API you are wrapping allowing you to maintain all the different versions in a single SIP specification.  The build system provides support to configure.pyscripts for working out the correct tags to use according to which version of the library is actually installed.

  • The TransferThis annotation has been added to the constructor’s argument.  It specifies that if the argument is not 0 (i.e. the Helloinstance being constructed has a parent) then ownership of the instance is transferred from Python to C++.  It is needed because Qt maintains objects (i.e. instances derived from the QObject class) in a hierachy.  When an object is destroyed all of its children are also automatically destroyed.  It is important, therefore, that the Python garbage collector doesn’t also try and destroy them.  This is covered in more detail in Ownership of Objects.  SIP provides many other annotations that can be applied to arguments, functions and classes. Multiple annotations are separated by commas.  Annotations may have values.

  • The = operator has been removed.  This operator is not supported by SIP.

  • The %If directive has been added to specify that everything up to the matching %End directive does not apply to Windows.WS_WIN is another tag defined by PyQt4, this time using the%Platforms directive.  Tags defined by the%Platforms directive are mutually exclusive, i.e. only one may be valid at a time [6].

One question you might have at this point is why bother to define the private copy constructor when it can never be called from Python?  The answer is to prevent the automatic generation of a public copy constructor.

We now look at the configure.py script.  This is a little different to the script in the previous examples for two related reasons.

Firstly, PyQt4 includes a pure Python module called pyqtconfig that extends the SIP build system for modules, like our example, that build on top of PyQt4. It deals with the details of which version of Qt is being used (i.e. it determines what the correct tags are) and where it is installed.  This is called a module’s configuration module.

Secondly, we generate a configuration module (called helloconfig) for our own hello module.  There is no need to do this, but if there is a chance that somebody else might want to extend your C++ library then it would make life easier for them.

Now we have two scripts.  First the configure.py script:

import osimport sipconfigfrom PyQt4 import pyqtconfig# The name of the SIP build file generated by SIP and used by the build# system.build_file = "hello.sbf"# Get the PyQt4 configuration information.config = pyqtconfig.Configuration()# Get the extra SIP flags needed by the imported PyQt4 modules.  Note that# this normally only includes those flags (-x and -t) that relate to SIP‘s# versioning system.pyqt_sip_flags = config.pyqt_sip_flags# Run SIP to generate the code.  Note that we tell SIP where to find the qt# module‘s specification files using the -I flag.os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "-I", config.pyqt_sip_dir, pyqt_sip_flags, "hello.sip"]))# We are going to install the SIP specification file for this module and# its configuration module.installs = []installs.append(["hello.sip", os.path.join(config.default_sip_dir, "hello")])installs.append(["helloconfig.py", config.default_mod_dir])# Create the Makefile.  The QtGuiModuleMakefile class provided by the# pyqtconfig module takes care of all the extra preprocessor, compiler and# linker flags needed by the Qt library.makefile = pyqtconfig.QtGuiModuleMakefile(
    configuration=config,
    build_file=build_file,
    installs=installs)# Add the library we are wrapping.  The name doesn‘t include any platform# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the# ".dll" extension on Windows).makefile.extra_libs = ["hello"]# Generate the Makefile itself.makefile.generate()# Now we create the configuration module.  This is done by merging a Python# dictionary (whose values are normally determined dynamically) with a# (static) template.content = {
    # Publish where the SIP specifications for this module will be
    # installed.
    "hello_sip_dir":    config.default_sip_dir,

    # Publish the set of SIP flags needed by this module.  As these are the
    # same flags needed by the qt module we could leave it out, but this
    # allows us to change the flags at a later date without breaking
    # scripts that import the configuration module.
    "hello_sip_flags":  pyqt_sip_flags}# This creates the helloconfig.py module from the helloconfig.py.in# template and the dictionary.sipconfig.create_config_module("helloconfig.py", "helloconfig.py.in", content)

Next we have the helloconfig.py.in template script:

from PyQt4 import pyqtconfig# These are installation specific values created when Hello was configured.# The following line will be replaced when this template is used to create# the final configuration module.# @SIP_CONFIGURATION@class Configuration(pyqtconfig.Configuration):
    """The class that represents Hello configuration values.    """
    def __init__(self, sub_cfg=None):
        """Initialise an instance of the class.        sub_cfg is the list of sub-class configurations.  It should be None        when called normally.        """
        # This is all standard code to be copied verbatim except for the
        # name of the module containing the super-class.
        if sub_cfg:
            cfg = sub_cfg
        else:
            cfg = []

        cfg.append(_pkg_config)

        pyqtconfig.Configuration.__init__(self, cfg)class HelloModuleMakefile(pyqtconfig.QtGuiModuleMakefile):
    """The Makefile class for modules that %Import hello.    """
    def finalise(self):
        """Finalise the macros.        """
        # Make sure our C++ library is linked.
        self.extra_libs.append("hello")

        # Let the super-class do what it needs to.
        pyqtconfig.QtGuiModuleMakefile.finalise(self)

Again, we hope that the scripts are self documenting.

[4] Some parts of a SIP specification aren’t subject to version control.
[5] Actually in versions.sip.  PyQt4 uses the %Includedirective to split the SIP specification for Qt across a large number of separate .sip files.
[6] Tags can also be defined by the %Feature directive.  These tags are not mutually exclusive, i.e. any number may be valid at a time.

Ownership of Objects

When a C++ instance is wrapped a corresponding Python object is created.  The Python object behaves as you would expect in regard to garbage collection - it is garbage collected when its reference count reaches zero.  What then happens to the corresponding C++ instance?  The obvious answer might be that the instance’s destructor is called.  However the library API may say that when the instance is passed to a particular function, the library takes ownership of the instance, i.e. responsibility for calling the instance’s destructor is transferred from the SIP generated module to the library.

Ownership of an instance may also be associated with another instance.  The implication being that the owned instance will automatically be destroyed if the owning instance is destroyed.  SIP keeps track of these relationships to ensure that Python’s cyclic garbage collector can detect and break any reference cycles between the owning and owned instances.  The association is implemented as the owning instance taking a reference to the owned instance.

The TransferThis, Transfer and TransferBack annotations are used to specify where, and it what direction, transfers of ownership happen.  It is very important that these are specified correctly to avoid crashes (where both Python and C++ call the destructor) and memory leaks (where neither Python and C++ call the destructor).

This applies equally to C structures where the structure is returned to the heap using the free() function.

See also sipTransferTo(), sipTransferBack() andsipTransferBreak().

Types and Meta-types

Every Python object (with the exception of the object object itself) has a meta-type and at least one super-type.  By default an object’s meta-type is the meta-type of its first super-type.

SIP implements two super-types, sip.simplewrapper andsip.wrapper, and a meta-type, sip.wrappertype.

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。