原生调用接口

原生调用接口

入门指南

能想象出的最简单的 NativeCall 用法应该类似于这样的东西:

use NativeCall;
sub some_argless_function() is native('something') { * }
some_argless_function();

第一行导入了各种 traits 和类型,接下来的一行看起来很像相对普通的 Raku 子例程声明 - 稍微有点变化。我们使用native这个 trait 是为了指定这个 sub 子例程实际上被定义在原生库中。Raku 会给你添加特定平台的扩展名(比如 .so 或者 .dll)还有任何惯常的前缀(例如: ‘lib’)。

当你第一次调用 “some_argless_function” 时,“libsomething” 将会被加载,然后会在 libsomething 库中定位到 “some_argless_function” 函数,接下来将会进行一次调用。之后的调用将会更快,因为符号句柄会被保留。

当然,大部分的函数都会接受参数或者返回值 - 但是你可以做的其他事情只是增加了这个声明Raku sub的简单模式

但是一切你需要做的就是增加这个简单的模式,通过声明一个 Raku 的过程、在符号后面指出你想要调用的名字,并且使用 “native” trait。

改变名字

有时你想要 Raku 子例程的名字和加载库中使用的名字不同,可能这个名字很长, 或者有不同的大小写或者在你想要创建的模块的上下文中, 这个名字很繁琐。

NativeCall 为你提供了一个 symbol trait 以指定库中原生子例程的名字, 这个名字和你的 Raku 子例程名字不同。

module Foo;
use NativeCall;
our sub init() is native('foo') is symbol('FOO_INIT') { * }

libfoo 库里面有一个子例程叫 FOO_INIT,因为我们创建了一个模块叫做 Foo,我们更愿意使用 Foo::init 调用子例程,我们使用 symbol trait 来指定在 libfoo 库名字符号的名字,然后以任何我们想要的方式调用这个子例程(这里是 “init”)。

传递值和返回值

普通的 Raku 签名和 returns trait 的使用是为了传送原生函数期望的参数类型以及返回的东西,下面有个例子:

sub add(int32, int32) returns int32 is native('calculator') { * }

在这里,我们声明该函数接受两个32位整数,返回一个32位整数。你可以在原生类型页面中找到可以传递的其他类型。 请注意,缺少 returns trait 用于指示 void 返回类型。 除指针参数化外,不要在任何地方使用 void 类型。

对于字符串,还有一个额外的 encoded trait,可以提供一些关于如何进行编组的额外提示。

use NativeCall;
sub message_box(Str is encoded('utf8')) is native('gui') { * }

为了指定如何对返回类型进行编组,只需在子例程自身应用这个 trait 即可。

use NativeCall;
sub input_box() returns Str is encoded('utf8') is native('gui') { * }

注意, 可以通过传递 Str 类型对象来传递 NULL 字符串指针; NULL 返回也将由类型对象表示。

如果 C 函数要求字符串的生命周期超过函数调用,则必须手动编码该参数并将其作为 CArray[uint8] 传递:

use NativeCall;
# C prototype is void set_foo(const char *) 
sub set_foo(CArray[uint8]) is native('foo') { * }
# C prototype is void use_foo(void) 
sub use_foo() is native('foo') { * } # will use pointer stored by set_foo() 
 
my $string = "FOO";
# The lifetime of this variable must be equal to the required lifetime of 
# the data passed to the C function. 
my $array = CArray[uint8].new($string.encode.list);
 
set_foo($array);
# ... 
use_foo();
# It's fine if $array goes out of scope starting from here. 

指定原生表示

使用原生函数时,有时需要指定要使用的原生数据结构类型。 is repr 是用于此的术语。

use NativeCall;
 
class timespec is repr('CStruct') {
    has uint32 $.tv_sec;
    has long $.tv_nanosecs;
}
 
sub clock_gettime(uint32 $clock-id, timespec $tspec --> uint32) is native { * };
 
my timespec $this-time .=new;
 
my $result = clock_gettime( 0, $this-time);
 
say "$result, $this-time"; # OUTPUT: «0, timespec<65385480>␤» 

我们调用的原始函数, clock_gettime 使用指向 timespec 结构的指针作为第二个参数。 我们在这里将它声明为一个,但是将其表示指定为 repr('CStruct'), 以指示它对应于 C 数据结构。 当我们创建该类的对象时,我们正在创建 clock_gettime 所期望的指针类型。 这样,数据可以无缝地传输到原生接口和从原生接口传输。

