32 SWIG and Python

32 SWIG and Python

32.1 Overview

  为了构建Python扩展模块,SWIG使用分层的方法,其中部分扩展模块用C定义,其他部分用Python定义。C层包含低级包装器,而Python代码用于定义高级特性。

  这种分层的方法认识到这样一个事实,即扩展构建的某些方面在每种语言中都能更好地完成(而不是试图用C或c++完成所有工作)。此外,通过用两种语言生成代码,您可以获得更多的灵活性,因为您可以用任何一种语言的支持代码来增强扩展模块。

  在描述Python接口时,本章从介绍配置、编译和安装Python模块的基础知识开始。接下来,描述Python对常见C和c++编程特性的接口。然后描述诸如类型映射之类的高级定制特性,然后讨论底层实现细节。

32.2 Preliminaries

32.2.1 Running SWIG

  假设你定义了一个SWIG模块,如下所示:

/* File: example.i */
%module example

%{
#define SWIG_FILE_WITH_INIT
#include "example.h"
%}

int fact(int n);

  #define SWIG_FILE_WITH_INIT行插入了一个宏,该宏指定生成的C文件应该作为Python扩展构建,并插入模块init代码。这个i文件包装了以下简单的C文件:

/* File: example.c */

#include "example.h"

int fact(int n) {
  if (n < 0) { /* This should probably return an error, but this is simpler */
    return 0;
  }
  if (n == 0) {
    return 1;
  } else {
    /* testing for overflow would be a good idea here */
    return n * fact(n-1);
  }
}

  在头文件中:

/* File: example.h */

int fact(int n);

  要构建Python模块,请使用-python选项运行SWIG:

$ swig -python example.i

  如果要构建c++扩展,添加-c++选项:

  这会创建两个不同的文件;一个C/ c++源文件 example_wrap.c 或 example_wrap.cxx。和一个Python源文件example.py。生成的C源文件包含低级包装器,这些包装器需要编译,并与C/ c++应用程序的其余部分链接,以创建扩展模块。Python源文件包含高级支持代码。这是您将导入以使用模块的文件。

  包装器文件的名称派生自输入文件的名称。例如,如果输入文件是example。包装器文件的名称是example_wrap.c。要改变这一点,可以使用-o选项。Python文件的名称派生自使用%module指定的模块名。如果模块名是example,则会创建一个example.py文件。

  下面几节将提供进一步的实际示例和详细信息,介绍如何编译和使用生成的文件。

32.2.2 Using distutils

  为Python构建扩展模块的首选方法是使用distutils进行编译,distutils与所有最新版本的Python (distutils Docs)一起提供。

  Distutils负责确保你的扩展是使用Python版本的所有正确标志、头文件等构建的。Distutils将编译您的扩展到一个共享的目标文件或DLL(。在Linux上是。pyd,在Windows上是。pyd等等)。此外,如果需要,distutils可以处理将包安装到site-packages中。配置文件(通常称为:setup.py)描述了扩展(和相关的Python模块)。然后distutils将生成所有正确的编译器指令来为您构建它。

  下面是上述示例的一个示例setup.py文件:

#!/usr/bin/env python

"""
setup.py file for SWIG example
"""

from distutils.core import setup, Extension


example_module = Extension('_example',
                           sources=['example_wrap.c', 'example.c'],
                           )

setup (name = 'example',
       version = '0.1',
       author      = "SWIG Docs",
       description = """Simple swig example from docs""",
       ext_modules = [example_module],
       py_modules = ["example"],
       )

  在这个例子中,行:example_module = Extension(....)创建了一个Extension模块对象,将名称定义为_example,并使用由swig生成的源代码文件example_wrap.c和您的原始c源代码example.c。swig(和其他Python扩展模块)的传统是让编译后的扩展具有Python部分的名称,并以下划线作为前缀。如果你的Python模块的名称是"example.py",那么相应的目标文件的名称将是"_example.so"

  然后setup调用设置distutils来构建包,定义一些元数据,并传入扩展模块对象。一旦这个被保存为setup.py,你可以用下面的命令来构建你的扩展:

