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
内存存储的值以及内存的地址。