指针的基本使用

当你的原生函数签名需要一个指向某些原生类型(int32uint32等等)的指针时,所有你需要做的就是将参数声明为 is rw

use NativeCall;
# C prototype is void my_version(int *major, int *minor) 
sub my_version(int32 is rw, int32 is rw) is native('foo') { * }
my_version(my int32 $major, my int32 $minor); # Pass a pointer to 

有的时候你需要获取一个从 C 库返回的指针(比如一个库句柄),你不关心它指向什么 - 你只需要保存它就可以了,Pointer 类型就是为此而生的:

use NativeCall;
sub Foo_init() returns Pointer is native("foo") { * }
sub Foo_free(Pointer) is native("foo") { * }

这个可以正常工作,但是你可能想要使用比 Pointer 更好的类型,事实证明,任何具有表示“CPointer”的类都可以担任此角色,这意味着你可以通过编写如下类来暴露工作在句柄上的库:

use NativeCall;
 
class FooHandle is repr('CPointer') {
    # Here are the actual NativeCall functions. 
    sub Foo_init() returns FooHandle is native("foo") { * }
    sub Foo_free(FooHandle) is native("foo") { * }
    sub Foo_query(FooHandle, Str) returns int8 is native("foo") { * }
    sub Foo_close(FooHandle) returns int8 is native("foo") { * }
 
    # Here are the methods we use to expose it to the outside world. 
    method new {
        Foo_init();
    }
 
    method query(Str $stmt) {
        Foo_query(self, $stmt);
    }
 
    method close {
        Foo_close(self);
    }
 
    # Free data when the object is garbage collected. 
    submethod DESTROY {
        Foo_free(self);
    }
}

请注意,CPointer 表示只能保存 C 指针。 这意味着你的类不能有额外的属性。 但是,对于简单的库,这可能是向其暴露面向对象的接口的一种巧妙方式。

当然,你总是可以有一个空类:

class DoorHandle is repr('CPointer') { }

只需像使用 Pointer 一样使用类,但有可能提高类型安全性和更易读的代码。

同样,类型对象用于表示 NULL 指针。

函数指针

C 库可以将指向 C 函数的指针暴露为函数的返回值和结构体的成员,例如 structs 和 unions。

使用定义所需函数参数和返回值的签名调用函数“f”返回的函数指针“$fptr”的示例:

sub f() returns Pointer is native('mylib') { * }

my $fptr    = f();
my $nfptr   = nativecast(:(Str, size_t --> int32), $fptr);

say $nfptr("test", 4);

数组

NativeCall 对数组有一些支持。 它受限于使用机器大小的整数,双精度和字符串,定型的数字类型,指针数组,结构体数组和数组的数组。

Raku 数组支持懒惰,在内存中以与 C 数组完全不同的方式布局。 因此,NativeCall 库提供了更原始的 CArray 类型,如果使用 C 数组,则必须使用该类型。

这是传递 C 数组的示例。

sub RenderBarChart(Str, int32, CArray[Str], CArray[num64]) is native("chart") { * }
my @titles := CArray[Str].new;
@titles[0]  = 'Me';
@titles[1]  = 'You';
@titles[2]  = 'Hagrid';
my @values := CArray[num64].new;
@values[0]  = 59.5e0;
@values[1]  = 61.2e0;
@values[2]  = 180.7e0;
RenderBarChart('Weights (kg)', 3, @titles, @values);

注意我们对 @titles 使用了绑定,而不是赋值,如果你使用赋值,则会把值放进 Raku 数组,然后它就不会工作了。如果这令你抓狂,忘记你所知道的关于 @ 符号的事情,使用 NativeCall 的时候直接使用 $ 吧。

use NativeCall;
my $titles = CArray[Str].new;
$titles[0] = 'Me';
$titles[1] = 'You';
$titles[2] = 'Hagrid';

获取数组的返回值也是一样的。

某些库 API 可能会将数组作为缓冲区,将由 C 函数填充,例如,返回填充的实际项数:

use NativeCall;
sub get_n_ints(CArray[int32], int32) returns int32 is native('ints') { * }

在这些情况下,重要的是 CArray 在将其传递给原生子例程之前至少具有要填充的元素的数量,否则 C 函数可能会遍历 Perl 的内存,从而可能导致不可预测的行为:

