【问题标题】:How to write a Raku declaration for a C function returning a whole struct?如何为返回整个结构的 C 函数编写 Raku 声明?
【发布时间】:2019-11-25 10:57:47
【问题描述】:

我有这个 C 代码:

typedef struct {
  double dat[2];
} gsl_complex;

gsl_complex gsl_poly_complex_eval(const double c[], const int len, const gsl_complex z);

C 函数返回一个完整的结构,而不仅仅是一个指针,所以我不能将 Raku 声明写为:

sub gsl_poly_complex_eval(CArray[num64] $c, int32 $len, gsl_complex $z --> gsl_complex)
  is native(LIB) is export { * }

有什么建议吗?

【问题讨论】:

  • 您是否尝试将返回值视为双精度的 CArray?或另一个合适大小的缓冲区。 Nativecall 并不真正关心 C 端的打字(我认为)。它信任你,只是将 c 中的字节倒入你告诉它的 raku 端。
  • @Holli 不,它不起作用:CArray 通过引用返回,而不是按值返回。尽管如此,我还是尝试了它,但 valgrind 显示读取返回的值会导致两个“大小为 8 的无效读取”。

标签: raku nativecall


【解决方案1】:

为此,您需要CStructP5localtime 模块包含一个更详细的示例。

【讨论】:

  • 我就是这么做的。不同之处在于,在/usr/include/time.h 中,我看到本地时间被定义为extern struct tm *localtime (const time_t *__timer) __THROW;:它返回一个指向结构的指针。在我的例子中,gsl_poly_complex_eval 返回一个结构,而不是一个指针。
  • 啊哈,嗯...也许docs.raku.org/language/nativecall#Buffers_and_blobs 可能会有所帮助。或者编写一个返回指针的C帮助函数?
  • 我试图返回一个 Blob,但它导致 moarvm 崩溃;我猜虚拟机不知道需要读取多少字节。 C 辅助函数应存储返回的结构,以使其 Raku 对应物读取该内存区域;我担心这会使它成为线程不安全的。
  • 那么也许是时候解决 Rakudo / MoarVM 问题了。
  • @raiph 我提交了 Rakudo 问题。我已经考虑了 this 特定问题的解决方案(我需要编写一个小的 C 函数,它返回一个 16 字节长的 double,通过位移组合两个 double 值),但我没有到目前为止,我有时间测试该解决方案,因为我正在开发一个与大型 libgsl 库的接口(并且 Raku 已经有一个复杂的数据类型)。
【解决方案2】:

问题

一些 C API 使用三阶段方法处理结构,通过引用传递结构,如下所示:

struct mystruct *init_mystruct(arguments, ...);
double compute(struct mystruct *);
void clean_mystruct(struct mystruct *);

这种实现方式隐藏了数据结构,但这是有代价的:最终用户必须跟踪他们的指针并记住自己清理,否则程序会泄漏内存。
另一种方法是我连接的库使用的方法:返回堆栈上的数据,因此可以将其分配给 auto 变量,并在超出范围时自动丢弃。
在这种情况下,API 被建模为两阶段操作:

struct mystruct init_mystruct(arguments, ...);
double compute(struct mystruct);

结构在堆栈上按值传递,之后无需清理。
但是 Raku 的 NativeCall 接口只能使用 C 结构体通过引用传递它们,因此出现了问题。

解决办法

这不是一个干净的解决方案,因为它会退回到所描述的第一种方法,即三相方法,但它是迄今为止我能够设计的唯一方法。
这里我考虑了库 API 中的两个 C 函数:第一个创建一个复数作为结构,第二个将两个数字相加。
首先我写了一个很小的C代码接口,文件complex.c:

#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <stdlib.h>

gsl_complex *alloc_gsl_complex(void)
{
  gsl_complex *c = malloc(sizeof(gsl_complex));
  return c;
}

void free_gsl_complex(gsl_complex *c)
{
  free(c);
}

void mgsl_complex_rect(double x, double y, gsl_complex *res)
{
  gsl_complex ret = gsl_complex_rect(x, y);
  *res = ret;
}

void mgsl_complex_add(gsl_complex *a, gsl_complex *b, gsl_complex *res)
{
  *res = gsl_complex_add(*a, *b);
}

我是这样编译的:
gcc -c -fPIC complex.c
gcc -shared -o libcomplex.so complex.o -lgsl
请注意最后一个 -lgsl 用于链接我正在连接的 libgsl C 库。
然后我写了 Raku 低级接口:

#!/usr/bin/env raku

use NativeCall;

constant LIB  = ('/mydir/libcomplex.so');

class gsl_complex is repr('CStruct') {
  HAS num64 @.dat[2] is CArray;
}

sub mgsl_complex_rect(num64 $x, num64 $y, gsl_complex $c) is native(LIB) { * }
sub mgsl_complex_add(gsl_complex $a, gsl_complex $b, gsl_complex $res) is native(LIB) { * }
sub alloc_gsl_complex(--> gsl_complex) is native(LIB) { * }
sub free_gsl_complex(gsl_complex $c) is native(LIB) { * }

my gsl_complex $c1 = alloc_gsl_complex;
mgsl_complex_rect(1e0, 2e0, $c1);
say "{$c1.dat[0], $c1.dat[1]}";          # output: 1 2
my gsl_complex $c2 = alloc_gsl_complex;
mgsl_complex_rect(1e0, 2e0, $c2);
say "{$c2.dat[0], $c2.dat[1]}";          # output: 1 2
my gsl_complex $res = alloc_gsl_complex;
mgsl_complex_add($c1, $c2, $res);
say "{$res.dat[0], $res.dat[1]}";        # output: 2 4
free_gsl_complex($c1);
free_gsl_complex($c2);
free_gsl_complex($res);

请注意,我必须明确释放我创建的三个数据结构,破坏了最初的 C API 精心设计。

【讨论】:

  • 在您询问后标记@raiph。
  • 我认为标记只适用于那些被 SO 认为直接参与特定帖子的人——Q 或特定 A。对于这个 A,这意味着只有你。因此,虽然 SO 让我知道您对 Liz 的回答的评论,说您提出了一个问题(因此能够对此表示赞赏),但直到现在我才遇到这个答案时,我才看到这个答案。因此,除了提交问题外,还非常喜欢发布此答案。新年快乐。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-07
  • 1970-01-01
相关资源
最近更新 更多