如何使用结构体

fin*_*fin 7 rakudo nativecall raku

我正在尝试从用 C 编写的共享库返回结构。这是简单的代码,用于测试返回结构和简单的 int32,libstruct.c编译者gcc -shared -Wl,-soname,libstruct.so.1 -o libstruct.so.1 libstruct.c

#include <stdint.h>

int32_t newint(int32_t arg) {
    return arg;
}

struct MyStruct {
    int32_t member;
};
struct MyStruct newstruct(int32_t arg) {
    struct MyStruct myStruct;
    myStruct.member = arg;
    return(myStruct);
}
Run Code Online (Sandbox Code Playgroud)

我可以通过简单的 C 程序来使用这个库,该程序usestruct.c由以下代码编译gcc -o usestruct usestruct.c ./libstruct.so.1

#include <stdio.h>
#include <stdint.h>

struct MyStruct {
    int32_t member;
};
extern struct MyStruct newstruct(int32_t);
extern int32_t newint(int32_t);

int main() {
    printf("%d\n", newint(42));
    struct MyStruct myStruct;
    myStruct = newstruct(42);
    printf("%d\n", myStruct.member);
    return 0;
}

Run Code Online (Sandbox Code Playgroud)

我可以使用 启动它LD_LIBRARY_PATH=./ ./usestruct,它工作正常,打印两个值。现在,让我们在 raku 中编写类似的程序usestruct.raku

#!/bin/env raku
use NativeCall;

sub newint(int32) returns int32 is native('./libstruct.so.1') { * }
say newint(42);

class MyStruct is repr('CStruct') {
    has int32 $.member;
}
sub newstruct(int32) returns MyStruct is native('./libstruct.so.1') { * }
say newstruct(42).member;
Run Code Online (Sandbox Code Playgroud)

首先打印42,但随后因分段错误而终止。

在 C 中,这个例子有效,但我不是 C 专家,也许我忘记了一些东西,一些编译选项?或者这是乐道的一个错误?

Mus*_*dın 5

NativeCall 接口要求使用指针进行 C 结构的事务:

CStruct 对象通过引用传递给本机函数,本机函数也必须通过引用返回 CStruct 对象。

但是,您的 C 函数会按值返回一个新结构。然后,我猜想,这被尝试解释为内存地址,因为它需要一个指针,并尝试从野生内存区域读取/写入,因此出现段错误。

您可以将函数指针化为:

struct MyStruct* newstruct(int32_t val) {
    /* dynamically allocating now */
    struct MyStruct *stru = malloc(sizeof *stru);
    stru->member = val;
    return stru;
}
Run Code Online (Sandbox Code Playgroud)

位于#include <stdlib.h>最顶部的malloc. Raku 程序本质上与一些美学上的模数相同:

# prog.raku
use NativeCall;

my constant LIB = "./libstruct.so";

class MyStruct is repr("CStruct") {
    has int32 $.member;
}

# C bridge
sub newint(int32) returns int32 is native(LIB) { * }
sub newstruct(int32) returns MyStruct is native(LIB) { * }

say newint(42);

my $s := newstruct(84);
say $s;
say $s.member;
Run Code Online (Sandbox Code Playgroud)

我们构建 lib 并运行 Raku 程序以获得

$ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
$ raku prog.raku
42
MyStruct.new(member => 84)
84
Run Code Online (Sandbox Code Playgroud)

(擅自将C文件重命名为“mod_struct.c”)

看起来不错。但有一个问题:既然进行了动态分配,就产生了将其返还的责任。我们需要自己用 C 桥 Freer 来做:

当基于 CStruct 的类型用作本机函数的返回类型时,GC 不会为您管理内存。

所以

$ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
$ raku prog.raku
42
MyStruct.new(member => 84)
84
Run Code Online (Sandbox Code Playgroud)

请注意,由于结构本身对其成员没有动态分配(因为它只有一个整数),因此我们没有进行进一步的释放。

现在 Raku 程序需要意识到这一点,并使用它:

/* addendum to mod_struct.c */
void free_struct(struct MyStruct* s) {
    free(s);
}
Run Code Online (Sandbox Code Playgroud)

输出如下

42
MyStruct.new(member => 84)
84
successfully freed struct
Run Code Online (Sandbox Code Playgroud)