my $number_of_ints = 10;
my $ints = CArray[int32].allocate($number_of_ints); # instantiates an array with 10 elements 
my $n = get_n_ints($ints, $number_of_ints);

注意:allocate 是在 Rakudo 2018.05 中引入的。 在此之前,你必须使用此机制将数组扩展为许多元素:

my $ints = CArray[int32].new;
my $number_of_ints = 10;
$ints[$number_of_ints - 1] = 0; # extend the array to 10 items 

数组的内存管理很重要。 当你自己创建一个数组时,可以根据需要为其添加元素,并根据需要为你进行扩展。 但是,这可能会导致元素在内存中移动(但是,对现有元素的赋值永远不会导致这种情况)。 这意味着如果在将数组传递给 C 库之后将数组旋转,你最好知道自己在做什么。

相比之下,当 C 库向你返回一个数组时,内存不能由 NativeCall 管理,并且它不知道数组的结束位置。 据推测,库 API 中的某些东西告诉你这一点(例如,你知道当你看到一个 null 元素时,你应该不再读取)。 请注意,NativeCall 在这里无法为您提供任何保护 - 一旦做错了,你将遇到 segfault 错误或导致内存损坏。 这不是 NativeCall 的缺点,它是原生世界的工作方式。害怕吗? 还在这里,拥抱一下。 祝好运!

CArray 方法

除了每个 Raku 实例上可用的常用方法之外,CArray 还提供了以下方法,可以从 Raku 的角度与它进行交互:

  • elems 提供数组中的元素数量;

  • AT-POS 在给定位置提供特定元素(从零开始);

  • list 提供了从原生数组迭代器构建它的数组中的元素列表

例如,请考虑以下简单的代码:

use NativeCall;
 
my $native-array = CArray[int32].new( 1, 2, 3, 4, 5 );
say 'Number of elements: ' ~ $native-array.elems;
 
# walk the array 
for $native-array.list -> $elem {
    say "Current element is: $elem";
}
 
# get every element by its index-based position 
for 0..$native-array.elems - 1 -> $position {
    say "Element at position $position is "
          ~ $native-array.AT-POS( $position );
}

产生以下输出:

Number of elements: 5
Current element is: 1
Current element is: 2
Current element is: 3
Current element is: 4
Current element is: 5
Element at position 0 is 1
Element at position 1 is 2
Element at position 2 is 3
Element at position 3 is 4
Element at position 4 is 5

结构体

由于表示多态性,可以声明一个看起来很正常的 Raku 类,实际上,C 编译器将它们放置在类似的结构体定义中以相同的方式存储其属性。 所需要的只是快速使用“repr” trait:

class Point is repr('CStruct') {
    has num64 $.x;
    has num64 $.y;
}

声明的属性只能是 NativeCall 已知的可以转换成结构体字段的类型,目前,结构体中可以包含机器大小的整数,doubles,strings 以及其它 NativeCall 对象(CArrays,还有 CPointer 以及 CStruct reprs)。除此之外,你可以做一些跟类一样的常用的设置,你甚至可以让某些属性来自于角色或者从其它的类继承。当然,方法也完全没有问题,疯狂!

CStruct 对象以引用的形式传递到原生函数,并且原生函数必须返回 CStruct 对象的引用,对于这些引用的内存管理规则跟数组的内存管理规则很像,尽管更简单,因为结构体的大小是不变的。当你创建一个结构体,内存也一并为你分配好,当指向 CStruct 实例的变量的生命期结束,GC 会负责释放内存。当基于 CStruct 的类型作为原生函数的返回类型时,GC 并不帮你管理它的内存。

NativeCall 目前并不把对象成员放到容器里面,所以不能对对象进行赋(使用 =)新值。 相反,你必须将新值绑定到私有成员上:

class MyStruct is repr('CStruct') {
    has CArray[num64] $!arr;
    has Str $!str;
    has Point $!point; # Point is a user-defined class 
 
    submethod TWEAK {
        my $arr := CArray[num64].new;
        $arr[0] = 0.9e0;
        $arr[1] = 0.2e0;
        $!arr := $arr;
        $!str := 'Raku is fun';
        $!point := Point.new;
    }
}

正如你预测的那样,空指针由结构体类型的类型对象表示的。

CUnions

同样地,我们可以声明一个 Raku 类,它的属性拥有和 C 编译器中联合体(union)的相同的内存布局,这可以使用 CUnion 表示:

use NativeCall;
 
class MyUnion is repr('CUnion') {
    has int32 $.flags32;
    has int64 $.flags64;
}
 
say nativesizeof(MyUnion.new);  # 8, ie. max(sizeof(MyUnion.flags32), sizeof(MyUnion.flags64)) 

嵌套的 CStructs 和 CUnions

反过来, CStructs 和 CUnions 可以被周围的 CStruct 和 CUnion 引用,或者嵌入到其他的 CStructs 和 CUnions 里面,如果是引用我们则像往常一样使用 has 来声明,如果是嵌入则使用 HAS 代替:

class MyStruct is repr('CStruct') {
    has Point $.point;  # referenced 
    has int32 $.flags;
}
 
say nativesizeof(MyStruct.new);  # 16, ie. sizeof(struct Point *) + sizeof(int32_t) 
 
class MyStruct2 is repr('CStruct') {
    HAS Point $.point;  # embedded 
    has int32 $.flags;
}
 
say nativesizeof(MyStruct2.new);  # 24, ie. sizeof(struct Point) + sizeof(int32_t) 

注意内存管理

分配结构体以用作结构体时,请确保在 C 函数中分配自己的内存。 如果要将结构体传递给需要提前分配的 Str/char* 的C函数,请确保在将结构体传递给函数之前为 Str 类型的变量分配容器。

在你的 Raku 代码中…

class AStringAndAnInt is repr("CStruct") {
  has Str $.a_string;
  has int32 $.an_int32;
 
  sub init_struct(AStringAndAnInt is rw, Str, int32) is native('simple-struct') { * }
 
  submethod BUILD(:$a_string, :$an_int) {
    init_struct(self, $a_string, $an_int);
  }
}

在此代码中,我们首先设置我们的成员 $.a_string$.an_int32。 之后,我们声明 init_struct() 函数以使 init() 方法包装; 然后从 BUILD 调用此函数以在返回创建的对象之前有效地分配值。

在你的 C 代码中 …

typedef struct a_string_and_an_int32_t_ {
  char *a_string;
  int32_t an_int32;
} a_string_and_an_int32_t;

这是结构体。 注意我们在那里有怎么得到一个 char *

void init_struct(a_string_and_an_int32_t *target, char *str, int32_t int32) {
  target->an_int32 = int32;
  target->a_string = strdup(str);
 
  return;
}

在这个函数中,我们通过按值分配整数并通过引用传递字符串来初始化 C 结构体。 该函数在复制字符串时将 <point * a_string> 指向的内存分配到结构中。 (注意,你还必须管理内存的释放以避免内存泄漏。)

# A long time ago in a galaxy far, far away... 
my $foo = AStringAndAnInt.new(a_string => "str", an_int => 123);
say "foo is {$foo.a_string} and {$foo.an_int32}";
# OUTPUT: «foo is str and 123␤» 

类型指针

Pointer 作为参数传递时可以类型化你的 Pointer。这不但对原生类型可用,同样适用于 CArray 以及 CStruct 定义类型,NativeCall 将不会显式为他们分配内存,即使在它们身上调用 new 方法也不会。这适用于那种 C 函数返回指针或者 CStruct 中嵌入的指针情况。

use NativeCall;
sub strdup(Str $s --> Pointer[Str]) is native {*}
my Pointer[Str] $p = strdup("Success!");
say $p.deref;

原生函数返回指向元素的数组的指针是很常见的。 可以将类型化指针解引用为数组以获取单个元素。

my $n = 5;
# returns a pointer to an array of length $n 
my Pointer[Point] $plot = some_other_c_routine($n);
# display the 5 elements in the array 
for 1 .. $n -> $i {
    my $x = $plot[$i - 1].x;
    my $y = $plot[$i - 1].y;
    say "$i: ($x, $y)";
}

指针也可以更新以引用数组中的连续元素:

my Pointer[Point] $elem = $plot;
# show differences between successive points 
for 1 ..^ $n {
    my Point $lo = $elem.deref;
    ++$elem; # equivalent to $elem = $elem.add(1); 
    my Point $hi = (++$elem).deref;
    my $dx = $hi.x = $lo.x;
    my $dy = $hi.y = $lo.y;
    say "$_: delta ($dx, $dy)";
}

通过声明 Pointer[void] 也可以使用 Void 指针。 有关该主题的更多信息,请参阅原生类型文档