$ swig -python example.i
$ python setup.py build_ext --inplace

  还有一个.so,或者.pyd或者…将为你而创造。它将构建一个与您运行该命令时使用的Python相匹配的版本。分解命令行:

  • python -- the version of Python you want to build for
  • setup.py -- the name of your setup script (it can be called anything, but setup.py is the tradition)
  • build_ext -- telling distutils to build extensions
  • --inplace -- this tells distutils to put the extension lib in the current dir. Otherwise, it will put it inside a build hierarchy, and you'd have to move it to use it.

32.2.3 Hand compiling a dynamic module

  虽然构建扩展模块的首选方法是使用distutils,但有些人喜欢将构建扩展与更大的构建系统集成,因此可能希望在不使用distutils的情况下编译他们的模块。要做到这一点,你需要使用像这样的命令来编译你的程序(在Linux中显示):

$ swig -python example.i
$ gcc -O2 -fPIC -c example.c
$ gcc -O2 -fPIC -c example_wrap.c -I/usr/local/include/python2.5
$ gcc -shared example.o example_wrap.o -o _example.so

  执行此操作的确切命令因平台而异。但是,SWIG在安装时尝试猜测正确的选项。因此,您可能想从SWIG/ examples /python目录中的一个示例开始。如果这不起作用,您将需要阅读编译器和链接器的手册页以获得正确的选项集。您也可以查看SWIG Wiki以获取更多信息。

  当链接模块时,输出文件的名称必须匹配带下划线前缀的模块名称。如果你的模块名称是"example",那么对应的目标文件的名称应该是"_example "。所以”或“_examplemodule。所以”。模块的名称可以使用%module指令或-module命令行选项指定。

  兼容性注意:在SWIG-1.3.13和更早的版本中,模块名称不包含前导下划线。这是因为模块通常只作为c扩展创建,不需要额外的Python支持文件(相反,创建Python代码是一个可选特性)。这在SWIG-1.3.14中已经更改,并且与其他Python扩展模块一致。例如,socket模块实际上由两个文件组成;socket.py _socket.so。许多其他内置Python模块遵循类似的约定。

32.2.4 Static linking

  动态链接的另一种方法是重新构建Python解释器,并在其中添加扩展模块。在过去,这种方法有时是必要的,因为在某些机器上的动态负载支持的限制。然而,这种情况在过去几年已经大大改善,除非真的没有其他选择,否则你不应该考虑这种方法。

  向Python中添加新模块的通常过程包括找到Python源代码,在Modules/Setup文件中添加条目,并使用Python Makefile重新构建解释器。然而,新的Python版本已经改变了构建过程。你可能需要编辑Python发行版中的'setup.py'文件。

  在SWIG的早期版本中,embed.i 库文件可以用来重建解释器。例如:

%module example

%inline %{
extern int fact(int);
extern int mod(int, int);
extern double My_variable;
%}

%include "embed.i"       // Include code for a static version of Python

  embed.i 库文件包含支持代码,其中包含重建Python所需的一切。要重新构建解释器,只需做如下操作:

  您将需要提供与第一次构建Python时相同的库。这可能包括系统库,如-lsocket、-lnsl和-lpthread。假设这实际工作,Python的新版本应该与默认版本相同,除了你的扩展模块将是解释器的内置部分。

  注释:在实践中,如果可能的话,您应该尽量避免使用静态链接。为了获得更好的性能,一些程序员可能倾向于使用静态链接。然而,在大多数情况下,静态链接所获得的性能往往是相当小的(坦率地说,在本文作者看来,不值得为此付出额外的努力)。

  兼容性说明:embed.i库文件已弃用,并且多年来没有得到积极的维护。即使它看起来与Python 2.7“工作”,也不能保证将来会得到支持。如果使用静态链接,您可能需要依赖另一种方法(可能是使用distutils)。

32.2.5 Using your module

   要使用模块,只需使用Python的import语句。如果一切顺利,你将能够运行这个:

32.2.6 Compilation of C++ extensions

  编译c++扩展一直以来都是一个棘手的问题。由于Python解释器是用C编写的,所以您需要采取步骤来确保c++被正确初始化,以及模块被正确编译。如果您使用distutils,这应该不是问题,因为它会为您处理所有这些问题。以下内容是出于历史原因而包含的,如果您需要自己编译的话。

  在大多数机器上,c++扩展模块应该使用c++编译器链接。例如:

