PHP FFI: Truyền Tham Số – Phần 2

Gọi trực tiếp thư viện PHP?

Nếu chúng ta viết một đoạn code vô dụng thì sao? Hãy thử nào!!! Hãy thử nào!! Hãy thử nào!!!

Được rồi, OK. Tôi đề xuất chúng ta làm điều gì đó vô dụng bằng cách sử dụng PHP để gọi một thư viện C sử dụng Zend Engine.

Thực ra, đoạn văn này không hề vô dụng như bạn nghĩ. Mục đích của nó là để cho bạn thấy một hành vi đặc biệt 😊

PHP-FFI có một số hạn chế: bạn không thể truyền trực tiếp một biến PHP để lấy giá trị động của nó từ thư viện bên ngoài.

Bạn chỉ có thể truyền các kiểu dữ liệu ngôn ngữ C. Cách chính xác để truyền tham số và lấy giá trị là qua:

$var = FFI::new('int'); 
$var->cdata=33; 

Tương tự cho việc lấy dữ liệu.
Bạn có thể tìm thêm thông tin trên trang web chính thức của PHP: https://www.php.net/manual/en/class.ffi-cdata.php.
Chúng tôi sẽ quay lại vấn đề này sớm.

Tuy nhiên, trong ví dụ này, chúng tôi sẽ sử dụng Zend để lấy giá trị của một biến trực tiếp qua tên của nó. Điều này chỉ có thể thực hiện nếu biến đó nằm trong ngữ cảnh của nó. Hãy biết rằng, việc này hoàn toàn vô dụng. Nhưng nó cho phép chúng tôi giới thiệu cách sử dụng Zend Engine nếu chúng tôi cần nó.

Chúng tôi sẽ bắt đầu bằng cách cài đặt g++ và các file header cho PHP 8.1

sudo apt-get install g++ php8.1-dev

Vì vậy, đây là mã C của chúng tôi sẽ cho phép chúng tôi truy cập các giá trị khác nhau của mình:

extern "C" {
    #include "php.h"
}

// FROM : https://stackoverflow.com/questions/70771433/is-it-possible-to-pass-php-variable-to-ffi-c-function
// https://github.com/mrsuh/php-var-sizeof/blob/master/library/ffi.cpp
zval * get_zval_by_name(char * name) {

    HashTable *symbol_table = zend_array_dup(zend_rebuild_symbol_table());

    zend_string *key_name = zend_string_init(name, strlen(name), 0);
    zval *data = zend_hash_find(symbol_table, key_name);

    zend_string_release(key_name);
    zend_array_destroy(symbol_table);

    return data;
}

extern "C" void test_var(char * name) {
    zval *zv_ptr = get_zval_by_name(name);

    if(zv_ptr != NULL) {
        // https://www.phpinternalsbook.com/php7/zvals/basic_structure.html#access-macros
        try_again:
            switch (Z_TYPE_P(zv_ptr)) {
                case IS_NULL:
                    php_printf("NULL: null\n");
                    break;
                case IS_LONG:
                    php_printf("LONG: %ld\n", Z_LVAL_P(zv_ptr));
                    break;
                case IS_TRUE:
                    php_printf("BOOL: true\n");
                    break;
                case IS_FALSE:
                     php_printf("BOOL: false\n");
                    break;
                case IS_DOUBLE:
                    php_printf("DOUBLE: %g\n", Z_DVAL_P(zv_ptr));
                    break;
                case IS_STRING:
                   php_printf("STRING: value=\"");
                   PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr));
                   php_printf("\", length=%zd\n", Z_STRLEN_P(zv_ptr));
                   break;
                case IS_RESOURCE:
                    php_printf("RESOURCE: id=%ld\n", Z_RES_HANDLE_P(zv_ptr));
                    break;
                case IS_ARRAY:
                    php_printf("ARRAY: hashtable=%p\n", Z_ARRVAL_P(zv_ptr));
                    break;
                case IS_OBJECT:
                    php_printf("OBJECT: object=%p\n", Z_OBJ_P(zv_ptr));
                    break;
                case IS_REFERENCE:
                            php_printf("REFERENCE: ");
                            zv_ptr = Z_REFVAL_P(zv_ptr);
                            goto try_again;
                EMPTY_SWITCH_DEFAULT_CASE()
            }
        }
}

Để giải thích, nếu bạn cần truy cập các giá trị trong ngữ cảnh, bạn có thể truy cập chúng như thế này.

Không ai nên cần điều này. Tôi chọn trình bày khả năng của mô-đun này thông qua một ví dụ trên lề.

File header: export-vars-php.h

#define FFI_LIB "./php-export-vars.so"

typedef struct zval zval;
void test_var(char *name);

Mã PHP: export-vars.php