字符串

显式内存管理

Buffers and Blobs

函数参数

NativeCall 也支持把函数作为原生函数的参数,一个常用的情况就是事件驱动模型中,使用函数指针作为回调。当通过 NativeCall 绑定了这些函数,只需要提供对等的 signature 作为函数参数的约束。

void SetCallBack(int (*callback)(char const *))

my sub SetCallBack(&callback(Str –> int32)) is native(‘mylib’) { * } 注意:原生代码负责传递给 Raku 回调的值的内存管理,换句话说,NativeCall 将不会释放传递给回调的字符串占用的内存。

库路径以及名字

native trait 接受库的名字或者全路径:

constant LIBMYSQL = ‘mysqlclient’; constant LIBFOO = ‘/usr/lib/libfoo.so.1’;

sub mysql_affectied_rows( .. ) returns int32 is native(LIBMYSQL); sub bar is native(LIBFOO); 你也可以使用相对路径比如’./foo’,NativeCall 将会自动根据不同的平台添加对应的扩展名。 注意:native trait 和 constant 都是在编译期求值的,constant类型的变量不要依赖动态变量,比如:

constant LIBMYSQL = %*ENV<P6LIB_MYSQLCLIENT> || ‘mysqlclient’; 这将在编译期保持给定的值,在一个模块预编译时,LIBMYSQL将会始终保持那个值。

ABI/API版本

假设你写的原生库为native(‘foo’), 在类Unix系统下,NativeCall 将会搜索’libfoo.so’(对于OS X是libfoo.dynlib,win32是foo.dll)。在大多数的现代系统上,将会需要你或者模块的使用者安装开发环境包,因为它们总是建议支持动态库的API/ABI的版本控制,所以’libfoo.so'大多数是一个符号链接,并且只被开发包提供。

sub foo is native(‘foo’, v1); # 将会查找并加载 libfoo.so.1 sub foo is native(‘foo’, v1.2.3); # 将会查找并加载 libfoo.so.1.2.3

my List $lib = (‘foo’, ‘v1’); sub foo is native($lib);

例程

native trait 也可以接受一个Callable作为参数,允许你使用自己的方式指定将会被加载的库文件:

sub foo is native(sub { ‘libfoo.so.42’ } ); 这个函数只会在第一个调用者访问的时候调用。

调用标准库

如果你想调用一个已经被加载的,或者是标准库或者来自你自己的程序的 C 函数,你可以将 Str 类型对象作为参数传递给is native,这将会是is native(Str)。 比如说,在类UNIX操作系统下,你可以使用下面的代码打印当前用户的home目录:

use NativeCall; my class PwStruct is repr(‘CStruct’) { has Str $.pw_name; has Str $.pw_passwd; has uint32 $.pw_uid; has uint32 $.pw_gid; has Str $.pw_gecos; has Str $.pw_dir; has Str $.pw_shell; }

sub getuid() returns uint32 is native(Str) { * } sub getpwuid(uint32 $uid) returns PwStruct is native(Str) { * }

say getpwuid(getuid()); 不过,使用$*HOME更方便一些 :-)

导出的变量

一个库导出的变量 – 也被叫做“全局(global)”或者 “外部(extern)”变量 – 可以使用cglobal访问。比如:

my $var := cglobal(‘libc.so.6’, ‘error’, int32); 这将会为$var绑定一个新的Proxy对象,并且将对它的访问重定向到被“libc.so.6”导出的叫做errno的整数变量。

对C++的支持

NativeCall 也支持使用来自 c++ 的类以及方法,就像这个例子展示的那样(还有相关的 c++ 文件),注意现阶段还不像 C 一样支持测试和开发。

Helper 函数

sub nativecast

sub cglobal

sub nativesizeof

sub explicitly-manage

例子

一些具体示例,以及在特定平台上使用上述示例的说明。

PostgreSQL

DBIish 中的 PostgreSQL 示例使用 NativeCall 库,并且原生使用 Windows 中的原生 _putenv 函数调用。

MySQL

注意:请记住,自 Stretch 版本以来,Debian 已经将 MySQL 替换为 MariaDB,因此如果要安装 MySQL,请使用 MySQL APT 存储库而不是默认存储库。

要在 DBIish 中使用 MySQL 示例,您需要在本地安装 MySQL 服务器; 在Debian-esque 系统上,它可以安装如下:

