我有一个带有以下签名的C函数.它接受(根据我对C语言的理解)一个C字符串数组,并返回一个指向随机字符串的指针.

const char *get_random(const char *const *words, uintptr_t len);

我正在做的是使用FFI在不同的语言中测试这个函数,对于像Java这样的东西,它非常简单(在JNA库的帮助下):

String[] words = { "One", "Two", "Three" };
String word = MyFFI.INSTANCE.get_random(words, words.length);

然而,由于对C语言的理解不足,我很难使用FFI API将PHP数组转换为合适的类型:

$ffi_def = file_get_contents("./get_random.h");
$ffi = FFI::cdef($ffi_def, "./libget_random.so");

$words = ["One", "Two", "Three"];

// Should call `$ffi->get_random()` here

我使用FFI APItry 了几个选项,但最终出现了一堆错误,比如SIGSEGV (Address boundary error)Attempt to perform assign pointer to owned C data.我不会分享例子,因为它们只是一些胡言乱语.

如果有人能帮我,或者至少把我推向正确的方向?

推荐答案

PHP是一种动态类型语言,不像Java那样提供表示严格的string[]数据类型的方法.因此,您需要首先在FFI中构造它,以便传递数据类型所需的函数. 为此,我们将使用FFI::New.

// Note: Before you use this as your final solution, read the rest of the post.
// Assuming it's const char *get_random(const char *const *words, uintptr_t len);
$ffi_def = file_get_contents("./get_random.h");
$ffi = FFI::cdef($ffi_def, "./libget_random.so");

$words = ['One', 'Two', 'Three'];

$count = count($words);

$cWords = FFI::new("char *[$count]", false);
foreach ($words as $i => $word) {
    $len = strlen($word);
    $cWords[$i] = FFI::new("char[$len]", false);
    FFI::memcpy($cWords[$i], $word, $len);
}

$randomWord = $ffi->get_random($cWords, $count);

for ($i = 0; $i < $count; $i++) {
    FFI::free($cWords[$i]);
}
FFI::free($cWords);

echo "$randomWord\n";

The above approach is based on code from over here:
https://github.com/dstogov/php-ffi/issues/40

注意,这种方法传递了FFI::new一个错误的值,所以我们必须自己管理内存.这有一个缺点是不能使用PHP的垃圾收集.如果你把它换成"true",你会遇到一些错误而不调整代码.继续读下go !我来教你怎么做.

另一种方法是调整动态库,使其接受一种便于PHP填充的格式.一个简单的例子可能是使用逗号作为分隔符.例如:

<?php
// Assuming it's changed to: char *get_random(char *words, uintptr_t len);
$ffi_def = file_get_contents('./get_random.h');
$ffi = FFI::cdef($ffi_def, './libget_random.so');

$words = ['One', 'Two', 'Three'];

$str = implode(',', $words);

$randomWord = $ffi->get_random($str, strlen($str));

echo FFI::string($randomWord), "\n";

请注意,在上面,我改变了第二个参数来表示字符串长度,而不是表示数组中的元素数.然后基本上编写你的C,使它在逗号字符上拆分,可能使用strtok_r. 如果你的字符串可以包含逗号,可能是base64围绕它或其他什么. Json是另一种 Select ,但它可能太强大了,因为我们不想支持多个维度来处理字符串[].

从这里可以看到,您可以传递字符串.问题是,PHP没有严格的字符串[]数据类型.如果是这样的话,您可以直接传递它.

例如,在你的C中,你可以写这样的东西:

char *globalStringArray[] = { "One", "Two", "Three" };

然后在标题中添加:

char *globalStringArray[];

您可以这样使用它:

<?php
$ffi_def = file_get_contents('./get_random.h');
$ffi = FFI::cdef($ffi_def, './libget_random.so');
$randomWord = $ffi->get_random($ffi->globalStringArray, 3);
echo "$randomWord\n";

这是一个概念证明,如果您可以加载一个严格的字符串[],则可以将其直接传递到函数中.

另一种方法是在动态库中添加更多的方法,这对PHP来说更容易使用:

<?php
$ffi_def = file_get_contents('./get_random.h');
$ffi = FFI::cdef($ffi_def, './libget_random.so');

$deck = $ffi->newDeck();
$ffi->addCard($deck, 'One');
$ffi->addCard($deck, 'Two');
$ffi->addCard($deck, 'Three')

