PNI 简介
PNI,是PHP Native Interface的简写。它是PHP的一个扩展,可以通过它,让PHP直接调用C语言写的函数。
想法源自在百度做项目时,不时地会面临同样一个问题,PHP该如何直接快速地与其他语言交互呢?
PHP 扩展的不足
用C写PHP的扩展是常规的方法。不过使用这种方法总是要面临诸多问题:
- 作为开发者,要去学习PHP-API、Zend-API,这是不小的学习成本,而且用C做开发测试的效率都不高。
- 写好了PHP扩展,怎么部署呢,还要找运维工程师去讨论、去争取后才能上线。
来来回回项目进度就被拖慢了。
有如此技术痛点,就找个通用的法子吧。
调研过程
JNI 和 Python
Java 可以通过JNI调用C/C++,Python也有相应的包,比如ctypes
HHVM
稍微去翻了一下HHVM的扩展,发现HHVM有 native interface, 但稍微看了一下,却发现那只是HHVM的native interface,PHP无法使用。
这个项目,模仿JNI,就取名PNI吧
c 对动态链接库的动态调用
c 加载动态链接库及调用函数有现成的方法。
使用dlfcn.h 库中的dlopen、dlsym、dlclose三个函数就够了。
设计和实现过程
接口设计
最开始的方案,就是模仿JNI。因此在最初的实现里,PNI对动态链接库的查询,调用函数的方法都直接模仿了JNI的接口实现。测试使用时,却感觉PNI的接口非常不友好。
和Java、Python 不同,PHP是弱类型语言。JNI 可以在给函数传递参数时,参数的数据类型是已知的,但是PHP传的都是zval,类型并不可知。我把数据类型的控制交给了开发者,在PNI的扩展里,通过struct zval 中 type 字段判断,此种方案让PNI很难于被使用。
于是改进方案,在PNI里添加PNIInteger、PNIDouble、PNIChar和其他几个可以标明参数数据类型的类。
参数压栈
PHP 的函数调用后,动过dlfcn.h库,可以找到函数地址,但是如何调用函数呢?调用函数又如何传参呢?我们无法知道所调用函数的参数列表是什么样子。
由于没有找到比较好的方案,所以就借助于汇编,使用汇编模式C语言的参数压栈方法。
于是写了很多种C语言的函数,主要是参数列表不一样,总结了GCC编译器在x86_64架构CPU下参数压栈的几个特点:
无论C函数的参数是什么样的类型,PNI都将其按64位的整形或浮点处理。
问题解决了
系统兼容性
因为参数压栈的问题,目前PNI只支持GCC编译器和x86_64架构的CPU。其他架构和编译器都没有来得及去实现。
总结
吭哧吭哧,PNI最初版本调试测试成功。
后记
如何让PNI被更多的人知道并使用,怎么做呢?(还在思考中)
- 开源,传到GitHub
- 融入社区
- 在问答网站上多回答相关问题
其他方法呢?再想一下。