wget https://dev.mysql.com/get/mysql-apt-config_0.8.10-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.10-1_all.deb # Don't forget to select 5.6.x 
sudo apt-get update
sudo apt-get install mysql-community-server -y
sudo apt-get install libmysqlclient18 -y

在尝试示例之前,请按照这些方法准备系统:

$ mysql -u root -p
SET PASSWORD = PASSWORD('sa');
DROP DATABASE test;
CREATE DATABASE test;

Microsoft Windows

这是一个 Windows API 调用的例子:

use NativeCall;
 
sub MessageBoxA(int32, Str, Str, int32)
    returns int32
    is native('user32')
    { * }
 
MessageBoxA(0, "We have NativeCall", "ohai", 64);

关于调用 C 函数的简明指南

这是一个调用标准函数并在 Raku 程序中使用返回信息的示例。

getaddrinfo 是 POSIX 标准函数,用于获取有关网络节点的网络信息,例如 google.com。 这是一个有趣的功能,因为它说明了 NativeCall 的许多元素。

Linux 手册提供了有关 C 可调用函数的以下信息:

int getaddrinfo(const char *node, const char *service,
       const struct addrinfo *hints,
       struct addrinfo **res);

该函数返回响应码 0 = 错误,1 = 成功。 数据是从 addrinfo 元素的链表中提取的,第一个元素由 res 指向。

从 NativeCall 类型表我们知道 intint32。 我们也知道 char * 是 C Str 的形式 C 之一,它简单地映射到 Str。 但是 addrinfo 是一个结构体,这意味着我们需要编写自己的 Type 类。 但是,函数声明很简单:

sub getaddrinfo( Str $node, Str $service, Addrinfo $hints, Pointer $res is rw )
    returns int32
    is native
    { * }

请注意,$res 将由函数写入,因此必须将其标记为 rw。 由于库是标准 POSIX,因此库名称可以是 Type 定义或 null。

我们现在必须处理结构体 Addrinfo。 Linux 手册提供了以下信息:

struct addrinfo {
               int              ai_flags;
               int              ai_family;
               int              ai_socktype;
               int              ai_protocol;
               socklen_t        ai_addrlen;
               struct sockaddr *ai_addr;
               char            *ai_canonname;
               struct addrinfo *ai_next;
           };

intchar * 部分很简单。 一些研究表明 socklen_t 可以依赖于架构,但是是一个至少32位的无符号整数。 所以 socklen_t 可以映射到 uint32 类型。

复杂的是 sockaddr,它取决于 ai_socktype 是否是未定义的,INET 还是 INET6(标准的v4 IP 地址或 v6 地址)。

所以我们创建一个 Raku 类来映射到 C struct addrinfo; 当我们在它的时候,我们还为 SockAddr 创建了另一个类。

class SockAddr is repr('CStruct') {
    has int32    $.sa_family;
    has Str      $.sa_data;
}
 
class Addrinfo is repr('CStruct') {
    has int32     $.ai_flags;
    has int32     $.ai_family;
    has int32     $.ai_socktype;
    has int32     $.ai_protocol;
    has int32     $.ai_addrlen;
    has SockAddr  $.ai_addr       is rw;
    has Str       $.ai_cannonname is rw;
    has Addrinfo  $.ai_next       is rw;
 
}

最后三个属性的 is rw 反映了这些在 C 中被定义为指针。

映射到 C Struct 的重要一点是类的状态部分的结构,即属性。 但是,类可以有方法,而 NativeCall 不会“触摸”它们以映射到C.这意味着我们可以向类添加额外的方法以更易读的方式解包属性,例如,

method flags {
    do for AddrInfo-Flags.enums { .key if $!ai_flags +& .value }
}

通过定义适当的 enumflags 将返回一串键而不是一个打包的整数。

sockaddr 结构中最有用的信息是节点的地址,它取决于 Socket 的族。 因此,我们可以将方法地址添加到 Raku 类中,该类根据族来解释地址。

为了获得人类可读的 IP 地址,有一个 C 函数 inet_ntop,它给出一个带有 addrinfo 的缓冲区的 char *

将所有这些组合在一起会产生以下程序:

#!/usr/bin/env raku 
 
use v6;
use NativeCall;
 
constant \INET_ADDRSTRLEN = 16;
constant \INET6_ADDRSTRLEN = 46;
 
