SWIG 实现传递 C++ 的数组到 perl

写了个基本的例子,作为后面的参考。下面的是 C++ 程序,我们看见用 vector<int> 返回了一个对象。一个函数 init() 是为了初始化随机种子,之后产生的 functor 也就是 random_number 作为一个 generator 通过 generate 这个 algorithm 对返回的 vector<int> 进行了赋值。

// random_array.cpp
#include <vector>
#include <iterator>
#include <algorithm>

#include <cstdlib>
#include <ctime>

using namespace std ;

class random_number {
 int q ;
public:
 random_number( int p ) : q(p) {}

 int
 operator() () {
 return rand() % q ;
 }
} ;

vector<int>
generate_random_array( int l, int q ) {
 vector<int> v( l ) ;
 generate( v.begin(), v.end(), random_number(q) ) ;
 return v ;
}

void
init() {
 srand( time( NULL ) ) ;
}

下面是对应的头文件,声明了两个函数。

#ifndef RANDOM_ARRAY_HPP
#define RANDOM_ARRAY_HPP

#include <vector>

using namespace std ;

vector<int>
generate_random_array( int, int ) ;

void
init() ;

#endif

这里是 wrapper 的代码,和前面不一样的是我们使用了 SWIG 的 std_vector.i 这个库,另外还注意那个 %template 的作用。

%module random_array

%include "std_vector.i"

namespace std {
 %template(vectori) vector<int> ;
} ;

%{
#include "random_array.hpp"
%}

%include "random_array.hpp"

下面是 Makefile。

CXXFLAGS += -I/usr/lib/perl/5.10.1/CORE/ -D_GNU_SOURCE

random_array.so: random_array.o random_array_wrap.cxx
        $(CXX) -shared $(CXXFLAGS) $^ -o $@

random_array_wrap.cxx: random_array.i
        swig -c++ -perl $^

.PHONY: clean

clean:
        rm -f *.o *.pm *~ *.so *_wrap.*

测试程序。

#!/usr/bin/perl -w
BEGIN {
 push @INC, "." ;
}

use strict ;
use random_array ;

random_array::init() ;
my $arr = random_array::generate_random_array( 20, 100 ) ;
for( my $i = 0 ; $i < @$arr ; ++ $ i ) {
 print $arr->[$i], "\n" ;
}

——————
God says to man, “I heal you therefore I hurt, love you therefore punish.”

Advertisements
SWIG 实现传递 C++ 的数组到 perl

SWIG 初体验

想来在这个 scripting language 盛行的时代很多人都不大会选择 C/C++ 作为首选的开发语言,其中一个问题在于尽管 C/C++ 的执行效率很高,却不拥有 scripting language 里面那些最让人觉得好用的东西,比如不严格的类型检查、garbage collection 等等,另外这些语言的解释器比起 C/C++ 的写程序、编译、测试的过程更加便捷(在 interpreter 的命令行上面玩玩多方便,瞧瞧人家 matlab)。

可是很多脚本语言还是需要有 C/C++ 模块的支持的,所谓的模块无非就是对某些 C/C++ 类或者函数的 wrapper,使得原来的一些函数能与脚本语言的解释器进行交互,因此本质上所谓的模块就是一个解释器能够动态载入的动态链接库。我们知道 Matlab 的 mex 就是干这个事情,我们也知道,每个 mex 的入口函数都是 mexFunction。那么千千万万的脚本语言的接口我们都要去了解是不是过于沉重的负担了呢?SWIG 的出现一定程度的缓解了这种开销。

SWIG 的设计就是通过 swig 命令进行预处理,用一个模块说明文件(一般是 .i 后缀)产生 wrapper,和对应的 C/C++ 程序一起编译连接成为动态链接库。(注:这部分代码主要来自 SWIG 的 manual)我们看看如下的一个接口声明文件:

/* File : example.i */
%module example
%{
/* Put headers and other declarations here */
extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);
%}

extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);

这里声明了一个 double 和两个函数,这两个函数非常简单啦,如下

/* File : example.c */