#!/usr/bin/php8.1
<?php
opcache_reset();

$ffi = FFI::load(__DIR__ . '/export-vars-php.h');

$testString='jjj';
$ffi->test_var('testString');

$testInt = 10;
$ffi->test_var('testInt');

$testBool=true;
$ffi->test_var('testBool');


$testBool=false;
$ffi->test_var('testBool');

$testNull=null;
$ffi->test_var('testNull');

$testArray=[1,2,'test'];
$ffi->test_var('testArray');

Vì vậy, bằng cách chạy mã của chúng tôi, chúng ta nên nhận được điều này:

g++ -I/usr/include/php/20210902 \
    -I/usr/include/php/20210902/main \
    -I/usr/include/php/20210902/TSRM \
    -I/usr/include/php/20210902/Zend \
    -c php-export-vars.cpp && \
    gcc -shared -o php-export-vars.so  php-export-vars.o
./export-vars.php
STRING: value="jjj", length=3
LONG: 10
BOOL: true
BOOL: false
NULL: null
ARRAY: hashtable=0x7f6cd1257428

Bạn sẽ nhận thấy rằng chúng tôi đã đi qua hàm FFI::load và chúng tôi đã trực tiếp xuất thông tin định nghĩa vào một file header. Điều này làm rõ mã một chút.

Vì vậy, bạn có thể, như một bước trung gian, bắt đầu tạo cổng PHP của bạn mà không cần phải đi qua quy trình tạo mô-đun PHP thông thường.

FFI\CData

Bạn có thể đối mặt với các hàm nhận các loại tham số khác nhau. Đáng chú ý là các con trỏ đến cấu trúc, chức năng, v.v.

Trong các thử nghiệm của tôi, đôi khi tôi gặp vấn đề trong việc triển khai chúng. Vì vậy, đây là cách tạo những dữ liệu này trong một mớ hỗn độn:

#!/usr/bin/php8.1
<?php
$test = FFI::new('int');
$test->cdata = 32;
var_dump($test);
echo PHP_EOL;

$value = FFI::new('char[2]');
FFI::memcpy($value, 'ab', 2);
var_dump($value);
var_dump(FFI::cast('char[2]', $value));
echo PHP_EOL;

$test = FFI::new(FFI::arrayType(FFI::type('int'), [2]));
$test[0] = 6541;
$test[1] = 8731;
var_dump($test);
echo PHP_EOL;

Không cần thiết phải đi sâu vào chi tiết, các ví dụ khá rõ ràng.

Đây là phản hồi thực thi mã:

./cdata.php 
object(FFI\CData:int32_t)#1 (1) {
  ["cdata"]=>
  int(32)
}

object(FFI\CData:char[2])#2 (2) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
}
object(FFI\CData:char[2])#3 (2) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
}

object(FFI\CData:int32_t[2])#3 (2) {
  [0]=>
  int(6541)
  [1]=>
  int(8731)
}

Bạn có thể cần truyền địa chỉ của các cấu trúc cho các hàm. Tài liệu chính thức rất tốt. (https://www.php.net/manual/en/ffi.examples-basic.php)

Tôi đề xuất ví dụ sau:

<?php
$ffi = FFI::cdef("
    typedef unsigned int time_t;
    typedef unsigned int suseconds_t;
 
    struct timeval {
        time_t      tv_sec;
        suseconds_t tv_usec;
    };
 
    struct timezone {
        int tz_minuteswest;
        int tz_dsttime;
    };
 
    int gettimeofday(struct timeval *tv, struct timezone *tz);    
", "libc.so.6");

$tv = $ffi->new("struct timeval");
$tz = $ffi->new("struct timezone");


var_dump($ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz)));


var_dump($tv->tv_sec);

var_dump($tz);
?>

Bạn thấy rằng **int gettimeofday(struct timeval tv, struct timezone tz); nhận 2 cấu trúc làm tham số. Vì vậy, bạn sẽ phải tạo chúng thông qua FFI::new và lấy địa chỉ của chúng thông qua FFI::addr.
Mọi thứ đều được làm tốt!

$ffi->new("struct timeval");
$ffi->new("struct timezone");

Vì chúng tôi đã truyền địa chỉ của chúng, chúng tôi chỉ cần lấy nội dung của cấu trúc của mình với thông tin được điền vào. Thật kỳ diệu!

Nói về phép màu, trong phần tiếp theo của chúng tôi, chúng tôi sẽ đề cập đến các hàm gọi lại nổi tiếng. Phép màu khó từ bỏ một khi bạn đã nếm thử!

PHP FFI: CallBack - phần 3

Cảm ơn Thomas Bourdin, Cédric Le Jallé, Stéphane Péchard vì sự giúp đỡ, lời khuyên và hiệu đính của họ.