enum AddrInfo-Family (
    AF_UNSPEC                   => 0;
    AF_INET                     => 2;
    AF_INET6                    => 10;
);
 
enum AddrInfo-Socktype (
    SOCK_STREAM                 => 1;
    SOCK_DGRAM                  => 2;
    SOCK_RAW                    => 3;
    SOCK_RDM                    => 4;
    SOCK_SEQPACKET              => 5;
    SOCK_DCCP                   => 6;
    SOCK_PACKET                 => 10;
);
 
enum AddrInfo-Flags (
    AI_PASSIVE                  => 0x0001;
    AI_CANONNAME                => 0x0002;
    AI_NUMERICHOST              => 0x0004;
    AI_V4MAPPED                 => 0x0008;
    AI_ALL                      => 0x0010;
    AI_ADDRCONFIG               => 0x0020;
    AI_IDN                      => 0x0040;
    AI_CANONIDN                 => 0x0080;
    AI_IDN_ALLOW_UNASSIGNED     => 0x0100;
    AI_IDN_USE_STD3_ASCII_RULES => 0x0200;
    AI_NUMERICSERV              => 0x0400;
);
 
sub inet_ntop(int32, Pointer, Blob, int32 --> Str)
    is native {}
 
class SockAddr is repr('CStruct') {
    has uint16 $.sa_family;
}
 
class SockAddr-in is repr('CStruct') {
    has int16 $.sin_family;
    has uint16 $.sin_port;
    has uint32 $.sin_addr;
 
    method address {
        my $buf = buf8.allocate(INET_ADDRSTRLEN);
        inet_ntop(AF_INET, Pointer.new(nativecast(Pointer,self)+4),
            $buf, INET_ADDRSTRLEN)
    }
}
 
class SockAddr-in6 is repr('CStruct') {
    has uint16 $.sin6_family;
    has uint16 $.sin6_port;
    has uint32 $.sin6_flowinfo;
    has uint64 $.sin6_addr0;
    has uint64 $.sin6_addr1;
    has uint32 $.sin6_scope_id;
 
    method address {
        my $buf = buf8.allocate(INET6_ADDRSTRLEN);
        inet_ntop(AF_INET6, Pointer.new(nativecast(Pointer,self)+8),
            $buf, INET6_ADDRSTRLEN)
    }
}
 
class Addrinfo is repr('CStruct') {
    has int32 $.ai_flags;
    has int32 $.ai_family;
    has int32 $.ai_socktype;
    has int32 $.ai_protocol;
    has uint32 $.ai_addrNativeCalllen;
    has SockAddr $.ai_addr is rw;
    has Str $.ai_cannonname is rw;
    has Addrinfo $.ai_next is rw;
 
    method flags {
        do for AddrInfo-Flags.enums { .key if $!ai_flags +& .value }
    }
 
    method family {
        AddrInfo-Family($!ai_family)
    }
 
    method socktype {
        AddrInfo-Socktype($!ai_socktype)
    }
 
    method address {
        given $.family {
            when AF_INET {
                nativecast(SockAddr-in, $!ai_addr).address
            }
            when AF_INET6 {
                nativecast(SockAddr-in6, $!ai_addr).address
            }
        }
    }
}
 
sub getaddrinfo(Str $node, Str $service, Addrinfo $hints,
                Pointer $res is rw --> int32)
    is native {};
 
sub freeaddrinfo(Pointer)
    is native {}
 
sub MAIN() {
    my Addrinfo $hint .= new(:ai_flags(AI_CANONNAME));
    my Pointer $res .= new;
    my $rv = getaddrinfo("google.com", Str, $hint, $res);
    say "return val: $rv";
    if ( ! $rv ) {
        my $addr = nativecast(Addrinfo, $res);
        while $addr {
            with $addr {
                say "Name: ", $_ with .ai_cannonname;
                say .family, ' ', .socktype;
                say .address;
                $addr = .ai_next;
            }
        }
    }
    freeaddrinfo($res);
}

这产生如下输出:

return val: 0
Name: google.com
AF_INET SOCK_STREAM
216.58.219.206
AF_INET SOCK_DGRAM
216.58.219.206
AF_INET SOCK_RAW
216.58.219.206
AF_INET6 SOCK_STREAM
2607:f8b0:4006:800::200e
AF_INET6 SOCK_DGRAM
2607:f8b0:4006:800::200e
AF_INET6 SOCK_RAW
2607:f8b0:4006:800::200e