PHP FFI: Sử dụng Thư viện Kotlin Đa nền tảng – Phần 4

Với Stéphane Péchard https://www.linkedin.com/in/stephanepechard/, chuyên gia Android và KMP guru, chúng tôi tự hỏi liệu việc phát triển một thuật toán dưới KMP và thực thi nó trực tiếp trong PHP có khả thi hay không. 
Đặc tả khá đơn giản: người phát triển Android (Stéphane) cung cấp cho tôi một tệp tiêu đề, một tệp .so và tôi xử lý nó. 
Với tệp tiêu đề, hợp đồng, chúng tôi có thể nghĩ ra điều gì đó... Vì vậy, chúng tôi đã quản lý để thực hiện cuộc gọi hàm của mình. Nhưng thành thật mà nói, tôi vẫn gặp khá nhiều khó khăn trước khi đạt được điều đó.

Đây là tệp tiêu đề được cung cấp:

#ifndef KONAN_LIBALGOBSCURE_H
#define KONAN_LIBALGOBSCURE_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
typedef bool            libalgobscure_KBoolean;
#else
typedef _Bool           libalgobscure_KBoolean;
#endif
typedef unsigned short     libalgobscure_KChar;
typedef signed char        libalgobscure_KByte;
typedef short              libalgobscure_KShort;
typedef int                libalgobscure_KInt;
typedef long long          libalgobscure_KLong;
typedef unsigned char      libalgobscure_KUByte;
typedef unsigned short     libalgobscure_KUShort;
typedef unsigned int       libalgobscure_KUInt;
typedef unsigned long long libalgobscure_KULong;
typedef float              libalgobscure_KFloat;
typedef double             libalgobscure_KDouble;
typedef float __attribute__ ((__vector_size__ (16))) libalgobscure_KVector128;
typedef void*              libalgobscure_KNativePtr;
struct libalgobscure_KType;
typedef struct libalgobscure_KType libalgobscure_KType;

typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Byte;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Short;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Int;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Long;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Float;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Double;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Char;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Boolean;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Unit;


typedef struct {
  /* Service functions. */
  void (*DisposeStablePointer)(libalgobscure_KNativePtr ptr);
  void (*DisposeString)(const char* string);
  libalgobscure_KBoolean (*IsInstance)(libalgobscure_KNativePtr ref, const libalgobscure_KType* type);
  libalgobscure_kref_kotlin_Byte (*createNullableByte)(libalgobscure_KByte);
  libalgobscure_kref_kotlin_Short (*createNullableShort)(libalgobscure_KShort);
  libalgobscure_kref_kotlin_Int (*createNullableInt)(libalgobscure_KInt);
  libalgobscure_kref_kotlin_Long (*createNullableLong)(libalgobscure_KLong);
  libalgobscure_kref_kotlin_Float (*createNullableFloat)(libalgobscure_KFloat);
  libalgobscure_kref_kotlin_Double (*createNullableDouble)(libalgobscure_KDouble);
  libalgobscure_kref_kotlin_Char (*createNullableChar)(libalgobscure_KChar);
  libalgobscure_kref_kotlin_Boolean (*createNullableBoolean)(libalgobscure_KBoolean);
  libalgobscure_kref_kotlin_Unit (*createNullableUnit)(void);

  /* User functions. */
  struct {
    struct {
      libalgobscure_KLong (*factoriel)(libalgobscure_KLong n);
      libalgobscure_KLong (*fibonacci)(libalgobscure_KLong n);
      void (*main)();
    } root;
  } kotlin;
} libalgobscure_ExportedSymbols;
extern libalgobscure_ExportedSymbols* libalgobscure_symbols(void);
#ifdef __cplusplus
}  /* extern "C" */
#endif
#endif  /* KONAN_LIBALGOBSCURE_H */

Thật không may, tiêu đề đã làm sập PHP 🙂 Bằng cách dọn dẹp nó một chút, chúng tôi đến với nội dung này:

#define FFI_LIB "./linuxX64/releaseShared/libalgobscure.so"

