PHP7.4 new extension method FFI detailed explanation
With PHP7.4 comes an extension that I think is very useful: PHP FFI (Foreign Function interface) , quoting a description in the PHP FFI RFC:
For PHP, FFI opens a way to write PHP extensions and bindings to C libraries in pure PHP.
Yes, FFI provides high-level languages to directly call each other, while for PHP In other words, FFI allows us to easily call various libraries written in C language.
In fact, there are a large number of PHP extensions that are packages of some existing C libraries. Some commonly used mysqli, curl, gettext, etc. are also included in PECL. Tons of similar extensions.
In the traditional way, when we need to use the capabilities of some existing C language libraries, we need to write wrappers in C language and package them into extensions. This process requires everyone to learn How to write PHP extensions? Of course, there are some convenient ways now, some kind of Zephir. But there are still some learning costs, and with FFI, we can directly call functions in libraries written in C language in PHP scripts.
In the decades of history of C language, excellent libraries have been accumulated, and FFI directly allows us to conveniently enjoy this huge resource.
Getting back to the subject, today I will use an example to introduce how we use PHP to call libcurl to crawl the content of a web page. Why use libcurl? Doesn’t PHP already have a curl extension? Well, first of all, I am familiar with libcurl's API. Secondly, it is precisely because of it that it is easy to compare. Isn't the traditional expansion method AS and FFI methods directly easier to use?
First of all, let’s take the current article you are reading as an example. I now need to write a piece of code to capture its content. If we use the traditional PHP curl extension, we will probably write like this :
<?php $url = "https://www.laruence.com/2020/03/11/5475.html"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_exec($ch); curl_close($ch);
(Because my website is https, there will be one more operation to set SSL_VERIFYPEER) What if I use FFI?
First enable ext/ffi of PHP7.4. It should be noted that PHP-FFI requires libffi-3 or above.
Then, we need to tell PHP FFI what the prototype of the function we want to call looks like. We can use FFI::cdef, and its prototype is:
FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
In the string $cdef, we can write a C language functional declaration, FFI will parse it, and understand that we want to use this in the string $lib What is the signature of the function called in the library? In this example, we use three libcurl functions. We can find their declarations in the libcurl documentation. Some information about curl_easy_init .
Specifically for this example, we write a curl.php, which contains all the things to be declared. The code is as follows:
$libcurl = FFI::cdef(<<<CTYPE void *curl_easy_init(); int curl_easy_setopt(void *curl, int option, ...); int curl_easy_perform(void *curl); void curl_easy_cleanup(void *handle); CTYPE , "libcurl.so" );
Here is a place where it is written in the document The return value is CURL *, but in fact since our example does not dereference it, it is just passed, then use void * instead to avoid trouble.
However, another troublesome thing is that PHP is predefined:
Okay, even if the definition part is completed, now we complete the actual logic part, and the entire code will be :
<?php require "curl.php"; $url = "https://www.laruence.com/2020/03/11/5475.html"; $ch = $libcurl->curl_easy_init(); $libcurl->curl_easy_setopt($ch, CURLOPT_URL, $url); $libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $libcurl->curl_easy_perform($ch); $libcurl->curl_easy_cleanup($ch);How about using curl expansion instead, is it just as concise?
Next, we make it a little more complicated, until, if we don’t want the result to be output directly, but returned as a string, for PHP’s curl extension, we only need to call
curl_setopSetCURLOPT_RETURNTRANSFERto 1, but in libcurl there is no ability to directly return a string, or an alternative function toWRITEFUNCTIONis provided. When there is data returned When libcurl calls this function, in fact, the PHP curl extension does the same.Currently we cannot directly pass a PHP function as an additional function to libcurl through FFI, then we have two ways to do it:
1. Use
WRITEDATA, the default libcurl will callfwriteas a variable function, and we can give libcurl an fd throughWRITEDATA, so that it does not writestdout, but writes Enter this fd2. We write a simple C function ourselves, pass the FFI date in, and pass it to libcurl.
Let’s use the first method first. First we need to use
fopen. This time we declare the prototype by defining a C header file (file.h) :void *fopen(char *filename, char *mode); void fclose(void * fp);Like
file.h, we also put all libcurl function declarations incurl.h#define FFI_LIB "libcurl.so" void *curl_easy_init(); int curl_easy_setopt(void *curl, int option, ...); int curl_easy_perform(void *curl); void curl_easy_cleanup(CURL *handle);Then we can use
FFI::loadto load the .h file:static function load(string $filename): FFI;But how to tell FFI to load the corresponding library? As above, we tell FFI that these functions come from
libcurl.soby defining aFFI_LIBmacro. When we useFFI::loadto load this h file, PHP FFI will automatically load libcurl.soSo why does
fopennot need to specify the loading library? That is because FFI will also look for symbols in the variable symbol table, andfopenis a standard library function that has existed for a long time.Okay, now the entire code will be:
<?php const CURLOPT_URL = 10002; const CURLOPT_SSL_VERIFYPEER = 64; const CURLOPT_WRITEDATA = 10001; $libc = FFI::load("file.h"); $libcurl = FFI::load("curl.h"); $url = "https://www.laruence.com/2020/03/11/5475.html"; $tmpfile = "/tmp/tmpfile.out"; $ch = $libcurl->curl_easy_init(); $fp = $libc->fopen($tmpfile, "a"); $libcurl->curl_easy_setopt($ch, CURLOPT_URL, $url); $libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA, $fp); $libcurl->curl_easy_perform($ch); $libcurl->curl_easy_cleanup($ch); $libc->fclose($fp); $ret = file_get_contents($tmpfile); @unlink($tmpfile);但这种方式呢就是需要一个临时的中转文件,还是不够优雅,现在我们用第二种方式,要用第二种方式,我们需要自己用C写一个替代函数传递给libcurl:
#include <stdlib.h> #include <string.h> #include "write.h" size_t own_writefunc(void *ptr, size_t size, size_t nmember, void *data) { own_write_data *d = (own_write_data*)data; size_t total = size * nmember; if (d->buf == NULL) { d->buf = malloc(total); if (d->buf == NULL) { return 0; } d->size = total; memcpy(d->buf, ptr, total); } else { d->buf = realloc(d->buf, d->size + total); if (d->buf == NULL) { return 0; } memcpy(d->buf + d->size, ptr, total); d->size += total; } return total; } void * init() { return &own_writefunc; }注意此处的初始函数,因为在PHP FFI中,就目前的版本(2020-03-11)我们没有办法直接获得一个函数指针,所以我们定义了这个函数,返回
own_writefunc的地址。最后我们定义上面用到的头文件
write.h:#define FFI_LIB "write.so" typedef struct _writedata { void *buf; size_t size; } own_write_data; void *init();注意到我们在头文件中也定义了
FFI_LIB,这样这个头文件就可以同时被write.c和接下来我们的PHP FFI共同使用了。然后我们编译
write函数为一个动态库:gcc -O2 -fPIC -shared -g write.c -o write.so好了,现在整个的代码会变成:
<?php const CURLOPT_URL = 10002; const CURLOPT_SSL_VERIFYPEER = 64; const CURLOPT_WRITEDATA = 10001; const CURLOPT_WRITEFUNCTION = 20011; $libcurl = FFI::load("curl.h"); $write = FFI::load("write.h"); $url = "https://www.laruence.com/2020/03/11/5475.html"; $data = $write->new("own_write_data"); $ch = $libcurl->curl_easy_init(); $libcurl->curl_easy_setopt($ch, CURLOPT_URL, $url); $libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA, FFI::addr($data)); $libcurl->curl_easy_setopt($ch, CURLOPT_WRITEFUNCTION, $write->init()); $libcurl->curl_easy_perform($ch); $libcurl->curl_easy_cleanup($ch); ret = FFI::string($data->buf, $data->size);此处,我们使用
FFI :: new($ write-> new)来分配了一个结构_write_data的内存:function FFI::new(mixed $type [, bool $own = true [, bool $persistent = false]]): FFI\CData
$own表示这个内存管理是否采用PHP的内存管理,有时的情况下,我们申请的内存会经过PHP的生命周期管理,不需要主动释放,但是有的时候你也可能希望自己管理,那么可以设置$own为flase,那么在适当的时候,你需要调用FFI :: free去主动释放。然后我们把
$data作为WRITEDATA传递给libcurl,这里我们使用了FFI :: addr来获取$data的实际内存地址:static function addr(FFI\CData $cdata): FFI\CData;然后我们把
own_write_func作为WRITEFUNCTION传递给了libcurl,这样再有返回的时候,libcurl就会调用我们的own_write_func来处理返回,同时会把write_data作为自定义参数传递给我们的替代函数。最后我们使用了
FFI :: string来把一段内存转换成PHP的string:static function FFI::string(FFI\CData $src [, int $size]): string好了,跑一下吧?
然而毕竟直接在PHP中每次请求都加载so的话,会是一个很大的性能问题,所以我们也可以采用
preload的方式,这种模式下,我们通过opcache.preload来在PHP启动的时候就加载好:ffi.enable=1 opcache.preload=ffi_preload.incffi_preload.inc:
<?php FFI::load("curl.h"); FFI::load("write.h");但我们引用加载的FFI呢?因此我们需要修改一下这俩个.h头文件,加入
FFI_SCOPE,比如curl.h:#define FFI_LIB "libcurl.so" #define FFI_SCOPE "libcurl" void *curl_easy_init(); int curl_easy_setopt(void *curl, int option, ...); int curl_easy_perform(void *curl); void curl_easy_cleanup(void *handle);对应的我们给
write.h也加入FFI_SCOPE为“ write”,然后我们的脚本现在看起来应该是这样的:<?php const CURLOPT_URL = 10002; const CURLOPT_SSL_VERIFYPEER = 64; const CURLOPT_WRITEDATA = 10001; const CURLOPT_WRITEFUNCTION = 20011; $libcurl = FFI::scope("libcurl"); $write = FFI::scope("write"); $url = "https://www.laruence.com/2020/03/11/5475.html"; $data = $write->new("own_write_data"); $ch = $libcurl->curl_easy_init(); $libcurl->curl_easy_setopt($ch, CURLOPT_URL, $url); $libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA, FFI::addr($data)); $libcurl->curl_easy_setopt($ch, CURLOPT_WRITEFUNCTION, $write->init()); $libcurl->curl_easy_perform($ch); $libcurl->curl_easy_cleanup($ch); ret = FFI::string($data->buf, $data->size);也就是,我们现在使用
FFI :: scope来代替FFI :: load,引用对应的函数。static function scope(string $name): FFI;然后还有另外一个问题,FFI虽然给了我们很大的规模,但是毕竟直接调用C库函数,还是非常具有风险性的,我们应该只允许用户调用我们确认过的函数,于是,
ffi.enable = preload就该上场了,当我们设置ffi.enable = preload的话,那就只有在opcache.preload的脚本中的函数才能调用FFI,而用户写的函数是没有办法直接调用的。我们稍微修改下
ffi_preload.inc变成ffi_safe_preload.inc<?php class CURLOPT { const URL = 10002; const SSL_VERIFYHOST = 81; const SSL_VERIFYPEER = 64; const WRITEDATA = 10001; const WRITEFUNCTION = 20011; } FFI::load("curl.h"); FFI::load("write.h"); function get_libcurl() : FFI { return FFI::scope("libcurl"); } function get_write_data($write) : FFI\CData { return $write->new("own_write_data"); } function get_write() : FFI { return FFI::scope("write"); } function get_data_addr($data) : FFI\CData { return FFI::addr($data); } function paser_libcurl_ret($data) :string{ return FFI::string($data->buf, $data->size); }也就是,我们把所有会调用FFI API的函数都定义在
preload脚本中,然后我们的示例会变成(ffi_safe.php):<?php $libcurl = get_libcurl(); $write = get_write(); $data = get_write_data($write); $url = "https://www.laruence.com/2020/03/11/5475.html"; $ch = $libcurl->curl_easy_init(); $libcurl->curl_easy_setopt($ch, CURLOPT::URL, $url); $libcurl->curl_easy_setopt($ch, CURLOPT::SSL_VERIFYPEER, 0); $libcurl->curl_easy_setopt($ch, CURLOPT::WRITEDATA, get_data_addr($data)); $libcurl->curl_easy_setopt($ch, CURLOPT::WRITEFUNCTION, $write->init()); $libcurl->curl_easy_perform($ch); $libcurl->curl_easy_cleanup($ch); $ret = paser_libcurl_ret($data);这样一来通过
ffi.enable = preload,我们就可以限制,所有的FFI API只能被我们可控制的preload脚本调用,用户不能直接调用。从而我们可以在这些函数内部做好适当的安全保证工作,从而保证一定的安全性。好了,经历了这个例子,大家应该对FFI有一个比较深入的理解了,详细的PHP API说明,大家可以参考:PHP-FFI Manual,有兴趣的话,就去找一个C库,试试吧?
本文的例子,你可以在我的github上下载到:FFI example
最后还是多说一句,例子只是为了演示功能,所以省掉了很多错误分支的判断捕获,大家自己写的时候还是要加入。毕竟使用FFI的话,会让你会有1000种方式让PHP segfault crash,所以be careful
推荐PHP教程《PHP7》
The above is the detailed content of PHP7.4 new extension method FFI detailed explanation. For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
Undresser.AI Undress
AI-powered app for creating realistic nude photos
AI Clothes Remover
Online AI tool for removing clothes from photos.
Clothoff.io
AI clothes remover
Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!
Hot Article
Hot Tools
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
What are public, private, and protected in php
Aug 24, 2025 am 03:29 AM
Public members can be accessed at will; 2. Private members can only be accessed within the class; 3. Protected members can be accessed in classes and subclasses; 4. Rational use can improve code security and maintainability.
How to execute an UPDATE query in php
Aug 24, 2025 am 05:04 AM
Using MySQLi object-oriented method: establish a connection, preprocess UPDATE statements, bind parameters, execute and check the results, and finally close the resource. 2. Using MySQLi procedure method: connect to the database through functions, prepare statements, bind parameters, perform updates, and close the connection after processing errors. 3. Use PDO: Connect to the database through PDO, set exception mode, pre-process SQL, bind parameters, perform updates, use try-catch to handle exceptions, and finally release resources. Always use preprocessing statements to prevent SQL injection, verify user input, and close connections in time.
How to use cURL in php
Aug 24, 2025 am 08:32 AM
cURLinPHPenablessendingHTTPrequests,fetchingAPIdata,anduploadingfiles.Initializewithcurl_init(),setoptionslikeCURLOPT_URLandCURLOPT_RETURNTRANSFER,useCURLOPT_POSTforPOSTrequests,sendJSONwithproperheaders,handleerrorsviacurl_errno()andHTTPcodeswithcur
How to read a CSV file in PHP?
Aug 29, 2025 am 08:06 AM
ToreadaCSVfileinPHP,usefopen()toopenthefile,fgetcsv()inalooptoreadeachrowasanarray,andfclose()tocloseit;handleheaderswithaseparatefgetcsv()callandspecifydelimitersasneeded,ensuringproperfilepathsandUTF-8encodingforspecialcharacters.
How to use AJAX with php
Aug 29, 2025 am 08:58 AM
AJAXwithPHPenablesdynamicwebappsbysendingasynchronousrequestswithoutpagereloads.1.CreateHTMLwithJavaScriptusingfetch()tosenddata.2.BuildaPHPscripttoprocessPOSTdataandreturnresponses.3.UseJSONforcomplexdatahandling.4.Alwayssanitizeinputsanddebugviabro
What is the difference between isset and empty in php
Aug 27, 2025 am 08:38 AM
isset()checksifavariableexistsandisnotnull,returningtrueevenforzero,false,oremptystringvalues;2.empty()checksifavariableisnull,false,0,"0","",orundefined,returningtrueforthese"falsy"values;3.isset()returnsfalsefornon-exi
Edit bookmarks in chrome
Aug 27, 2025 am 12:03 AM
Chrome bookmark editing is simple and practical. Users can enter the bookmark manager through the shortcut keys Ctrl Shift O (Windows) or Cmd Shift O (Mac), or enter through the browser menu; 1. When editing a single bookmark, right-click to select "Edit", modify the title or URL and click "Finish" to save; 2. When organizing bookmarks in batches, you can hold Ctrl (or Cmd) to multiple-choice bookmarks in the bookmark manager, right-click to select "Move to" or "Copy to" the target folder; 3. When exporting and importing bookmarks, click the "Solve" button to select "Export Bookmark" to save as HTML file, and then restore it through the "Import Bookmark" function if necessary.
How to configure SMTP for sending mail in php
Aug 27, 2025 am 08:08 AM
Answer: Using the PHPMailer library to configure the SMTP server can enable sending mails through SMTP in PHP applications. PHPMailer needs to be installed, set up SMTP host, port, encryption method and authentication credentials of Gmail, write code to set sender, recipient, topic and content, enable 2FA and use application password to ensure that the server allows SMTP connection, and finally call the send method to send email.


