这是一篇非常有聊(?)的日记。
昨晚心血来潮,在尝试用SWIG(http://www.swig.org)给C++的库做Python绑定,网上例子非常多,看了几个就动手。后来被动态链接的问题折腾到晚上十二点半,对我这个每天早上7点前起床的人来说有点伤不起。而且,你知道吗,错误原因是我把函数名写错了,测试用源码里的randnum在接口里我写成了rand_num。
SWIG的工作原理是:你提供源代码,然后把源码里的部分声明抽出来,写成一个 扩展名.i 的文件提供给SWIG,再指定你的源码语言(比如C++)和目标语言(比如Python),SWIG会帮你生成一个长长的胶水源码,这个胶水代码就是python和C++的桥梁了。
编译的时候比较复杂,我直接说目的以便理清思路:最终你会得到两个动态链接库,一个是胶水库,一个是实际干活的库。胶水库是用胶水代码生成的,可以被python识别,而且里面没有具体函数实现。实际干活的库就是你的源码编译成的so文件,它和python、SWIG一点关系都没有是纯正的C/C++库,它会被胶水库调用。此外为了做一些管理工作还会生成一个xxxx.py 源码,写python代码的时候直接import xxxx就可以了。
既然有两个so文件,这就涉及到so搜索的问题。敢写这篇日记说明我已经基本搞清了:)。
首先,python有自己的导入dll/so扩展库的机制,可以通过python标准库的imp模块的imp.load_module实现,参数里可以指定路径。胶水库通过这种方式导入,这个很好理解。
其次,在导入胶水dll/so的时候,会导入相应依赖的dll/so,这个导入是操作系统级别的,也就是是C/C++领域的问题,和python没有关系。这个导入能否成功取决于两点:在搜索路径下能搜索到相应文件;这个文件里能找到所有需要的符号定义(函数实现),如果有未定义的符号会报错(至少运行时肯定会报错,这个地方表述不是完全准确,建议通读《程序员的自我修养——链接、装载与库》的前半部分。我不想误人子弟。)
既然这个导入取决于两个关键点,那么这两个关键点都有可能出错。
1、搜索路径问题。windows和linux的动态链接库,除了命名方式不同,还有个很大区别是搜索方式不同。windows默认先搜索当前目录,linux默认不搜索当前目录。具体顺序挺麻烦,请百度。在windows下只要把dll拷贝到当前工作目录就能用了(windows有个麻烦的事情,是注意两个so的命名不要一样了,可能造成冲突)。
linux建议这么做:执行shell命令export LD_LIBRARY_PATH=“工作目录”。这样可以临时的把当前目录加进来,而且不影响其他进程。如果你的库已经稳定了,就把你的so拷贝到/usr/local/lib或者类似的地方,它们是默认的so搜索路径。
2、动态链接库里的定义是否完整。为啥会不完整捏?举我犯过的这个脑残的例子。在胶水so里,我声明了一个函数rand_seed,在实际so里,没有rand_seed函数,只有randseed函数,而且这个错误在复杂的编译过程中都不会被发现,只有在动态加载这个工作so时才会发现这个错误!由于加载工作so的时候你已经在python环境里了,错误提示会让你很纳闷。
一开始我猜测还有一种找不到函数定义的情况:C++对函数名做了修饰(在非入门书籍上应该都有提到),但是我后来发现python会去找 _Zrand_numV_类似这种样子的符号,也就是说swig把这些问题都处理妥当了,不用担心。
做这种麻烦的事情,关键是理清思路,如果照着教程顺利走下来,真是没有什么收获,而且遇到复杂错误会茫然不知所措。基础知识是王道:)
附很好的一篇SWIG,C++ Python 入门教程:
http://hi.baidu.com/125725385/blog/item/8811a511d3cbae07213f2e97.html
———————————————一觉醒来,对上一节一些错误的修正—————————————————————
仔细看了一天SWIG,纳闷为啥要有两个动态链接库,放在一起岂不是更方便。仔细读了官方文档,果不其然。
http://www.swig.org/Doc1.3/SWIGDocumentation.html#Python
其中的:31.2.2 Using distutils,其方法就是写setup.py的时候,把俩库链接到一起,这样使用起来就没有上面那种方法找不到so的麻烦了。恩。但是按照本日记的核心思想,只有给自己找麻烦才能进步。
又学到一招神技:如果编译一个liba.so时引用到另一个libb.so,那么可以把b静态链接到a里,变成一个so,发布给别人的时候可能方便一些。大致写法如下,主要是用到gcc的 -Xlinker 参数。
$ gcc -shared example.o example_wrap.o -L/home/beazley/projects/lib -lfoo \ -Xlinker -rpath /home/beazley/projects/lib \ -o _example.so
———————————————————————————————————————————————————
几个月前,一位同事给我们的游戏项目GUI部分做控件扩展。我们的GUI界面都是用xml文件配置的,支持xml包含,类似C语言的include。他忙活了一天发现了一个诡异的xml包含时错误,百试不得其解。他给我看了这个问题,我看过配置也很头疼。后来我俩分工,我调试源码,他查配置,俩人折腾了一个多小时。最后就在我快要搞清xml包含机制的时候,他发现了问题:单词拼错了。
我感谢他的这次拼写错误,如果没有这次拼写错误,xml读取机制在我脑海中一直是个“谜团”(虽然现在仍然是小谜团)。顺带说一句,项目整个的GUI库包括独家xml库都是一位大侠自己写的。
思绪飘到了去年夏天,一位大侠扩展了脚本引擎代码,然后发生诡异的编译时异常。我查了一周多的时间,发现是扩展指令集的时候,优化用数组忘记扩展了,导致越界。我想说的是,在这个过程中,我用到了内存泄漏检查工具(原理是重载new进行记录),了解了各种VC++配置,知道了5种神奇的Debug技巧,生平第一次靠汇编代码调试程序。最后,这个可爱的BUG引领我走进了《编译原理》的大门……至少是完全了解了词法分析(至今停留在语法分析那章)。
我想说的是,一个有经验的程序员肯定有过不少类似的经历。对一个半吊子程序员来说尚且如此,更不用谈各种高手的成长经历了。其实对任何脑力劳动者,比如数学家、科学家可能都免不了这些经历。
总结一下,让自己记住:遇到任何小问题,不要轻易放过,更不要一开始就尝试绕过去。正面和它死磕,有极大可能性你会揪出一只大象。