PHP 8.3通过FFI调用Pascal DLL时如何持久化全局句柄?
首先明确:PHP传统请求模式(比如PHP-FPM、CGI)下,全局变量没法跨请求保留FFI实例。
原因很简单:PHP的每个请求都是独立的执行上下文,就算用PHP-FPM的进程池,每个进程的内存空间也是完全隔离的——你在A请求里初始化的FFI实例,只存在于处理A请求的进程内存中,B请求大概率会落到另一个进程;就算凑巧落到同一个进程,之前的全局变量也可能因为进程复用的清理机制被重置,根本没法稳定复用。
你遇到的Session问题本质也是一样:FFI实例和指针是绑定当前进程内存空间的,序列化后存到Session里,另一个请求加载时,指针指向的内存地址在新进程里是完全无效的,自然会触发访问冲突。
给你几个可行的解决思路:
1. 用常驻进程框架(推荐)
如果业务场景允许,改用Swoole、Workerman这类常驻进程框架。这类框架会在进程启动时初始化一次FFI实例,之后所有请求都在同一个进程(或固定的进程池)里处理,FFI实例可以直接存在全局变量或类的静态属性里,全程复用,不会有跨进程的问题。
示例伪代码:
// 进程启动时初始化 $ffi = FFI::cdef(" void init_dll(); void fill_data(char* data); void save_data(); ", "your_dll.dll"); $ffi->init_dll(); // 请求处理时直接复用 $app->on('request', function($request, $response) use ($ffi) { if ($request->action === 'fill') { $ffi->fill_data($request->input); } elseif ($request->action === 'save') { $ffi->save_data(); } });
2. 把DLL状态序列化到外部存储
如果没法改框架,那你需要避免直接存FFI指针,而是把DLL中需要保留的业务状态数据(比如填充的输入内容、配置参数)提取出来,存到Redis、文件或者数据库里。下次请求时,重新加载DLL,再把这些数据传入DLL恢复状态,而不是复用之前的指针。
比如:
// 填充数据的请求 $ffi = FFI::cdef(/* 函数定义 */, "your_dll.dll"); $ffi->init_dll(); $ffi->fill_data($input); // 提取DLL里的状态数据(需要DLL提供导出函数来获取) $state = $ffi->get_dll_state(); // 存到Redis $redis->set('dll_state', serialize($state)); // 保存数据的请求 $ffi = FFI::cdef(/* 函数定义 */, "your_dll.dll"); $ffi->init_dll(); // 从Redis取出状态并恢复 $state = unserialize($redis->get('dll_state')); $ffi->set_dll_state($state); $ffi->save_data();
当然这要求你的Pascal DLL能提供获取/设置状态的导出函数,如果DLL本身不支持,这个方案没法用。
3. 调整DLL为无状态或自管理状态
如果能修改DLL的代码,最好把它改成无状态调用——每次调用fill_data和save_data时,都传入唯一的上下文标识(比如用户ID、请求ID),让DLL自己把状态存在文件或者内存映射里,PHP只需要在调用时传递这个标识,不需要自己保留任何指针或实例。
这样不管PHP怎么重启进程,只要传入正确的标识,DLL就能找到对应的状态继续处理。
内容的提问来源于stack exchange,提问作者Renato Rubinho

