perl 的C++扩展,返回值为自定义类型。
在 perl C/C++扩展(三) 中,我已经介绍了,如何让perl 认识 c++的类,但是前面的介绍中,包括我参考的博客http://chunyemen.org/archives/493,都提到,返回值必须是基础类型。对于开发者而言,如果返回值只能是基础类型,那么对于扩展的开发热情就大大降低了。楼主排除万难,终于在《高级perl编程(第二版)》.((美)simon cozens)一书的第十八章与第二十章中得到些许启发。
下面我来介绍一下玩法。
首先创建一个新的工程,名为Cat
h2xs -A -n Cat
创建好工程后,进入Cat目录
cd Cat
创建一个mylib目录,并将c++代码拷贝到mylib目录下
mkdie mylib
Cat.h
#ifndef INCLUDE_CAT_H #define INCLUDE_CAT_H 1 #include <iostream> class Cat { public: Cat(char *,int); Cat(const Cat &); void display(); char * getName(); int getAge(); ~Cat(); private: char * name; int age; }; #endif
Cat.cpp
#include "Cat.h" Cat::Cat(char * name, int age) { this->name = name; this->age = age; } Cat::Cat(const Cat & incat) { name = incat.name; age = incat.age; } void Cat::display() { std::cout<<"~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~"<<std::endl; std::cout<<"name="<<name<<" age="<<age<<std::endl; } char * Cat::getName() { return name; } int Cat::getAge() { return age; } Cat::~Cat(){}
Animal.h
#ifndef INCLUDE_ANIMAL_H #define INCLUDE_ANIMAL_H 1 #include <iostream> #include "Cat.h" class Animal { public: Animal(char *, int); void display(); Cat * getAnimal(); Cat * setAnimal(Cat *); bool haveAnimal(); ~Animal(); private: Cat * cat; }; #endif
Animal.cpp
#include "Animal.h" Animal::Animal( char * name, int age): cat( new Cat(name, age) ) {} void Animal::display() { std::cout<<"~~~~~~~~~~~Animal.display()~~~~~~~~~~~~~~~~~ "; cat->display(); } Cat * Animal::getAnimal() { return cat; } Cat * Animal::setAnimal(Cat * lcat) { //delete cat; cat = new Cat(*lcat); return cat; } bool Animal::haveAnimal() { //return false; return 1; //throw 1; } Animal::~Animal() { //delete cat; }
在mylib 目录下创建Makefile.PL 文件
mylib/Makefile.PL
1 use ExtUtils::MakeMaker; 2 $Verbose = 1; 3 WriteMakefile( 4 NAME => 'Animal::mylib', 5 SKIP => [qw(all static static_lib dynamic dynamic_lib)], 6 clean => {'FILES' => 'libanimal.so'}, 7 'CC' => 'g++', 8 ); 9 10 sub MY::top_targets { 11 ' 12 all :: static 13 pure_all :: static 14 static :: libanimal.so 15 libanimal.so: $(C_FILES) 16 $(CC) -shared -fpic -g -Wall -o libanimal.so $(C_FILES) 17 $(RANLIB) libanimal.so 18 '; 19 }
注意:mylib/Makefile.PL 的16 17 行前的不是空格键,而且table 键,因为这里是Makefile的代码,如果不按照Makefile格式编写,make 操作就会报错
退回Cat 目录
cd ../
创建一个typemap 文件,并写入如下内容。
typemap
TYPEMAP Cat * ANIMAL_OBJECT OUTPUT ANIMAL_OBJECT sv_setref_pv($arg, CLASS, (void *) $var); INPUT ANIMAL_OBJECT $var = ($type) SvIV((SV*) SvRV($arg));
这个typemap 文件是让xs 识别新定义的类。在(三)那里是在Makefile.PL 文件中指定了一个perlobject。map的文件,其实内容和这个差不多,我们没有必要定义那么多的别名。
修改Ma ke f ile.PL
1 #use 5.018002; 2 use ExtUtils::MakeMaker; 3 # See lib/ExtUtils/MakeMaker.pm for details of how to influence 4 # the contents of the Makefile that is written. 5 $CC = 'g++'; 6 WriteMakefile( 7 NAME => 'Cat', 8 VERSION_FROM => 'lib/Cat.pm', # finds $VERSION 9 PREREQ_PM => {}, # e.g., Module::Name => 1.1 10 ($] >= 5.005 ? ## Add these new keywords supported since 5.005 11 (ABSTRACT_FROM => 'lib/Cat.pm', # retrieve abstract from module 12 AUTHOR => 'chen <chen@>') : ()), 13 LIBS => ['-Lmylib -lanimal'], # e.g., '-lm' 14 DEFINE => '', # e.g., '-DHAVE_SOMETHING' 15 INC => '-Imylib', # e.g., '-I. -I/usr/include/other' 16 # Un-comment this if you add C files to link with later: 17 # OBJECT => '$(O_FILES)', # link all the C files too 18 'XSOPT' => '-C++', 19 'CC' => $CC, 20 'LD' => '$(CC)', 21 ); 22 23 sub MY::postamble { 24 ' 25 mylib/libanimal.so: mylib/Makefile 26 cd mylib && $(MAKE) $(PASSTHRU) 27 '; 28 }
红色的代码为添加或修改代码。注意:26 行代码前是table 键,而不是普通空格
函数 MY::postmable 是往Makefile 文件插入的一段代码,作用是让编译Cat的扩展库之前,首先编译依赖的libanimal.so包。
其余添加的代码主要是定义编译使用g++。再一次提醒,需要注释第一行代码#use 5.018002;
修改Cat.xs 文件
Cat.xs
#ifdef __cplusplus extern "C"{ #endif #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #ifdef __cplusplus } #endif #include "ppport.h" #include "mylib/Cat.h" MODULE = Cat PACKAGE = Cat Cat * Cat::new(char * name, int age) void Cat::display() char * Cat::getName() int Cat::getAge() void Cat::DESTROY()
格式和之前的(三)一样,这里就不再展开讲了。
生成Makefile 文件,编译
perl Makefile.PL && make
割一下############################################################
第二部分,创建Animal的工程
h2xs -A -n Animal
将Cat工程的mylib 软链接过来
ln -sf /home/chen/learn/perl_c/Cat/mylib /home/chen/learn/perl_c/Animal/mylib
创建并编辑typemap
1 TYPEMAP 2 Animal * ANIMAL_OBJECT 3 Cat * CAT_OBJECT 4 5 OUTPUT 6 ANIMAL_OBJECT 7 sv_setref_pv($arg, CLASS, (void *) $var); 8 CAT_OBJECT 9 sv_setref_pv($arg, "Cat", (void *) $var); 10 11 INPUT 12 ANIMAL_OBJECT 13 $var = ($type) SvIV((SV*) SvRV($arg)); 14 CAT_OBJECT 15 $var = ($type) SvIV((SV*) SvRV($arg));
实际上,这里就是本次博客的核心。
我们仔细观察Animal工程的 typemap 和Cat 工程的typemap 有什么不一样。Animal 工程的typemap 多了一个CAT_OBJECT 的定义,并且在 CAT_OBJECT 的 OUTPUT 中(第九行),是写明指向Cat的类。
如果我们仔细看一下前面的Cat 工程编译命令,有
g++ -c -Imylib -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fstack-protector -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g -DVERSION="0.01" -DXS_VERSION="0.01" -fPIC "-I/usr/lib/perl/5.18/CORE" Cat.c
Cat.c这个文件是在make 命令执行时产生的文件,我们打开Cat.c 文件查看的话,会发现一些有趣的东西
XS_EUPXS(XS_Cat_new) { dVAR; dXSARGS; if (items != 3) croak_xs_usage(cv, "CLASS, name, age"); { char * CLASS = (char *)SvPV_nolen(ST(0)) ; Cat * RETVAL; char * name = (char *)SvPV_nolen(ST(1)) ; int age = (int)SvIV(ST(2)) ; RETVAL = new Cat(name, age); ST(0) = sv_newmortal(); sv_setref_pv(ST(0), CLASS, (void *) RETVAL); } XSRETURN(1); }
上面的代码实际上就是Cat::new() 的真实代码。我们可以发现CLASS 的变量,其实就是一个字符串数组。我在实验过程中,将CLASS 字符串打印了一下,发现原来它记录的就是Cat的工程名“Cat"。
分析到这里,我们就不难反推,如果需要返回值是自定义的类,我们只需要将”CLASS“ 字段写成我们自己的工程名即可。
有兴趣的同学也可以深挖一下,为什么CLASS 会自动识别当前工程名,与(三)的perlobject.map文件中其他玩法。
后面的事情就很简单了,不过是修改Makefile.PL 与 Aniaml.xs 文件
Makefile.PL
1 #use 5.018002; 2 use ExtUtils::MakeMaker; 3 # See lib/ExtUtils/MakeMaker.pm for details of how to influence 4 # the contents of the Makefile that is written. 5 $CC = 'g++'; 6 WriteMakefile( 7 NAME => 'Animal', 8 VERSION_FROM => 'lib/Animal.pm', # finds $VERSION 9 PREREQ_PM => {}, # e.g., Module::Name => 1.1 10 ($] >= 5.005 ? ## Add these new keywords supported since 5.005 11 (ABSTRACT_FROM => 'lib/Animal.pm', # retrieve abstract from module 12 AUTHOR => 'chen <chen@>') : ()), 13 LIBS => ['-Lmylib -lanimal'], # e.g., '-lm' 14 DEFINE => '', # e.g., '-DHAVE_SOMETHING' 15 INC => '-Imylib', # e.g., '-I. -I/usr/include/other' 16 'CC' => $CC, 17 'LD' => '$(CC)', 18 # Un-comment this if you add C files to link with later: 19 # OBJECT => '$(O_FILES)', # link all the C files too 20 'XSOPT' => '-C++', 21 #'LDDLFLAGS' => '-r', 22 ); 23 24 sub MY::postamble { 25 ' 26 $(MYEXTLIB): mylib/Makefile 27 cd mylib && $(MAKE) $(PASSTHRU) 28 '; 29 }
红色部分为增改内容。
注意:27行代码前的为table 键,而不是普通空格
Animal.xs
1 #ifdef __cplusplus 2 extern "C"{ 3 #endif 4 5 #define PERL_NO_GET_CONTEXT 6 #include "EXTERN.h" 7 #include "perl.h" 8 #include "XSUB.h" 9 #ifdef __cplusplus 10 } 11 #endif 12 #include "ppport.h" 13 #include "mylib/Cat.h" 14 #include "mylib/Animal.h" 15 16 MODULE = Animal PACKAGE = Animal 17 18 Animal * 19 Animal::new(char * name, int age) 20 21 void 22 Animal::display() 23 24 Cat * 25 Animal::getAnimal() 26 27 Cat * 28 Animal::setAnimal(Cat * lcat) 29 30 bool 31 Animal::haveAnimal() 32 33 void 34 Animal::DESTROY()
生成Makefile并编译
perl Makefile.PL && make
编写测试代码,test.pl
1 #!/usr/bin/perl 2 use Animal; 3 use Cat; 4 $animal = new Animal("chen",123); 5 $animal->display(); 6 $cat = $animal->getAnimal(); 7 $cat->display(); 8 9 $name = $cat->getName(); 10 11 print $name; 12 13 $name = "sdjlfa"; 14 $animal->display(); 15 16 print "~~~~~~###############~~~~~~~~~~~~~~~ "; 17 18 $lcat = new Cat("ASKJKLF",889); 19 $lcat->display(); 20 print "~~~~~~###############~~~~~~~~~~~~~~~ "; 21 22 $tcat = $animal->setAnimal( $lcat ); 23 $animal->display(); 24 25 $test = $animal->haveAnimal(); 26 print "@@@@@@@@@$test@@@@@@ "; 27 28 29 $animal2 = $animal; 30 31 $animal2->display();
添加环境变量
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/chen/learn/perl_c/Animal/blib/arch/auto/Animal:/home/chen/learn/perl_c/Animal/mylib:/home/chen/learn/perl_c/Cat/blib/arch/auto/Cat
export PERLLIB=${PERLLIB}:/home/chen/learn/perl_c/Animal/lib:/home/chen/learn/perl_c/Cat/lib
Animal 的扩展包动态库在Animal 工程的 blib/arch/auto/Animal 目录下,pm 文件则在 Animal 工程的 lib 目录下
同样,Cat 的扩展包动态库在Cat 工程的blib/arch/auto/Animal 目录下,pm 文件则在 Cat 工程的 lib 目录下
同时需要将libanimal.so 文件添加到LD_LIBRARY_PATH 环境变量中。
运行一下测试程序
perl test.pl
输出:
~~~~~~~~~~~Animal.display()~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=chen age=123 ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=chen age=123 ~~~~~~~~~~~Animal.display()~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=chen age=123 chen~~~~~~###############~~~~~~~~~~~~~~~ ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=ASKJKLF age=889 ~~~~~~###############~~~~~~~~~~~~~~~ ~~~~~~~~~~~Animal.display()~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=ASKJKLF age=889 @@@@@@@@@@@@@@ ~~~~~~~~~~~Animal.display()~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~Cat.display()~~~~~~~~~~~~~~~~~~~~ name=ASKJKLF age=889
测试成功。