分类: Python/Ruby
2011-02-24 23:59:17
1.1. 起因
原以为用不到swig了。以 前没有专门看过,只做过个小例子。后来不玩不玩又都忘了。前几天玩pyopengl,发现一个很奇怪的事情。原始的意图是准备不开窗口,直接在一张位图中 离屏渲染3D图形。可惜OpenGL中没有定义标准的离屏渲染函数,离屏渲染的工作被扔到各个系统中分别实现。OpenGL有几种主要实现,在X系统上是 GLX,在windows系统上是WGL,在Apple上是AGL,它们各自都有支持离屏渲染的API,而且各个系统不一样。OS2上的OpenGL实现 PGL仿佛不叫离屏渲染,叫“获得上个缓冲区”,大概和离屏渲染差不多。OS2我大概八辈子用不到了。主要看看GLX和WGL大概差不多了。GLX一支 持,一大片的系统都支持了。够了。但是着手在WGL的试验中,我发现一个很奇怪的问题。用C++的代码写出的函数没问题。但是仿写成python总是会出 现莫名错误。也不知道到底是什么原因,哪块出了问题也实在不好找(因为涉及到pyopengl,pywin32,PIL3个库,PIL可能在这里不是必要 的)。后来一想,算了,既然C++不会错,我就包装一个C++的库好了。把离屏渲染的准备工作和扫尾工作放到库中,用Python调用这个库进行初始化离 屏渲染的上下文环境和删除上下文环境的工作。具体的OpenGL绘制放到Python中调用(或者全部放到C++中,由Python计算控制视点变化和状 态改变?)
1.2. 一个最简单的绑定
既然考虑到用绑定了,问题就来了。绑定到底怎么玩?虽然以前玩过,但是一知半解,只是随便玩玩,现在却一点也想不起来怎么弄的了。绑定C++到Python有很多的选择,到底用哪种?
我想起GDAL。这个库就是C++写的,但是通过swig绑定到了Python。现在我都用Python来调用GDAL了。是否可以参考它呢?
研 究了一下,发现GDAL的Python绑定比较庞大(本来就比较庞大,它一个库就包含了一大堆的子库,通过静态和动态链接搞在一起,形成一个大库)。研究 了一个下午,终于有点懂了(其实可以借鉴一下pyshapelib这样比较小的库,结构比较简单)。它用到的主要是两个东西。一个swig,一个是标准库 distutils,swig用来绑定,distutils用来统一编译和安装。
既然用到了这两个东西,我们就来研究一下吧。
学习swig可以到,里面的Document有很多文档,比较可看的有和,还有一个,不过通过一个下午的研究发现可以不用太花力气在swig的使用和编译链接上面。swig主要要学的只是“.i”文件的写法,其他编译啊什么的都可以放到setup.py中,不过这就是distutils的任务了。
学习distutils直接就进入中察看。是教如何写setup.py文件的。它教了我们如何写setup.py文件,然后把我们的模块编译打包。现在仿佛应用最广的就是这种方式。
废话不多说。开始吧!我们做个最简单的。
首先要建立一个库,这一定是最先要做的。因为绑定绑定,首先要有个东西来绑,没有要绑的东西,还叫什么绑定!
建 库方法可以随便,只要能生成动态链接库就Ok(swig有两种绑定方式,一种静态链接,一种动态链接。静态链接是直接链接到Python核心中去,成为内 建模块形式,这一般是不用的,现在又不是非常时期,不需要打入敌人内部:)。一种是动态链接,形成一个模块,然后用的时候用import导入,这是一种我 们最熟悉的形式,而大多数的情况下都是这么做的)。
我 在windows下测试,所以我用vs建立了一个dll工程。我不是一个狂热的控制台一族,我还是喜欢IDE形式的。当然你如果在linux下,可以用 anjuta,kdevelop同样建立一个工程,或者你喜欢单用一个文本工具一个字一个字得敲以显示超牛的技术风格,没问题。我们只要能生成库就好。下 面说明的都是在windows下的试验过程。
建立成dll动态链接库工程TestModlue,然后把什么预编译头编译开关去掉,删掉那个什么stdafx.h 和 stdafx.cpp,把TestModule.cpp清空。
在TestModule.cpp中写几个函数吧!
#include
好 了,定义了两个函数,一个打印“helloworld”一个做加法,这里需要注意的是_ _ declspec(dllexport)。这是函数导出的标志。一定要加这个。不然链接不会出现lib文件。函数导出有两种方法,一种是这个,一种是 def模块定义文件,我用前一种只是因为我习惯用那个。不过看起来def的定义方式更容易后期修改。你可以试一试。
好 了,生成工程。我有一步修改工程属性的步骤。修改了生成dll和lib的路径和名称。默认的路径在Debug目录下或者在release目录下,名称和工 程名相同。同名的dll到后面和swig生成的py文件有冲突。所以我把生成目录转移到工程目录下,把lib的名称改成TestModuleD.lib, 把dll名称改成TestModuleDCpp.dll,之所以转移目录是为了后面指定目录路径不会太深。在工程名后面加D说明是Debug版本,为了和 Release版本区分,你也可以不加,不过后面指定lib的名称时需要注意。dll文件名加Cpp后缀是为了和py文件定义的模块区分。
这样我们就有了可以使用的C++库了。
为了把C++绑定到python,要写一个.i文件。跳出TestModule文件夹,建立一个TestModule.i的文件,然后写一下给swig用来转化的接口。
%module TestModule
还是很简单的。%module后面跟模块名。下面%{ %}中间的内容是需要包含的头文件和函数定义。下面是函数的定义声明。
现在可以用swig生成绑定的cpp文件了,不过等等,不要像swig教程那样编译链接,虽然可以这样做,但是我们有distutils了!有炮不用,用鸟枪?
要 用distutils,就要建立一个setup.py文件。这个有这个文件,我们就可以简单地使用python setup.py build 和python setup.py install来编译链接,并且安装模块到python系统库中。一切其他的事情都不用你管。
建立一个setup.py。
写setup.py 其实很简单,需要的就只有一行(其他别看那么多,全部都是服务于那一行)。
最简单的setup.py 看,不过这还不是最简单的,最简单的是这样!
from distutils.core import setup
当然这样什么用都没有。
我们要编译一个模块,就要在setup中的ext_modules参数的列表中添加模块定义,这个模块定义是需要用到Extension类的。模块的定义看起来像这样:
TestModule_module = Extension('_TestModule',
第 一个参数是模块名,这里是编译出来后的模块名,这个模块名最好不要和原来的模块同名,一般是在前面加个"_",但是也不要把模块名乱改,不然生成的绑定 cpp链接后init方法认不到。sources参数是指定要编译的源文件。这个注意,源文件是经过swig转化后的cpp文件,一般是模块名加个后缀 _wrap。include_dirs是指定编译时需要额外包含头文件的路径。libraries是指定要链接时需要用来链接的库,这里因为需要链接 c++的库,所以要将TestModuleD.lib链接进来,这里因为上面在编译链接时有改lib的名称,所以这里用的是TestModuleD,如果 你没有改,就要用TestModule,反正要跟lib去掉扩展名后的名称一样。library_dirs指定查找libraries中库所在的路径。最 后一个是而外链接标记,不管它。
最后经过整理,排版,最后的setup.py是这样:
from distutils.core import setup,Extension
好了。所有工作做完,现在编译链接。
一般来说,编译链接的工作放到一个makefile中进行,不过这里要进行的工作很少,不用那么兴师动众吧!写个批处理文件就好。
建立一个runsetup.bat,输入下列内容:
@echo off
这里看到只作了两件事,一个是swig生成绑定的cpp,一个是用setup.py 进行编译。
好了,运行这个runsetup.bat吧!
running build
好了生成成功!我们可以看到生成一个build文件夹,生成的东西放在build文件下的lib.win32-2.4目录下,是一个_TestModule.pyd的文件。
现在我们试试看python能否正确调用:
建立一个bin文件夹,把生成的_TestModule.pyd文件和TestModule.py文件还有那个C++生成的TestModuleDCpp.dll拷贝进来,然后在这个bin目录下运行python交互解释器。
>>> import TestModule
It work!运行正常!
不过可以看到dir(TestModule)会有一堆乱七八糟的东西出来。这是怎么回事?
>>> import _TestModule
试试这个!果然清爽很多。
因 为生成TestModule是swig的工作,而真正的绑定是在_TestModule中做的。我们完全可以不要swig生成的py文件,自己写一个。导 入_TestModule中所有的原生态的东西。甚至可以对_TestModule进行包装!正像GDAL中做的一样,_gdal.dll只是包装了c的 绑定,gdal.py没有采用swig生成的那个,而是自己写了一个,在新的gdal.py中对_gdal.dll中的C函数进行包装,形成C++类。而 不是直接在Swig的i文件中包装成python类。