$ swig -c++ -python example.i
$ g++ -O2 -fPIC -c example.cxx
$ g++ -O2 -fPIC -c example_wrap.cxx -I/usr/local/include/python2.5
$ g++ -shared example.o example_wrap.o -o _example.so

  -fPIC选项告诉GCC生成位置无关的代码(PIC),这是大多数架构所需要的(这在x86上并不重要,但仍然是一个好主意,因为它允许在进程之间共享库中的代码页)。其他编译器可能需要指定一个不同的选项来代替-fPIC。

  除此之外,您可能需要包含额外的库文件以使其工作。例如,如果你在Solaris上使用Sun c++编译器,你通常需要添加一个额外的库- lrun,像这样:

$ swig -c++ -python example.i
$ CC -c example.cxx
$ CC -c example_wrap.cxx -I/usr/local/include/python2.5
$ CC -G example.o example_wrap.o -L/opt/SUNWspro/lib -o _example.so -lCrun

  当然,要使用的额外库是完全不可移植的——您可能需要进行一些试验。

  有时人们建议,有必要使用c++编译器重新链接Python解释器,以使c++扩展模块工作。根据本文作者的经验,这似乎从来没有必要。用c++重新链接解释器实际上只包括上面描述的特殊运行时库——只要您将扩展模块与这些库链接,就不应该重新构建Python。

  如果您不能完全确定c++扩展的链接,您可以查看现有的c++程序。在许多Unix机器上,ldd命令将列出库依赖项。这将为您提供一些线索,说明在链接扩展模块时可能需要包括哪些内容。例如:

  最后一个复杂的问题是,c++的一个主要缺点是它没有为库的二进制链接定义任何类型的标准。这意味着由不同编译器编译的c++代码不能正确地作为库链接在一起,也不能以任何可移植的方式实现类的内存布局和数据结构。在一个单片c++程序中,这个问题可能不会被注意到。然而,在Python中,可以用不同的c++编译器编译不同的扩展模块。只要这些模块是自包含的,这可能并不重要。然而,如果这些模块开始共享数据,您将需要采取步骤来避免分割错误和其他不稳定的程序行为。如果要处理大量的软件组件,您可能需要研究使用更正式的标准,比如COM。

32.2.7 Compiling for 64-bit platforms

  在支持64位应用程序的平台上(Solaris、Irix等),在构建扩展模块时需要特别注意。在这些机器上,64位应用程序使用不同的一组编译器/链接器选项进行编译和链接。此外,通常不可能在同一个应用程序中混合32位和64位代码。

  要使用64位,Python可执行文件需要重新编译为64位应用程序。此外,应用程序的所有库、包装器代码和其他所有部分都需要针对64位进行编译。如果您计划使用其他第三方扩展模块,也必须将它们重新编译为64位扩展。

  如果您正在包装没有源代码的商业软件,您将被迫使用与该软件使用的相同的链接标准。这可能会阻止64位扩展的使用。在支持多个链接标准的平台上(例如,Irix上的-o32和-n32)也可能会出现问题。

  在Linux x86_64平台(Opteron或EM64T)上,除了上面讨论的必需的编译器选项-fPIC外,还需要注意链接的库或使用的库路径。通常,Linux发行版将有两组库,一组用于本机x86_64程序(在/usr/lib64下),另一组用于32位兼容性(在/usr/lib下)。另外,编译器选项-m32和-m64允许您为您的Python扩展选择所需的二进制格式。

