• Loren Blog

  • Menu
  • 调用 C 的 rand 函数
  • 字符串的传递
    • 使用 Str
    • 使用 CArray
    • 使用 Buf
  • 指针的使用
    • 指针作为参数
      • 使用 trait is rw
      • 使用 CArray
      • 使用 Buf[::T]
    • 使用 Pointer
    • 指针作为对象
    • 函数指针
  • 数组
  • 结构
  • 函数作为参数
  • 全局变量
  • 库路径以及名字
    • 标准库

    Perl6调用C接口

    NativeCall 是 Perl6 内置的一个模块,它可以让你不用编写 C 代码就可以轻松完成 调用现有 C 库接口的任务。

    调用 C 的 rand 函数

    目标

    使用 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 中不被支持

    使用 Str

    目标

    使用 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 指定字符串的编码。

    使用 CArray

    目标

    将字符数组复制到另一个字符数组

    接口

    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 方法解码并显示到终端。

    使用 Buf

    在向大神请教的过程中得知,其实上一小节的例子我们可以不用 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

    使用 CArray

    目标

    使用 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[::T]

    正如 Buf 可以替代 CArray[uint8] 一样,Buf[uint32] 可以替代 CArray[uint32] 作为缓冲区。

    代码2
    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];
    }

    输出结果和上一小节相同。

    使用 Pointer

    目标

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

    指针作为对象

    函数指针

    数组

    结构

    函数作为参数

    全局变量

    库路径以及名字

    标准库

Posts

  • Perl6的符号表
  • Perl6调用C接口
  • 插件生命周期(QtCreator文档翻译)
  • 1的补码与2的补码
  • 归纳操作符(reduce operator)
  • emacs config
  • c++模板的技巧示例
  • 简单括号匹配
  • IA-32算术移位与逻辑移位
Loren Blog

将会写一些关于C/C++/Perl6 的小文章,记录一些知识点。


© 2017 araraloren | araraloren@github.com