PHP FFI 加载外部动态链接库
前言⌗
在网上看到一个开源项目 DooTask,按照官网介绍的功能来看挺强大的,于是打算本地部署一套试试。
部署完成后发现,专业版只能供三个人使用,这不是白给么!我想既然是 PHP 还是基于 Laravel 开发的,那我熟啊。果然让我找到了 License 和用户数量限制逻辑,但是他用了 PHP 的 FFI,这个我还真是第一次见……
介绍⌗
PHP FFI(Foreign Function Interface)是 PHP 7.4 版本引入的功能,它允许 PHP 直接调用和操作外部的 C 语言库(或者其他语言的共享库)。通过 PHP FFI,开发人员可以在 PHP 中直接调用这些外部库中的函数、访问结构体和全局变量,从而实现与底层系统级别的交互。
一些主要特点和优势包括:
直接调用:PHP FFI 可以直接调用 C 函数,无需通过中间层或扩展,简化了与 C 语言库的交互过程。
系统级别交互:通过 PHP FFI,可以直接访问和操作系统级别的功能,如操作系统 API、硬件相关功能等。
性能优势:由于可以直接调用底层库的函数,PHP FFI 可以在一定程度上提高性能,尤其是对于那些需要频繁调用系统级别函数的应用。
扩展性:PHP FFI 提供了与外部库进行交互的灵活性,使得 PHP 能够更好地与其他语言或系统进行集成。
跨平台:通过 PHP FFI,可以编写跨平台的 PHP 代码,无需担心不同操作系统之间的兼容性问题。
虽然 PHP FFI 的使用需要谨慎,因为直接调用外部库可能导致安全性和稳定性方面的问题,但对于一些需要与 C 语言库进行交互的特定场景,PHP FFI 提供了一种便捷而强大的解决方案。
场景⌗
过去我们 PHP 代码只能通过加密混淆来让代码变得不可读,这样在交付给甲方以后避免对方复制分发,或者硬编码绕过验证机制,那么通过编译性语言实现的外部动态链接库,则可以提高破解成本,代码也无需加密!
示例⌗
package main
import (
"C"
"fmt"
)
func main() {}
//export SayHello
func SayHello(name *C.char) {
str := C.GoString(name)
fmt.Printf("Hello %s!\n", str)
}
在 Go 里面接收 C 的 String 需要用 *C.char
类型来接收,再用 C.GoString()
转换为 Go 的 string。
go build -o hello.so -buildmode=c-shared
编译完成后会输出 hello.so
和 hello.h
两个文件:
file hello.so
hello.so: Mach-O 64-bit dynamically linked shared library arm64
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package _/Users/George/Desktop */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern void SayHello(char* name);
#ifdef __cplusplus
}
#endif
在 PHP 中通过 FFI 导入 hello.so
文件:
<?php
$ffi = FFI::cdef("void SayHello(char* name);", __DIR__ . "/hello.so");
$ffi->SayHello("George");
?>
测试是否能够正常运行:
php hello.php
//Output: Hello George!
I hope this is helpful, Happy hacking…