前言

在网上看到一个开源项目 DooTask,按照官网介绍的功能来看挺强大的,于是打算本地部署一套试试。

部署完成后发现,专业版只能供三个人使用,这不是白给么!我想既然是 PHP 还是基于 Laravel 开发的,那我熟啊。果然让我找到了 License 和用户数量限制逻辑,但是他用了 PHP 的 FFI,这个我还真是第一次见……

介绍

PHP FFI(Foreign Function Interface)是 PHP 7.4 版本引入的功能,它允许 PHP 直接调用和操作外部的 C 语言库(或者其他语言的共享库)。通过 PHP FFI,开发人员可以在 PHP 中直接调用这些外部库中的函数、访问结构体和全局变量,从而实现与底层系统级别的交互。

一些主要特点和优势包括:

  1. 直接调用:PHP FFI 可以直接调用 C 函数,无需通过中间层或扩展,简化了与 C 语言库的交互过程。

  2. 系统级别交互:通过 PHP FFI,可以直接访问和操作系统级别的功能,如操作系统 API、硬件相关功能等。

  3. 性能优势:由于可以直接调用底层库的函数,PHP FFI 可以在一定程度上提高性能,尤其是对于那些需要频繁调用系统级别函数的应用。

  4. 扩展性:PHP FFI 提供了与外部库进行交互的灵活性,使得 PHP 能够更好地与其他语言或系统进行集成。

  5. 跨平台:通过 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.sohello.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…