typedef bool            libalgobscure_KBoolean;
typedef unsigned short     libalgobscure_KChar;
typedef signed char        libalgobscure_KByte;
typedef short              libalgobscure_KShort;
typedef int                libalgobscure_KInt;
typedef long long          libalgobscure_KLong;
typedef unsigned char      libalgobscure_KUByte;
typedef unsigned short     libalgobscure_KUShort;
typedef unsigned int       libalgobscure_KUInt;
typedef unsigned long long libalgobscure_KULong;
typedef float              libalgobscure_KFloat;
typedef double             libalgobscure_KDouble;
typedef float   libalgobscure_KVector128;
typedef void*              libalgobscure_KNativePtr;
struct libalgobscure_KType;
typedef struct libalgobscure_KType libalgobscure_KType;

typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Byte;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Short;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Int;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Long;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Float;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Double;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Char;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Boolean;
typedef struct {
  libalgobscure_KNativePtr pinned;
} libalgobscure_kref_kotlin_Unit;


typedef struct {
  /* Service functions. */
  void (*DisposeStablePointer)(libalgobscure_KNativePtr ptr);
  void (*DisposeString)(const char* string);
  libalgobscure_KBoolean (*IsInstance)(libalgobscure_KNativePtr ref, const libalgobscure_KType* type);
  libalgobscure_kref_kotlin_Byte (*createNullableByte)(libalgobscure_KByte);
  libalgobscure_kref_kotlin_Short (*createNullableShort)(libalgobscure_KShort);
  libalgobscure_kref_kotlin_Int (*createNullableInt)(libalgobscure_KInt);
  libalgobscure_kref_kotlin_Long (*createNullableLong)(libalgobscure_KLong);
  libalgobscure_kref_kotlin_Float (*createNullableFloat)(libalgobscure_KFloat);
  libalgobscure_kref_kotlin_Double (*createNullableDouble)(libalgobscure_KDouble);
  libalgobscure_kref_kotlin_Char (*createNullableChar)(libalgobscure_KChar);
  libalgobscure_kref_kotlin_Boolean (*createNullableBoolean)(libalgobscure_KBoolean);
  libalgobscure_kref_kotlin_Unit (*createNullableUnit)(void);

  /* User functions. */
  struct {
    struct {
      libalgobscure_KLong (*factoriel)(libalgobscure_KLong n);
      libalgobscure_KLong (*fibonacci)(libalgobscure_KLong n);
      void (*main)();
    } root;
  } kotlin;
} libalgobscure_ExportedSymbols;

extern libalgobscure_ExportedSymbols* libalgobscure_symbols(void);

Để hiểu rõ hơn về các đối số của tiêu đề không tương thích, đây là sự khác biệt. Việc dọn dẹp sau cùng không quá phức tạp.

image
image-1

Cơ bản, KMP xuất cho chúng tôi một hàm cung cấp một cấu trúc -> trong một cấu trúc -> trong một cấu trúc với hai con trỏ hàm: giai thừadãy Fibonacci.

Mẹo để truy cập các hàm là điều hướng qua các cấu trúc:

$ffi = FFI::load(__DIR__ . '/kmp.h');
// On vient chercher la methode libalgobscure_symbols()
// qui nous renvoie une structure libalgobscure_ExportedSymbols
$libalgobscure = $ffi->libalgobscure_symbols();
// ensuite on remonte le fil et on récupère l'adresse de nos fonction : 
$factoriel = FFI::addr($libalgobscure->kotlin->root->factoriel)[0];
$fibonacci = FFI::addr($libalgobscure->kotlin->root->fibonacci)[0];

// Et on peut exécuter directement notre fonction anonyme : 
$factoriel(5)

Đây là mã hoàn chỉnh:

#!/usr/bin/php8.1
<?php
opcache_reset();
$ffi = FFI::load(__DIR__ . '/kmp.h');
$libalgobscure = $ffi->libalgobscure_symbols();
$factoriel = FFI::addr($libalgobscure->kotlin->root->factoriel)[0];
echo "factoriel 5 = ";
var_dump($factoriel(5));

echo "factoriel 12 = ";
var_dump($factoriel(12));

$fibonacci = FFI::addr($libalgobscure->kotlin->root->fibonacci)[0];
echo "fibonacci 6 = ";

