• Rust开发PHP扩展Liunx版【必须收藏】


    众所周知,作为一名phper,对php有限的功能感到尴尬,比如:调用ffmpeg视频处理工具。没有专门的扩展来操作的,什么?利用php system函数调用?对于开源的php脚本,这太不安全了!

    这个时候作为深资的phper会考虑开发php扩展,在扩展中实现对ffmpeg的操作。

    目前c站上对于rust如何开发php扩展的文章几乎没有,就连开发php扩展流程的文章也都很对付。

    特此撰写此文!!!

    关注我,我在坑里等你们!

    为什么要使用php扩展?

    优点:

    1、php扩展是C开发的,那速度没得说。

    2、耦合性高,它的出现就是用来增强php的。

    3、安全性高,毕竟扩展是编译后的程序,代码不开源。

    缺点:

    1、需针对php版本及系统环境进行开发,那么就比较麻烦了。也就是说7.4版本的php,liunx环境下开发的扩展,只支持该php版本及系统。

    2、需要会C、C++,当然本文是以rust进行开发,对C的数据类型进行了解,对rust FFI的操作及数据类型转换需精通。

    3、调试相对麻烦。

    为什么要用rust开发php扩展?

    原因很简单,这还要说起rust的语言特性。

    1、因“所有权”的特性使你的程序更安全,不会像C那样出现各种“玄学BUG”。

    2、拥有C一样的性能。

    3、毕竟是最受欢迎的语言,我很看好它的发展。

    rust开发php扩展流程:

    当然,rust目前是没有专门开发php扩展的骨架。所以我的逻辑也很简单,利用rust开发静态库暴露给C【涉及FFI的了解】。我们在php官方骨架中直接引入rust静态库调用其方法即可。

    开发环境

    宝塔【CentOS 7.6】、GCC【涉及php扩展骨架的编译,我这里系统内置就有,如果编译扩展报相关错了自行安装】、对应php版本源码、web环境【宝塔中安装对应php版本、nginx、mysql等等】

    开发整体流程:

    1、准备宝塔

    宝塔安装流程:CentOS宝塔搭建(超详细)_一码超人的博客-CSDN博客_centos 宝塔

     

     这里我们以开发php7.4扩展为例。

    2、下载php7.4 liunx版源码

    php官网:PHP: Hypertext Preprocessor

     

     注意!该源码版本必须与你环境php版本完全一致!!!

     

     下载完毕:

     3、上传php源码到宝塔

    /usr/phper

    在usr下创建一个phper文件夹,然后将源码压缩包上传到此处。

     解压该压缩包

     4、创建一个我们自己的扩展

     /usr/phper/php-7.4.30/ext目录下有这么一个php文件,它可以创建扩展!

     注意设置命令行版本,因为接下来利用php命令必须是版本一致的!

     在刚刚的目录下,点击终端,输入创建扩展命令。

    php ext_skel.php --ext 扩展名称

     

     这里就多出了一个新的扩展源码文件。

     

     在该目录下点击终端,输入:

    phpize

     接着输入:

    ./configure --with-php-config=/www/server/php/74/bin/php-config

    注意这个参数php路径,如果是别的版本,请自行在宝塔里安装找到对应版本路径,它们都是放一起的。

     回车开始进行检查了

     最后输入:

    make

    进行编译。

     

     这个目录下便是编译出来的so扩展最终文件了!

     让我们看下默认生成的扩展有哪些功能

     查看主文件【需了解php扩展骨架,这里以它默认给的为例】

     也就是说,刚刚编译出来的扩展,是有这两个函数的,咱们测试一下玩玩。

    注意!每次修改主文件,都需要重新按上述命令跑一遍,否则不生效,很奇怪!

    1. phpize
    2. ./configure --with-php-config=/www/server/php/74/bin/php-config
    3. make

    5、使用扩展

     复制刚刚生成的扩展文件到我们php环境的扩展里

     

     配置php.ini加载hello.so扩展

     

     

    extension = hello.so

    保存后记得重新启动下php,否则不生效的!

     

     

    在文件管理中点击终端,输入:

    php -m

    可以看到我们的扩展在列表中了。

    创建一个站点,测试下扩展中两个函数。

     看好,php版本是7.4

     

     访问站点

     没有问题哦!

    当然也可以通过命令行运行php脚本查看结果【前提是网站那里php命令行版本设置的7.4】

    php index.php

     OK!从创建到生成到使用扩展的流程结束,接下来才进入正题,开始用rust开发扩展。

    6、rust与php扩展的整合开发

    开发工具:CLion

    需要rust环境与CLion中rust插件的安装与配置,这个自行去百度,比我想象中的全!

     创建一个hello命名的库项目

     我们写两个导出函数,分别是加法功能和base64字符串解析功能。

     lib.rs

    1. #![crate_type = "staticlib"]
    2. extern crate libc;
    3. //使用C类型约束
    4. use std::ffi::{CStr, CString};
    5. use libc::{c_char, c_int};
    6. //add_int【参数:两个c语言的int类型】:对两个int类型数值进行相加
    7. #[no_mangle]
    8. pub extern "C" fn add_int(x:c_int, y:c_int) -> c_int{
    9. //两个数相加
    10. return x + y;
    11. }
    12. //base64_decode函数【参数:c语言的*char类型】:对字符串进行base64解码
    13. #[no_mangle]
    14. pub extern "C" fn base64_decode(s:*const c_char) -> *mut c_char {
    15. //c char类型转&str
    16. let h = unsafe{CStr::from_ptr(s).to_str().unwrap()};
    17. //base64 解码
    18. let s = base64::decode(h.to_string());
    19. if let Err(_s) = s {
    20. panic!("类型错误!");
    21. }
    22. let n = String::from_utf8(s.unwrap().clone()).unwrap();
    23. //String 转 C CString
    24. let a = CString::new(n.as_str()).unwrap();
    25. //C CString 转 C char
    26. //这里实属无奈,因为rust ffi中阐述,对字符串返回只能是该字符串地址,所以需要该方法进行返回C才能接收到!
    27. let r = a.into_raw();
    28. return r;
    29. }

     Cargo.toml

    1. [package]
    2. name = "hello"
    3. version = "0.1.0"
    4. edition = "2021"
    5. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    6. [lib]
    7. name = "hello"
    8. crate-type = ["staticlib"]
    9. [dependencies]
    10. libc = "*"
    11. base64 = "0.12.1"

    注意在编译过程中涉及系统类型,不然在引入该静态库编译扩展可能报错,提示不支持。

    编译64位静态库

    1. rustup target add x86_64-unknown-linux-musl
    2. cargo build --target x86_64-unknown-linux-musl --release

    编译32位静态库

    1. rustup target add i686-unknown-linux-musl
    2. cargo build --target i686-unknown-linux-musl --release

    这里我们是64位系统。

     会生成一个.a文件,该文件便是liunx支持的静态库文件。

    生成支持C语言的胶水头文件【用于C调用该库需要写的函数声明,很方便】

    创建cbindgen.toml文件

    内容:

    language = "C"

     

     安装cbindgen,创建头文件。

    1. cargo install --force cbindgen
    2. cbindgen --config cbindgen.toml --crate 项目名称 --output 头文件名称.h

     

     自动生成了C语言的函数声明hello.h文件,用于调用。

    回到之前我们创建的hello扩展

    创建lib文件夹

     将刚刚编译出来的静态库.a文件上传到lib目录下

     将刚刚创建的.h头文件上传到扩展目录下

     配置.m4预编译文件【关键】

     设置引入lib文件夹中的静态库文件

     

    1. PHP_ADD_LIBRARY_WITH_PATH(hello, /usr/phper/php-7.4.30/ext/hello/lib, HELLO_SHARED_LIBADD)
    2. PHP_SUBST(HELLO_SHARED_LIBADD)

     保存.m4

     编写主文件

     

    1. /* hello extension for PHP */
    2. #ifdef HAVE_CONFIG_H
    3. # include "config.h"
    4. #endif
    5. #include "php.h"
    6. #include "ext/standard/info.h"
    7. #include "php_hello.h"
    8. #include "hello.h"//引入头文件
    9. /* For compatibility with older PHP versions */
    10. #ifndef ZEND_PARSE_PARAMETERS_NONE
    11. #define ZEND_PARSE_PARAMETERS_NONE() \
    12. ZEND_PARSE_PARAMETERS_START(0, 0) \
    13. ZEND_PARSE_PARAMETERS_END()
    14. #endif
    15. /* {{{ void hello_test1()
    16. */
    17. PHP_FUNCTION(hello_test1)
    18. {
    19. ZEND_PARSE_PARAMETERS_NONE();
    20. int num = add_int(1,2);//rust中两个数相加函数并返回。
    21. php_printf("The extension %d is loaded and working!\r\n", num);
    22. }
    23. /* }}} */
    24. /* {{{ string hello_test2( [ string $var ] )
    25. */
    26. PHP_FUNCTION(hello_test2)
    27. {
    28. char *var = "World";
    29. size_t var_len = sizeof("World") - 1;
    30. zend_string *retval;
    31. ZEND_PARSE_PARAMETERS_START(0, 1)
    32. Z_PARAM_OPTIONAL
    33. Z_PARAM_STRING(var, var_len)
    34. ZEND_PARSE_PARAMETERS_END();
    35. char *newstr = base64_decode(var);//rust中解析base64字符串并返回。
    36. retval = strpprintf(0, "Hello %s", newstr);
    37. RETURN_STR(retval);
    38. }
    39. /* }}}*/
    40. /* {{{ PHP_RINIT_FUNCTION
    41. */
    42. PHP_RINIT_FUNCTION(hello)
    43. {
    44. #if defined(ZTS) && defined(COMPILE_DL_HELLO)
    45. ZEND_TSRMLS_CACHE_UPDATE();
    46. #endif
    47. return SUCCESS;
    48. }
    49. /* }}} */
    50. /* {{{ PHP_MINFO_FUNCTION
    51. */
    52. PHP_MINFO_FUNCTION(hello)
    53. {
    54. php_info_print_table_start();
    55. php_info_print_table_header(2, "hello support", "enabled");
    56. php_info_print_table_end();
    57. }
    58. /* }}} */
    59. /* {{{ arginfo
    60. */
    61. ZEND_BEGIN_ARG_INFO(arginfo_hello_test1, 0)
    62. ZEND_END_ARG_INFO()
    63. ZEND_BEGIN_ARG_INFO(arginfo_hello_test2, 0)
    64. ZEND_ARG_INFO(0, str)
    65. ZEND_END_ARG_INFO()
    66. /* }}} */
    67. /* {{{ hello_functions[]
    68. */
    69. static const zend_function_entry hello_functions[] = {
    70. PHP_FE(hello_test1, arginfo_hello_test1)
    71. PHP_FE(hello_test2, arginfo_hello_test2)
    72. PHP_FE_END
    73. };
    74. /* }}} */
    75. /* {{{ hello_module_entry
    76. */
    77. zend_module_entry hello_module_entry = {
    78. STANDARD_MODULE_HEADER,
    79. "hello", /* Extension name */
    80. hello_functions, /* zend_function_entry */
    81. NULL, /* PHP_MINIT - Module initialization */
    82. NULL, /* PHP_MSHUTDOWN - Module shutdown */
    83. PHP_RINIT(hello), /* PHP_RINIT - Request initialization */
    84. NULL, /* PHP_RSHUTDOWN - Request shutdown */
    85. PHP_MINFO(hello), /* PHP_MINFO - Module info */
    86. PHP_HELLO_VERSION, /* Version */
    87. STANDARD_MODULE_PROPERTIES
    88. };
    89. /* }}} */
    90. #ifdef COMPILE_DL_HELLO
    91. # ifdef ZTS
    92. ZEND_TSRMLS_CACHE_DEFINE()
    93. # endif
    94. ZEND_GET_MODULE(hello)
    95. #endif

    删除之前生成的扩展文件

     重新生成扩展

     

    1. phpize
    2. ./configure --with-php-config=/www/server/php/74/bin/php-config
    3. make

     大小都变了,说明我们的静态库在里面了哈哈。

    按上述使用扩展流程替换扩展

    注意!替换扩展文件后要重启PHP哦,不然不生效!

    7、测试rust开发的php扩展

     

     

     网页测试

     命令行测试

     也可以通过php扩展骨架直接进行测试

     

     

     编写要执行测试的扩展函数

    1. --TEST--
    2. hello_test2() Basic test
    3. --SKIPIF--
    4. php
    5. if (!extension_loaded('hello')) {
    6. echo 'skip';
    7. }
    8. ?>
    9. --FILE--
    10. php
    11. hello_test1();
    12. var_dump(hello_test2('5LiA56CB6LaF5Lq6'));
    13. ?>
    14. --EXPECT--
    15. string(11) "Hello World"
    16. string(9) "Hello PHP"

     扩展目录下直接输入:

    make test

     

    执行后 tests目录下输出了一个.out文件

     

     是不是这样更方便了呢?

     以上就是整体的开发流程,需要经通过的话还是多少要了解C语言、php扩展骨架、rust精通。

     快卷起来吧!

  • 相关阅读:
    c++小知识
    Node.js学习
    数学建模学习(100):交通运输问题建模
    恢复二叉搜索树[根据题意模拟->发现问题->分析问题->见招拆招]
    【C语言刷LeetCode】1282. 用户分组(M)
    Pegasus智能家居套件样例开发--软定时器
    JVM 类加载机制 超详细学习笔记(三)
    SpringCloud 核心组件Nacos【配置管理&配置拉取】第4章
    【java核心技术】Java知识总结 -- 语法篇
    《低代码指南》——如何通过维格表实现生产采购管理
  • 原文地址:https://blog.csdn.net/weixin_47723549/article/details/126578667