2024 年日本 PHP 研討會

FFI 基本用法

在深入探討 FFI API 的細節之前,讓我們先來看幾個範例,示範 FFI API 在一般任務中的簡易用法。

注意:

其中一些範例需要 libc.so.6,因此在沒有它的系統上將無法運作。

範例 #1 從共享函式庫呼叫函式

<?php
// 建立 FFI 物件,載入 libc 並匯出函式 printf()
$ffi = FFI::cdef(
"int printf(const char *format, ...);", // 這是標準的 C 宣告
"libc.so.6");
// 呼叫 C 的 printf()
$ffi->printf("Hello %s!\n", "world");
?>

上述範例會輸出

Hello world!

注意:

請注意,某些 C 函式需要特定的呼叫慣例,例如 __fastcall__stdcall__vectorcall

範例 #2 透過引數呼叫函式,回傳結構

<?php
// 建立 gettimeofday() 繫結
$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");
// 建立 C 資料結構
$tv = $ffi->new("struct timeval");
$tz = $ffi->new("struct timezone");
// 呼叫 C 的 gettimeofday()
var_dump($ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz)));
// 讀取 C 資料結構的欄位
var_dump($tv->tv_sec);
// 印出整個 C 資料結構
var_dump($tz);
?>

上述範例會輸出類似以下的內容

int(0)
int(1555946835)
object(FFI\CData:struct timezone)#3 (2) {
  ["tz_minuteswest"]=>
  int(0)
  ["tz_dsttime"]=>
  int(0)
}

範例 #3 讀取現有的 C 變數

<?php
// 建立 FFI 物件,載入 libc 並匯出 errno 變數
$ffi = FFI::cdef(
"int errno;", // 這是標準的 C 宣告
"libc.so.6");
// 印出 C 的 errno
var_dump($ffi->errno);
?>

上述範例會輸出

int(0)

範例 #4 建立和修改 C 變數

<?php
// 建立一個新的 C int 變數
$x = FFI::new("int");
var_dump($x->cdata);

// 簡單賦值
$x->cdata = 5;
var_dump($x->cdata);

// 複合賦值
$x->cdata += 2;
var_dump($x->cdata);
?>

上述範例會輸出

int(0)
int(5)
int(7)

範例 #5 操作 C 陣列

<?php
// 建立 C 資料結構
$a = FFI::new("long[1024]");
// 像操作一般的 PHP 陣列一樣操作它
for ($i = 0; $i < count($a); $i++) {
$a[$i] = $i;
}
var_dump($a[25]);
$sum = 0;
foreach (
$a as $n) {
$sum += $n;
}
var_dump($sum);
var_dump(count($a));
var_dump(FFI::sizeof($a));
?>

上述範例會輸出

int(25)
int(523776)
int(1024)
int(8192)

範例 #6 操作 C 列舉 (enums)

<?php
$a
= FFI::cdef('typedef enum _zend_ffi_symbol_kind {
ZEND_FFI_SYM_TYPE,
ZEND_FFI_SYM_CONST = 2,
ZEND_FFI_SYM_VAR,
ZEND_FFI_SYM_FUNC
} zend_ffi_symbol_kind;
'
);
var_dump($a->ZEND_FFI_SYM_TYPE);
var_dump($a->ZEND_FFI_SYM_CONST);
var_dump($a->ZEND_FFI_SYM_VAR);
?>

上述範例會輸出

int(0)
int(2)
int(3)

新增註釋

使用者貢獻的註釋 1 則註釋

4
wowabbs+php at gmail dot com
3 年前
<?
if(!dl("ffi")) // 載入擴充套件
throw new Exception('無法載入 FFI 擴充套件。');

function setWindowsDesktop($bmpFilePath)
{
define('SPI_SETDESKWALLPAPER' , 0x14);
define('SPIF_UPDATEINIFILE' , 0x1);
define('SPIF_SENDWININICHANGE' , 0x2);
assert(File_Exists($bmpFilePath));

// 宣告用於更改桌面設定的 Win32 API 函式。
$User32 = FFI::cdef(<<<'IDL'
int SystemParametersInfoA(int uAction, int uParam, char *lpvParam, int fuWinIni);
IDL, 'User32.dll');
$Kernel32 = FFI::cdef(<<<'IDL'
int GetLastError();
IDL, 'Kernel32.dll');
// 呼叫 Windows API 更新桌面背景。
$Ret = $User32->SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, $bmpFilePath, SPIF_UPDATEINIFILE || SPIF_SENDWININICHANGE);
if ($Ret == 0)
{
$Error = $Kernel32->GetLastError();
throw new Exception("呼叫 Windows API 失敗(錯誤 {$Error})。");
}
}

$Url='https://php.dev.org.tw//images/news/phpkonf_2015.png';
$Img=File_Get_Contents($Url);
File_Put_Contents($File=basename($Url), $Img);
setWindowsDesktop(realpath($File));
?>
To Top