32.2.8 Building Python extensions under Windows

  在Windows下构建Python的SWIG扩展的过程与在Unix中使用的过程大致类似。使用distutils,它在本质上是相同的。如果你的MS编译器与Python编译时使用的版本相同(python.org发行的python2.4和python2.5是使用Visual Studio 2003编译的),那么标准的Python setup.py编译应该可以工作。

  从python2.5开始,distutils支持使用MingGW直接构建扩展。遵循这里的说明:只使用免费工具为Windows构建Python扩展应该是你的入门。

  如果你需要自己构建它,以下的注释提供:

  •   您将需要创建一个可以加载到解释器中的DLL。本节简要描述在Microsoft Visual c++中使用SWIG。作为起点,许多SWIG的示例包括项目文件(。dsp文件)的Visual c++ 6。这些可以由最新版本的Visual Studio打开。除了阅读本节,您可能还想快速浏览一下这些示例。
  •   在Developer Studio中,SWIG应该作为自定义构建选项来调用。通常是这样做的:
  •   打开一个新的工作区并使用AppWizard来选择一个DLL项目。
  •   添加SWIG接口文件(即.i文件)、任何支持的C文件,以及将由SWIG创建的包装器文件的名称(即。example_wrap.c)。注意:如果使用c++,请为包装器文件选择不同的后缀,例如example_wrap.cxx。如果包装器文件还不存在,也不要担心——Developer Studio会保留对它的引用。
  •   选择SWIG接口文件并进入设置菜单。在设置下,选择“自定义构建”选项。
  •   在描述字段中输入“SWIG”。
  •   输入"swig -python -o $(ProjDir)\$(InputName)_wrap.c $(InputPath)"在"Build command(s) field" " Enter "$(ProjDir)\$(InputName)_wrap.c"在"Output files(s) field"。
  •   接下来,选择整个项目的设置,然后进入“c++:Preprocessor”。在“附加包括目录”下添加Python安装的包括目录。在预处理器选项下定义符号__WIN32__。
  •   最后,选择整个项目的设置,然后进入“链接选项”。将Python库文件添加到链接库中。例如“python27.lib”。另外,将输出文件的名称设置为与Python模块的名称相匹配,例如_example.pyd
  •   构建您的项目。

  如果一切顺利,SWIG将在构建项目时自动调用。对接口文件所做的任何更改都将导致自动执行SWIG以生成包装器文件的新版本。

32.2.9 Additional Python commandline options

  下表列出了Python模块可用的其他命令行选项。它们也可以通过以下方式看到:

swig -python -help

  

32.9.6 Pointer handling

  有时候,可能需要转换使用SWIG类型指针表示存储的指针值。由于指针有几种表示方式,下面两个函数可以安全地执行这种转换:

  int SWIG_ConvertPtr(PyObject *obj, void **ptr, swig_type_info *ty, int flags)

  将Python对象obj转换为C指针。转换的结果被放入位于ptr的指针中。ty是一个SWIG类型描述符结构。标志用于处理错误检查和转换的其他方面。它是几个标志值的位或值,包括SWIG_POINTER_EXCEPTION和SWIG_POINTER_DISOWN。第一个标志使函数在类型错误时引发异常。第二个标志另外窃取对象的所有权。成功时返回0,错误时返回-1。

  PyObject *SWIG_NewPointerObj(void *ptr, swig_type_info *ty, int own)

  创建一个新的Python指针对象。ptr是要转换的指针,ty是描述该类型的SWIG类型描述符结构,own是一个标志,表示Python是否应该拥有该指针的所有权。

  这两个函数都需要使用特殊的SWIG类型描述符结构。这个结构包含了数据类型的名称、类型等价信息,以及c++继承下指针值转换的信息。对于Foo *类型,类型描述符结构通常被访问如下:

Foo *f;
if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &f, SWIGTYPE_p_Foo, 0))) {
  SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Foo");
}

PyObject *obj;
obj = SWIG_NewPointerObj(f, SWIGTYPE_p_Foo, 0);

  在typemap中,应该始终使用特殊的typemap变量$1_descriptor来访问类型描述符。例如:

%typemap(in) Foo * {
  if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0))) {
    SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Foo");
  }
}

  如果有必要,任何类型的描述符都可以在类型映射中使用$descriptor()宏获取。例如:

%typemap(in) Foo * {
  if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &$1, $descriptor(Foo *), 0))) {
    SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Foo");
  }
}

  虽然指针处理函数主要用于操作低级指针,但这两个函数都完全了解Python代理类。具体来说,SWIG_ConvertPtr()将从任何具有this属性的对象检索指针。此外,SWIG_NewPointerObj()可以自动生成代理类对象(如果适用)。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

posted @ 2021-12-01 13:32  神龙逗勇士  阅读(179)  评论(0)    收藏  举报