1
2
3
use NativeCall;
sub crand() of int32 is symbol("rand") is native(Str) { * }
say crand();
NativeCall 是 Perl6 内置的一个模块,它可以让你不用编写 C 代码就可以轻松完成
调用现有 C 库接口的任务。
使用 rand 函数获取随机数。
int rand(void);
它没有参数,返回一个随机的整数。
1
2
3
use NativeCall;
sub crand() of int32 is symbol("rand") is native(Str) { * }
say crand();
第1行告诉 Perl6 我们需要引用模块 NativeCall。
然后接下来是一个 sub(我们使用 trait is native 来表明这是一个原生接口) 的声明,
这告诉 Perl6 我们想要使用的 C 接口的样子。
在这个例子中就是一个返回值为 int32(of int32,
详见 类型对应表),
参数为 空((),参数表表示了调用 C 接口的参数),
库 libc(is native(Str),这里因为 libc 的原因不可以写成 is native('libc'),
只能留空或者写成 is native('libc.so.6'))中名字为 rand(is symbol("rand"),没有此 trait 的时候
Perl6 会查找跟 sub 名字相同的函数)的 C 函数。
crand 即我们在 Perl6 可以访问的名字,接着第3行我们调用 say 输出 crand 的返回值。
这样一次简单的调用 C 接口的旅程就完成了。
最主要的几个关键点就是函数的 名字、返回值、参数,以及库的名字。
字符串的传递可以使用 Str、CArray[uint8] 以及 Buf 来实现,
Str 用来表示 const char* 参数,函数内部不会对字符串的内容作出更改;
其他情况可以使用 CArray[uint8] 或者 Buf。
Buf 并不是 native 类型,目前在 nativecast 中不被支持
|
使用 puts 输出 Perl6 的 Str 类型的对象。
int puts(const char* s)
puts 将接受的字符串输出到标准输出,返回成功输出的字符个数。
1
2
3
4
use NativeCall;
sub puts(Str) of int32 is native('libc.so.6') { * }
my Str $want-display="Hello, I'm from Perl6";
say puts($want-display);
我们使用 Str 来表示 puts 接受一个字符串参数,
尾部的 trait is native('libc.so.6') 表明我们的函数存在于 C 库中,这与第一个例子写法有点不同,
之后我们没有使用 is symbol,Perl6 很聪明,它会自动查找和 sub 名字一样的函数。
接下来,我们定义了一个字符串 $want-display,它一共 22 个字符。
调用 puts 输出 $want-display,屏幕将会显示
Hello, I'm from Perl6 22
22 是 puts 的返回值,它返回成功输出的字符个数。
此外,Str 还支持 is encoded 指定字符串的编码。
|
将字符数组复制到另一个字符数组
char* strncpy(char* dest, const char* str, size_t n);
将 str 的前 n 个字符追加到 dest 字符串后面,返回指向 dest 的指针。
如果 src 的长度小于 n,则额外的 '\0' 会被追加直到 n 个字符。
1
2
3
4
5
6
use NativeCall;
sub strncpy(CArray[uint8], Str, size_t) of Str is native(Str) { * }
my @str := CArray[uint8].new(0 xx 64);
strncpy(@str, "we are friend!", 32);
my $buf = Buf.new(@str[^32]);
say $buf.decode("utf8");
我们使用 CArray[uin8] 来表示第一个参数 char*,这样在函数调用返回时,@str 依然可以访问。
根据 jnthn(@IRC) 大神所说,如果使用 Str,参数传递的时候将会是:
产生临时的缓冲区,之后把编码好的数据放入,然后传递给要调用的函数,这样就无法将改变的数据带回了。
由于第二个参数是只读的,我们使用 Str 来表示,剩下的就不再赘述了。
之后我们新建一个类型为 CArray[uint8] 的数组,长度为 64,注意我们这里使用了 binding 操作符
将数组绑定到 @str 上。
接下来时调用函数,strncpy 就会将字符串复制到我们传递的缓冲区中,我们使用 Buf 将复制到 @str
的数据取出,调用 decode 方法解码并显示到终端。
在向大神请教的过程中得知,其实上一小节的例子我们可以不用 CArray[uin8],可以用 Buf 作为缓冲区。
1
2
3
4
5
use NativeCall;
sub strncpy(Buf, Str, size_t) of Str is native(Str) { * }
my $str = Buf.new(0 xx 64);
strncpy($str, "we are friend!", 32);
say $str.decode("utf8");
可以看到使用 Buf 或者 Blob 比较简单,示例的输出结果不变。
当需要传递指针时,Perl6 提供了 Pointer 以及 trait is rw。
trait is rw使用 time 函数获取当前的 POSIX 时间。
一般来说 time_t 就是 long,所以在这里我们使用 long 来演示。
|
time_t time(time_t *tloc);
1
2
3
4
5
6
use NativeCall;
sub time(long is rw) of long is native(Str) { * }
say time(my long $null);
my long $l .= new;
say time($l);
say $l;
time 的参数我们使用 long is rw 来表示 C 接口的参数是 long 的指针,并且
我们使用类型对象 $null 来表示 NULL,也可以使用 new 创建 long 类型实例,
传递给 time 函数。
因为这里执行的时间很短,输出大致像这样:
1501953598 1501953598 1501953598
使用 rand_r 函数获取一个随机值,它接受一个 int* 的参数,并且它会在 seedp 指向的
内存里存储随机的状态,即 rand_r 会修改参数指向的值,我们需要保证传递的参数的生命周期在函数
调用结束后依然有效,在这里我们使用 CArray。
int rand_r(unsigned int *seedp);
1
2
3
4
5
6
7
use NativeCall;
sub rand_r(CArray[uint32]) of int32 is native(Str) { * }
my @u := CArray[uint32].new;
@u[0] = 55;
for ^5 {
say "CURRENT => ", @u[0], " CALL-RET[", rand_r(@u), "] AFTER => ", @u[0];
}
这里使用的参数类型为 CArray[uint32],CArray 是一个支持类型参数的 role。
定义变量 @u 并调用 rand_r 之后输出大约是这样:
CURRENT => 55 CALL-RET[431173127] AFTER => 1107800770 CURRENT => 1107800770 CALL-RET[480593526] AFTER => 1182139145 CURRENT => 1182139145 CALL-RET[1915167251] AFTER => 504621372 CURRENT => 504621372 CALL-RET[2132881580] AFTER => 1823219531 CURRENT => 1823219531 CALL-RET[1564670800] AFTER => -1420796954
正如 Buf 可以替代 CArray[uint8] 一样,Buf[uint32] 可以替代 CArray[uint32] 作为缓冲区。
1
2
3
4
5
6
use NativeCall;
sub rand_r(Buf[uint32]) of int32 is native(Str) { * }
my $buf = Buf[uint32].new(<55>);
for ^5 {
say "CURRENT => ", $buf[0], " CALL-RET[", rand_r($buf), "] AFTER => ", $buf[0];
}
输出结果和上一小节相同。
使用 C 语言的内存管理函数来完成内存的申请与释放。
void *malloc(size_t size);
void free(void *ptr);
int printf(const char*, …);
1
2
3
4
5
6
7
8
9
10
11
use NativeCall;
sub malloc(size_t) of Pointer is native(Str) { * }
sub free(Pointer) is native(Str) { * }
sub printf(Str, int32, int32, CArray[int32], Pointer) of int32 is native(Str) { * }
my Pointer $ptr = malloc(4);
my CArray[int32] $buf = nativecast(CArray[int32], $ptr);
my Pointer[int32] $pint = nativecast(Pointer[int32], $ptr);
$buf[0] = int32.new(64);
my Str $format = 'value = %d <-> %d @ %p <-> %p' ~ "\n";
printf($format, $buf[0], $pint.deref, $buf, $ptr);
free($ptr);
这里 free 的参数以及 printf 的第五个参数使用了 Pointer 表示 void* 类型的指针。
printf 是一个可变参数的函数(目前没看到 NativeCall 如何处理可变参数),
这里显式的设定了它的参数。
我们使用 malloc 分配了一块 int32 大小的内存,malloc 的返回值 - 内存地址 - 存储
在 $ptr 里,之后使用 nativecast 将 $ptr 分别转换为 CArray[int32] 以及 Pointer[int32]
类型,然后存储了一个 int32 的值 64,
之后打印出 $buf、 $ptr、 $pint 内存存储的值以及内存的地址。