var_dump($fibonacci(6));

echo "fibonacci 12 = ";
var_dump($fibonacci(12));

var_dump($libalgobscure);

Và kết quả thực thi:

factoriel 5 = int(120)
factoriel 12 = int(479001600)
fibonacci 6 = int(8)
fibonacci 12 = int(144)
object(FFI\CData:struct <anonymous>*)#2 (1) {
  [0]=>
  object(FFI\CData:struct <anonymous>)#5 (13) {
    ["DisposeStablePointer"]=>
    object(FFI\CData:void(*)())#6 (1) {
      [0]=>
      object(FFI\CData:void())#19 (0) {
      }
    }
    ["DisposeString"]=>
    object(FFI\CData:void(*)())#7 (1) {
      [0]=>
      object(FFI\CData:void())#19 (0) {
      }
    }
    ["IsInstance"]=>
    object(FFI\CData:bool(*)())#8 (1) {
      [0]=>
      object(FFI\CData:bool())#19 (0) {
      }
    }
    ["createNullableByte"]=>
    object(FFI\CData:struct <anonymous>(*)())#9 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableShort"]=>
    object(FFI\CData:struct <anonymous>(*)())#10 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableInt"]=>
    object(FFI\CData:struct <anonymous>(*)())#11 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableLong"]=>
    object(FFI\CData:struct <anonymous>(*)())#12 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableFloat"]=>
    object(FFI\CData:struct <anonymous>(*)())#13 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableDouble"]=>
    object(FFI\CData:struct <anonymous>(*)())#14 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableChar"]=>
    object(FFI\CData:struct <anonymous>(*)())#15 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableBoolean"]=>
    object(FFI\CData:struct <anonymous>(*)())#16 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["createNullableUnit"]=>
    object(FFI\CData:struct <anonymous>(*)())#17 (1) {
      [0]=>
      object(FFI\CData:struct <anonymous>())#19 (0) {
      }
    }
    ["kotlin"]=>
    object(FFI\CData:struct <anonymous>)#18 (1) {
      ["root"]=>
      object(FFI\CData:struct <anonymous>)#19 (3) {
        ["factoriel"]=>
        object(FFI\CData:int64_t(*)())#20 (1) {
          [0]=>
          object(FFI\CData:int64_t())#23 (0) {
          }
        }
        ["fibonacci"]=>
        object(FFI\CData:int64_t(*)())#21 (1) {
          [0]=>
          object(FFI\CData:int64_t())#23 (0) {
          }
        }
        ["main"]=>
        object(FFI\CData:void(*)())#22 (1) {
          [0]=>
          object(FFI\CData:void())#23 (0) {
          }
        }
      }
    }
  }
}

Vì vậy, rõ ràng là khả thi để tạo ra một thuật toán cụ thể với KMP và truy xuất nó trong PHP. Chúng ta nên so sánh dựa trên loại hoạt động thực hiện, nhưng nếu nó tránh được tuần lễ phát triển để chuyển thư viện sang PHP, đó là một giải pháp đáng xem xét.
So sánh, không chắc rằng một thuật toán trong PHP sẽ nhanh hơn một cuộc gọi đến thư viện được xuất bởi Kotlin qua php-ffi. Dù sao, tôi nghĩ đó là một hướng đáng khám phá. Đặc biệt nếu bạn đã quyết định chuyển từ backend PHP sang backend KMP và muốn làm điều đó dần dần hoặc chỉ đơn giản là chia sẻ một số mã.

Vẫn với mục tiêu tối ưu, chúng ta sẽ xem trong phần tiếp theo của nghiên cứu trường hợp của chúng tôi cách tích hợp một thư viện thực sự để tạo ra điều gì đó từ nó.
Chúng ta sẽ xem xét QuickJs của Fabrice Bellard, cho phép chúng tôi thực thi mã Javascript trực tiếp từ PHP.
Bạn có thể nghĩ đó là vô ích nhưng tôi đảm bảo rằng tôi đã thấy những tích hợp JS rất thú vị được thực thi bởi PHP.
Nói chung, để tiếp tục, hãy theo hướng này:

PHP FFI: Phần 5 (đang được viết)

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ọ.