double  My_variable  = 3.0;

/* Compute factorial of n */
int  fact(int n) {
	if (n <= 1) return 1;
	else return n*fact(n-1);
}

/* Compute n mod m */
int my_mod(int n, int m) {
	return(n % m);
}

然后有如下的 Makefile:

CFLAGS += -I/usr/lib/perl/5.10/CORE/
CC = /usr/bin/gcc-4.1

example_wrap.c: example.pm

example.pm: example.i
	swig -perl $^

example.so: example.o example_wrap.o
	gcc -shared $^ -o$@

.PHONY: clean

clean:
	rm -f *.o *.pm *~

我们 make example.so 后,可以用如下的 perl 程序测试:

#!/usr/bin/perl -w

use strict ;
use example ;

print example::fact(4), "\n";
print example::my_mod(23,7), "\n";
print $example::My_variable + 4.5, "\n";

我们来看看上面这些东西怎么组合在一起的:首先看 Makefile,这揭示了几个文件的依赖关系,我们知道模块由原来的那个 C/C++ 源程序和一个 wrapper 组成(即 example_wrap.c),这个接口文件是 SWIG 生成的,然后 SWIG 还生成了一个 .pm 文件,这是为了利用脚本语言的特性(即 perl 的 use 或者 require 读入一个模块)。然后我们在 perl 程序里面非常 perl 的使用了这些东西。

后面有空的话将详细的研究一下 perl 的各个数据结构和 C/C++ 的转换方法。这样你有什么 C/C++ 的库,你就能用 perl 使用它们了。记得很多语言的 binding 么,比如 gtk、Qt 等在 python 里面的 binding,现在大约明白我们怎么将这些 GUI kits 用在 scripting language 里面了。google 一下,看看 Matlab 有没有 SWIG 的支持,ms 是有的!

PS: debian 里面的那个 perl 的 CORE 似乎跟 gcc 4.1 以后的有问题,在一台 solaris 上 gcc 4.0 没问题,但是我在 debian 用 gcc-4.1 就不行,汗…

——————
My day is done, and I am like a boat drawn on the beach, listening to the dance-music of the tide in the evening.

SWIG 初体验

使用 gnuplot 返回动态图片(续)

前面说到这个简单的 CGI 技巧后,stevenshen 在 facebook 上提出了一些疑问。虽然给了一些解决的方案,但是觉得还是不很完美,今天早上突然受到启发,因为不 work 的原因有一部分是 echo 引起的,造成命令行参数过长(如果数据很多)。但是如果将数据放在一个临时文件又觉得不合算,放在临时文件里面的好处是,调用 cat 就行了,命令行的长度就不会受到影响。

因此,最好的方法是直接使用管道,将输入灌入管道,如果所在的操作系统支持 fork,那么 perl 可以很容易完成这个任务,perl 在打开文件的时候可以用 | 表示管道,如果我们需要将输入喂进一个程序,可以用 |-,如果是需要从某个程序读输出,则是 -|,这里 | 哪边留空哪边是给我们的程序控制的,那么 – 可以用某些程序直接替代,比如 gnuplot。

管道工作的方式就是修改某些 file descriptor,然后 fork,比如我们创建的 gnuplot,就希望它的输入不是 stdin,但是输出还是 stdout(和父进程一致),这样我们在父进程对其标准输入进行写就可以了。下面是一个例子

my $pid = open PIPE, "|-";
die "Cannot fork $!" unless defined $pid;

unless ( $pid ) {
    exec( $gnuplot ) or die "Cannot open pipe to gnuplot: $!";
}

后面在父进程里面对 PIPE 进行 print,把我们的程序输入,最后 close PIPE ; 即可。

这个方法额外的好处是比较安全,因为直接 echo 的时候,如果输入里面含有一些恶意的东西就比较麻烦,因为整个语句是在 shell 里面解析的,但是现在的做法输入完全喂给子进程,一般该进程不会处理偏系统的功能,比如 rm 什么的,因此整体上安全一些。

使用 gnuplot 返回动态图片(续)