由于一个搞嵌入式又会php又会C还主要用C++的朋友的需要,目前PHP的功能满足不了他的XX.于是一起探讨能不能自己给PHP加一些功能,找遍了整个网上都是如何使用C直接写个模块然后用php编译安装,这么做的确可以,但是很麻烦,而且容易出问题.于是抽时间爬了一遍Github终于找到了一个比较好的东西PNI.
什么是PNI(PHP Native Interface )?
它是PHP 的一个C扩展,通过它,可以让PHP调用其他语言写的程序,比如C/C++、汇编等等
需要PHP来调用,但PHP有限使用的领域里,PNI可以发挥用处,比如图像处理、统计学习、神经网络、实时性要求高的程序等等
为啥要用PNI?
PHP是最好的语言[引战符].但是PHP不是最完美的语言,总有一些情况下,不得不使用其他语言来协助完成。在这些特殊的场景下,使用PNI就可以将PHP与其他语言连接起来,例如:
实时性要求特别高的程序,特别底层的程序
用其他语言写的程序,历史遗留下来的程序,如果用PHP重新实现成本太高的程序或逻辑
基于平台特性的代码,不能用PHP实现的程序
调用系统的动态链接库
等等这些情况都需要使用PNI来帮助.
好了说了那么多,来看看怎么使用吧.
由于PNI的作者zuocheng-liu这位大佬可能是太忙,所以官方给出的教程让我一次入坑,爬了将近一天才爬出来.所以这里写的一些操作可能和官方不太一样.
------------------安装------------------
在安装PNI之前,请先安装好PHP,并确定PHP.ini文件的位置,和php-config文件的位置,不同的脚本包和安装方式,可能位置都不一样,本教程以lnmp一键安装包为例.服务器不限制apache/nginx都可以因为和服务没关系.
然后我们打开linux的命令行,或远程连接上,进入命令界面.
0:安装git,由于后面我们要用到git所以先安装好git
yum -y install git
1:下载
git clone https://github.com/zuocheng-liu/pni.git
2.进入pni目录(这里容易坑)
cd ./pni/
3.选择php分支,根据你的php版本来选择,不过这里注意,php最低5.3,否则不兼容.
git checkout for_php_5
或
git checkout for_php_7
4.调用一次phpize(注意不要换目录)
phpize
5.执行configure,这里要确定你的php-config文件在什么位置.
./configure --with-php-config=/usr/local/php/bin/php-config
6.安装
make&&make install
7.添加extension=pni.so; 到php.ini文件
vi /usr/local/php/etc/php.ini
在最后加上extension=pni.so; 然后保存
8.根据你的服务器类型重启php
service php-fpm restart // cgi模式
apachectl restart // sapi模式
9.现在PNI已经安装好了,接着来准备测试,准备好.c文件(libtest.c)
#include"stdio.h"
#include"math.h"
int add(int a, int b)
{
return a+b;
}
10.把c文件编译成so文件
命令:gcc -fPIC -shared -o 输出.so 你的.c
gcc -fPIC -shared -o libtest.so libtest.c
[大坑请注意:so文件必须以lib开头,否则无法关联]
11.创建拓展文件夹,我这里就放在php的安装目录下了,方便区别.
mkdir /usr/local/php/ex/
12.把第10步生成的.so文件拷贝到扩展文件夹,注意lib开头命名
cp ./libtest.so /usr/local/php/ex/libtest.so
13.修改系统的动态调用
vi /etc/ld.so.conf
在最后加上我们刚才创建的拓展文件夹/usr/local/php/ex/
14.重启动态调用
/sbin/ldconfig -v
这里注意一定要看到你的so文件被调用,如果不是lib开头,系统不会调用哦.
15.准备PHP测试文件(index.php)
echo '本程序共会执行2次调用,用以测试是否运行成功.第一次调用C原生库,第二次调用自定义库.';
echo '开始调用C原生pow方法.';
try {
$pow = new PNIFunction(PNIDataType::DOUBLE, 'pow', 'libm.so.6');
$a = new PNIDouble(2);
$b = new PNIDouble(2);
$res = $pow($a, $b);
var_dump($res);
} catch (PNIException $e) {
}
echo '调用完毕.';
echo '开始调用自定义add方法.';
try {
$add = new PNIFunction(PNIDataType::INTEGER,'add', 'libtest.so');
$a = new PNIInteger(15);
$b = new PNIInteger(235);
$res = $add($a, $b);
var_dump($res);
} catch (PNIException $e) {
}
echo '调用完毕.';
echo '注意,返回的都是对象格式,需要使用对象调用方式获取数据,例如:';
echo '你就是个'.$res->getValue('protected').'';
echo 'end';
16.访问这个php文件.
可以看到,我们的so文件被调用了,而且得到了正确的结果.
------------------扩展知识------------------
PNI数据类型:
PNIDataType
PNIInteger
PNILong
PNIDouble
PNIFLOAT
PNIChar
PNIString
PNIPointer
PNI数据类型常量:
PNIDataType::VOID
PNIDataType::CHAR
PNIDataType::INTEGER
PNIDataType::LONG
PNIDataType::FLOAT
PNIDataType::DOUBLE
PNIDataType::POINTER
由于PHP只有64整形,所以PNILong 和 PNIInteger 实际上是等效的。
如果通过PNI调用的函数参数类型是32位、16位数据怎么办?需要开发人员保证PNILong和PNIInteger存放的值不能超出大小。
PNIDouble 和 PNIFloat 也是等效的,因为PHP只有64位浮点。如果调用的C函数参数列表里有32位浮点呢? 不用担心,即使是32位的浮点,在x86_64架构的CPU里,也是赋给了64位的浮点运算器。
注意事项
如果PHP是多线程运行,需要注意PNI调用的动态链接库是否是线程安全的
对于在动态链接库中申请的资源,要及时释放
目前PNI还不支持对复杂数据类型的操作,比如struct,C++的类等
传送门PNI作者的Github:
https://github.com/zuocheng-liu/pni/blob/master/README-zh.md