手动跟踪 MyStruct 对象并记住在一段时间后释放它们可能很麻烦;那就是写C!在 Raku 级别中,我们已经有一个代表结构的类;然后我们可以向它添加一个DESTROY子方法,只要垃圾收集器认为有必要,它就会释放自己:

# prog.raku
use NativeCall;

my constant LIB = "./libstruct.so";

class MyStruct is repr("CStruct") {
    has int32 $.member;
}

# C bridge
sub newint(int32) returns int32 is native(LIB) { * }
sub newstruct(int32) returns MyStruct is native(LIB) { * }
sub free_struct(MyStruct) is native(LIB) { * };   # <-- new!

say newint(42);

my $s := newstruct(84);
say $s;
say $s.member;

# ... after some time
free_struct($s);
say "successfully freed struct";
Run Code Online (Sandbox Code Playgroud)

通过此添加,不需要手动调用free_struct(事实上,最好不要,因为它可能导致双重释放,这是 C 级别上未定义的行为)。


PS 你的主 C 文件可能会被修改,例如,头文件似乎是有序的,但这超出了范围,或者这只是一个谁知道的说明性示例。无论哪种情况,感谢您提供 MRE 并欢迎访问该网站。

  • 嗨@raiph,感谢您的客气话。我不太清楚为什么会这样。不过我可以推测:)在 C 中通过引用传递结构是更好的做法,尤其是当它们很大时,因为按值传递会复制整个内容,不仅会损失速度,而且函数的堆栈可能会溢出。有了指针,这两个问题都不再是问题。然而,它使结构“is rw”在 C 级别上:) [续] (2认同)
  • @fingolfin 抱歉应该澄清这一点!答案是:不是真的。使用 `:=` 而不是 `=` ,我避免了标量容器围绕 RHS 值(在本例中为 MyStruct 实例)的包装。它(`:=`)将使`$s`直接“查看”RHS值,可以这么说,并防止重新分配给`$s`(例如,`$s = -7`将失败)点;这种强加的不变性,一个遗嘱)。您可以在[此处](https://docs.raku.org/type/Scalar)和[此处](https://docs.raku.org/language/containers)查看有关标量和容器的信息。(另外:您的回答非常好,谢谢!) (2认同)

fin*_*fin 5

除了很棒的@Mustafa 的回答。

我找到了另一种方法来解决我的问题:我们可以在 raku 中分配结构并将其传递给 C 函数。这是一个示例,文件mod_struct.c

#include <stdint.h>

struct MyStruct {
    int32_t member;
};
void writestruct(struct MyStruct *outputStruct, int32_t arg) {
    outputStruct->member = arg;
}
Run Code Online (Sandbox Code Playgroud)

文件usestruct.raku

#!/bin/env raku
use NativeCall;

class MyStruct is repr('CStruct') {
    has int32 $.member;
}
sub writestruct(MyStruct is rw, int32) is native('./libstruct.so') { * }

my $myStruct = MyStruct.new;
writestruct($myStruct, 42);
say $myStruct.member;
Run Code Online (Sandbox Code Playgroud)

编译并运行它:

$ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
$ ./usestruct.raku
42
Run Code Online (Sandbox Code Playgroud)

  • 再次感谢您分享这一点。想要在这里指出一个警告:这种方法适用于非指针涉及成员的结构(例如 int 和 float);然而,如果它有一个字符串(char *)或一个数组,Raku 制作的值可以很好地传递,但如果不进行克隆,它们就不能在 C 级别上安全地使用,因为引用可以在 Raku 级别上被破坏(甚至可能)立即!)然后 C 级将查看内存中的禁区。对于他们来说,需要在C层进行动态分配。 (2认同)
  • 例如,如果您在没有“strdup”的情况下运行[此处](https://docs.raku.org/language/nativecall#Notes_on_memory_management)的示例,但其他一切都相同,有时我确实会得到“foo is str and 123” ... 但!我还得到“foo is &amp;sayM 和 123”,即奇怪的内存读取,甚至有时会出现错误“UTF-8 字符串的格式错误”,这意味着传递的字符串有时已经消失(释放)并且我们面临未定义的情况所见行为。 (2认同)