最近,需要完成一个功能,需要在PERL中调用C++动态库。
这一个星期就为了完成这项工作,还是付出不少努力。
首先,找到一篇文章:
《Perl 对 C 的扩展接口》
http://www.ibm.com/developerworks/cn/aix/library/0908_tangming_perltoc/index.html
写得很详细。
看完后,开工。
清单 17 . 头文件 test.h
#define TESTVAL 3
extern double add(int, long);
extern int max(int, int);
清单 18 . 源文件 test1.c
#include "./test.h"
double add(int a, long b)
{
return (a + b + TESTVAL);
}
清单 19 . 源文件 test2.c
#include "./test.h"
int max(int a, int b)
{
return ((a>b)? a:b);
}
这些照着做,
2. 在 Mytest/mylib 目录下创建 Makefile.PL 文件,以保证在 Mytest 目录运行 ”make” 时会自动调用该 Makefile.PL 并生成相应的 Makefile。
这步手工用VC6做一个静态库,当然动态库也成
3. 在 Mytest 的上级目录中执行命令 “h2xs -A -O -n Mytest ./Mytest/mylib/test.h” 以生成扩展接口系列文件。
注意:Perl 会提示覆盖 Mytest 目录,并在 Mytest 中生成上节介绍的系列文件,这也是要将源文件放在 /Mytest/mylib/ 下的原因,以免被自动生成的文件覆盖。
照做
4. Perl 在 Mytest 下自动生成的 Makefile.PL 并不知道子目录 mylib 的存在,因此需要修改该 Makefile.PL。
清单 21 . 修改 Mytest 目录下的 Makefile.PL
WriteMakefile(
'NAME' => 'Mytest',
'VERSION_FROM' => 'Mytest.pm', # finds $VERSION
'LIBS' => [''], # e.g., '-lm'
'DEFINE' => '', # e.g., '-DHAVE_SOMETHING'
'INC' => '', # e.g., '-I/usr/include/other'
'MYEXTLIB' => 'mylib/libmylib$(LIB_EXT)',
);
指定了 MYEXTLIB 为 mylib 子目录下的 libmylib.a。
清单 22 . 在 Makefile.PL 文件的最后添加 MY::postamble 函数
sub MY::postamble {
'
$(MYEXTLIB): mylib/Makefile
cd mylib && $(MAKE) $(PASSTHRU)
';
}
在 MY::postamble 中进入 mylib 子目录并运行其下的 Makefile 进行编译,以生成静态库 libmylib.a。
注意: ‘ cd ’ 前面应该是 ‘ Tab ’ 而不是空格,否则 Make 会报 “missing separator” 错误并终止编译。
5. 修改 MANIFEST 文件使其能够正确包含该扩展接口的所有内容。清单 23 . 修改 MANIFEST 文件
mylib/Makefile.PL
mylib/test1.c
mylib/test2.c
mylib/test.h
6. 修改 Mytest.xs 文件并添加函数定义。
清单 24 . 修改 #include test.h
#include "mylib/test.h"
修改路径为 mylib/test.h 并将尖括号 <> 改为双引号””,以使编译程序能正确找到 mylib 子目录下面的 test.h 头文件。
清单 25 . 添加 add() 和 max () 函数定义
double
add(a,b)
int a
long b
int
max(a,b)
int a
int b
提供 Perl 与 C 之间的接口函数,使得 Perl 代码通过 Mytest.xs 中的接口函数可以直接调用相应的 C 函数 add(a,b) 和 max(a,b) 函数。
7. 在 Mytest 目录下运行 ”perl Makefile.PL” 生成 Makefile。清单 26 . 运行 perl Makefile.PL
% perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for Mytest::mylib
Writing Makefile for Mytest
%
这里会出错,不要理它,原因是IBM这篇文章是对linux而言的,所以第2步就已经不对了,cr 指令不支持。
编译器根据 Makefile.PL 自动生成相应的 Makefile 和 mylib 子目录下的 Makefile。
8. 运行 ”make” 生成需要的库文件。
清单 27 . 运行 make
% make
gcc -c <compile_flag> test1.c
gcc -c <compile_flag> test2.c
ar cr libmylib.a test1.o test2.o
: libmylib.a
perl xsubpp -typemap typemap Mytest.xs > Mytest.xsc && mv Mytest.xsc Mytest.c
Please specify prototyping behavior for Mytest.xs (see perlxs manual)
gcc -c <compile_flag> Mytest.c
rm -f blib/arch/auto/Mytest/Mytest.so
gcc <compile_flag> Mytest.o -o blib/arch/auto/Mytest/Mytest.so mylib/libmylib.a
chmod 755 blib/arch/auto/Mytest/Mytest.so
cp Mytest.bs blib/arch/auto/Mytest/Mytest.bs
chmod 644 blib/arch/auto/Mytest/Mytest.bs
Manifying blib/man3/Mytest.3pm
%
xsubpp -typemap typemap Mytest.xs > Mytest.xsc && mv Mytest.xsc Mytest.c
这个是关键,我们要得到Mytest.c,注意前面不要写perl,否则无法执行。
然后,建一个DLL,引入这个mytest.c,然后写个def文件:见我写的前一篇文章。
LIBRARY "Mytest"
EXPORTS
boot_Mytest
_boot_Mytest = boot_Mytest
XSUB.h 改成:
#undef XS
#undef XS_EXTERNAL
#undef XS_INTERNAL
#if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
# define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
# define XS_INTERNAL(name) STATIC XSPROTO(name)
#endif
#if defined(__SYMBIAN32__)
# define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
# define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
#endif
#ifndef XS_EXTERNAL
# if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
# define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
# define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
# else
# ifdef __cplusplus //haoyujie modify 2014-03-13
# define XS_EXTERNAL(name) extern "C" __declspec(dllexport) XSPROTO(name)
# define XS_INTERNAL(name) static XSPROTO(name)
# else
# define XS_EXTERNAL(name) __declspec(dllexport)XSPROTO(name)
# define XS_INTERNAL(name) STATIC XSPROTO(name)
# endif
# endif
#endif
9. 修改 Mytest.t 文件,添加测试代码
清单 28 . 修改 Mytest.t
is( &Mytest::add(1, 2), 6 );
is( &Mytest::add(3, 1), 7 );
is( &Mytest::max(1, 2), 2 );
is( &Mytest::max(3, 1), 3 );
下面的都不需要看了,因为编译不过。
测试代码以不同的参数调用 Mytest::add() 和 Mytest::max() 并同预期结果比较,以确认相应的 C 函数 add() 和 max() 被正确调用。
10. 运行命令 ”make test”,确保所有测试结果正确。清单 29 . 运行 make test
% make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness
(0, 'blib/lib', 'blib/arch')" *.t
Mytest....ok
All tests successful.
Files=1, Tests=4, 0 wallclock secs ( 0.03 cusr + 0.01 csys = 0.04 CPU)
%
从结果可以看到测试全部通过,说明相应 C 函数被调用并计算得到了正确的结果。
1. 运行命令 ”make install”,将生成的库文件安装到系统目录中。
2. 至此,我们就能在自己的 Perl 代码中直接调用 test1.c 和 test2.c 里面定义的函数 add() 和 max() 了。
清单 30 .Perl 代码调用
use Mytest;
my $sum = &Mytest::add(1, 2);
my $max = &Mytest::max(2, 3);
print “Sum of 1 add 2 is: $sum\n”;
print “Max of 2 and 3 is: $max\n”;
============================
用VC6编译过之后,我们要手工进行安装,以使perl能加载。
编译结果拷到这里:
C:\perl\site\lib\auto\Mytest
建一个文本文件.packlist,最好从你装好的别的目录找一个:
内容如下,
C:\Perl\html\site\lib\Mytest.html
C:\Perl\site\lib\Mytest.pm
C:\Perl\site\lib\auto\Mytest\Mytest.bs
C:\Perl\site\lib\auto\Mytest\Mytest.dll
C:\Perl\site\lib\auto\Mytest\Mytest.exp
C:\Perl\site\lib\auto\Mytest\Mytest.lib
C:\Perl\site\lib\auto\Mytest\Mytest.pdb
OK。
用eclipse打开上面写好的测试文件,调试一下吧:
OYeah.
总结:
IBM的文章把原理讲得很清晰,所以一定要打印出来,反反复复地看。
主方向:
.h ===> .XS === >mytest.c==========>dll==========>手工安装
h2xs xsubpp 用VC编译&link 制作.packlist文件。
其它:作mytest.dll不要忘了,把那两个C文件所在的静态库libmylib.lib,和 perl516.lib,引入。