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";