$card = $ffi->getRandomCard($deck);
$ffi->freeDeck($deck);

echo "$card\n";

既然我介绍了这些其他选项,您可能对最佳方法感到好奇.是的,这可能是FFI::new.但是不,如果我们可以使用PHP的垃圾收集来使它工作,我们可能不想传递一个错误的第二个参数.

准备好解决方案了吗?在玩了这个之后,我发现我们基本上需要铸造,以避免所有权错误.在赋值时,我们需要将变量保持在作用域中,这样PHP的垃圾收集就不会出现.当你在foreach循环中时,变量将超出循环之间的作用域,除非它存在于循环作用域之外.

这就对了!

<?php
// Assuming: const char *get_random(const char *const *words, uintptr_t len);
$ffi_def = file_get_contents('./get_random.h');
$ffi = FFI::cdef($ffi_def, './libget_random.so');

$words = ['One', 'Two', 'Three'];

$wordsCount = count($words);
$cWords = $ffi->new("char*[$wordsCount]");

// Need to keep this in memory so PHP's garbage collecting doesn't kick in between loops
$cWordsHolder = [];

foreach($words as $i => $word)
{
    $len = strlen($word);
    $cWordsHolder[$i] = $ffi->new("char[$len]");
    FFI::memcpy($cWordsHolder[$i], $word, $len);
    $cWords[$i] = FFI::cast('char*', $cWordsHolder[$i]); // cast needed to avoid ownership error
}

$chosenString = $ffi->get_random($cWords, $wordsCount);
echo "$chosenString\n"; 

我要做的最后一件事是切换到面向对象的编程,这样整个动态库就像一个对象:

<?php

class GetRandomFFI
{
    protected FFI $ffi;
    protected array $memoryHolder = [];
    public function __construct()
    {
        $ffi_def = file_get_contents(__DIR__ . '/get_random.h');
        $this->ffi = FFI::cdef($ffi_def, __DIR__ . '/libget_random.so');
    }

    public function getRandom(array $words = []): ?string
    {
        if (!count($words)) {
            return null;
        }
        $output = $this->ffi->get_random($this->phpStringArrayToCArray($words), count($words));
        // Since we used our argument, we can clear it from memory early now, but not needed unless we're gonna do a lot of heavy-lifting.
        // Once our class instance goes out of scope, this would clear automatically. Might as well clear it early.
        array_pop($this->memoryHolder);
        return $output;

    }
    protected function phpStringArrayToCArray(array $phpStringArray)
    {
        $phpStringArrayCount = count($phpStringArray);
        $cArray = $this->ffi->new("char*[$phpStringArrayCount]");
        $memoryHolder = [];
        foreach($phpStringArray as $i => $phpString)
        {
            $len = strlen($phpString);
            $memoryHolder[$i] = $this->ffi->new("char[$len]");
            FFI::memcpy($memoryHolder[$i], $phpString, $len);
            $cArray[$i] = FFI::cast('char*', $memoryHolder[$i]); // cast needed to avoid ownership error
        }
        // Once this goes out of scope, our $cArray will point to garbage.
        // So we store this on a property that will live as long as our class instance.
        $this->memoryHolder[] = $memoryHolder;
        return $cArray;
    }
}


$grf = new GetRandomFFI();
echo $grf->getRandom(['One','Two','Three']), "\n";

Php相关问答推荐

如何在Livewire中验证多个 Select 字段

如何在数据库中添加注册表单中的动态新行?

在WooCommerce中执行一次银行电汇确认付款代码

在WooCommerce产品档案页面中显示产品销售百分比

将带有值的属性添加到WooCommerce产品,保留现有属性

使用PHP阅读XML提要

与会者在WooCommerce中按购物车项目数量自定义 checkout 字段

如何在函数中定位WooCommerce产品循环

将WooCommerce WC_Order对象与wp_Schedule_Event一起使用

如何配置我的.env文件以在laravel的一个项目中连接两个不同的数据库?

在PHP项目中安装OpenAI PHP SDK时出现问题

文本到语音:不考虑音高值

未检测到laravel控制器

PHP Laravel 迁移文件不创建关系

Google Drive API 服务->文件->get('root') 失败并显示找不到文件. (范围问题?)

由于 PHP 版本不受支持,如何终止 PHP 脚本?

如何设置Mailersend PHP API 模板变量?

在wordpress注销后防止后退操作

PHP for each 函数打印 td

如何为多个表的id列设置不同的值