Perl 6 document(2016-03-25)mysql
翻译c++
本翻译意在帮助理解原文,因我的水平有限,不免会有翻译不当,若是疑惑请在评论中指出。git
原文github
最简单的能想象出的 NativeCall 应该相似于这样:sql
use NativeCall; sub some_argless_function() is native('something') { * } some_argless_function();
代码的第一行导入了各类 traits 和类型,接下来的一行很像相对普通的 Perl 6 过程的声明 - 稍微有点变化。咱们使用“native”这个 trait 为了指定这个 sub 实际上定义在原生库中。特定平台的扩展名(好比.so或者.dll)将自动添加。
当你第一次调用“some_argless_function”函数时,“libsomething”将会被加载,而后“some_argless_function”将会被定位,接下来将会进行一次调用。以后的调用将会更快,由于符号句柄会被保留。
固然,大部分的函数都会接受参数或者返回值 - 可是一切你须要作的就是增长这个简单的模式,经过声明一个 Perl 6 的过程、在符号后面指出你想要调用的名字,而且使用“native”trait 标记。shell
有时你想要你的 Perl 子例程使用一个与加载的库中本来不一样的名字,这个名字可能长一点或者有不一样的大小写或者在你想要建立的模块的上下文中的其余繁琐的名字。
NativeCall 支持一个“symbol”trait,这使你能够指定你的 Perl 子例程使用与库中原生例程不一样的名字。windows
module Foo; use NativeCall; our sub init() is native('foo') is symbol('FOO_INIT') { * }
在“libfoo”库里面有一个例程叫“FOO_INIT”,由于咱们建立了一个模块叫作Foo,咱们宁愿使用Foo::init
调用子例程,咱们使用“symbol”特性指定在“libfoo”库名字符号的名字,以任何咱们想要的方式调用这个子例程(这种状况下是“init”)。api
普通的 Perl 6 signatures 和returns
trait 的使用是为了传达原生函数指望的参数类型以及返回的类型,下面举个例子:数组
sub add(int32, int32) returns int32 is native('calculator') { * }
这里,咱们声明这个函数接受两个32位整型的参数,返回一个32位整数。下面是一些其它你可能须要传递的参数类型(之后会慢慢增长)。安全
int8 (c中的char) int16 (c中的short) int32 (c中的int) int64 (明确的64位int,就像C99中的int64_t) long (32位或者64位,依赖与本地long) longlong (至少64位,跟本地的long long同样) num32 (c中的float) num64 (c中的double) Str (c中的string,即char *) CArray[int32] (c中的int*,一个整型的数组) Pointer[void] (c中的void*,能够指向任何其它类型) bool (C99中的bool) size_t (C中的size_t)
不要使用int
、num
之类的 Perl 6 原生类型,这可能在32位和64位系统之间形成不可预料的边界问题(好比 Perl 6 的 int 能够是8个字节,可是 C 的 int 只有4个字节)。
注意,没有使用returns
trait 表示函数返回空类型(void),除了 Pointer 参数化,不要使用'void'类型。
对于字符串,有一个额外的“encoded”trait 给出额外的提示如何进行编组。
sub message_box(Str is encoded('utf8')) is native('gui') { * }
为了指定如何对返回类型进行编组,只须要在 routine 自己加上这个特性便可。
sub input_box() returns Str is encoded('utf8') is native('gui') { * }
注意,Str 类型的对象可能接受一个空的字符串了;对应的,类型对象也可能返回一个空的字符串。
若是 C 函数须要彻底生命期的字符串(即对于函数来说,字符串一直有效)执行函数调用,参数必须是通过手动编码,而且以CArray[uint8]
来传递:
# C 原型是 void set_foo(const char * foo) sub set_foo(Str) is native('foo') { * } # C 原型是 void use_foo(void) sub use_foo() is native('foo') { * } # 将会使用set_foo函数设置的指针 my $string = "FOO"; # 变量的生命期必须与 C 函数期待的生命期相等 my $array = CArray[uint8].new($string.encode.list); set_foo($array); # ... 其它代码 use_foo(); # 使用完毕以后,$arrray就能够安全的销毁了
当你的原生函数 signature 须要一个指向原生类型(int32
、uint32
等等)的指针时,全部你须要作的就是为参数加上is rw
:
# C 原型是 void my_version(int *major, int *minor) sub my_version(int32 is rw, int32 is rw) is native('foo') { * } my int32 ($major, $minor); ... my_version($major, $minor); //传递一个指针
有的时候你须要获取一个从C库返回的指针(好比一个库句柄),你不须要这个它指向什么 - 你只须要保存它就能够了,Pointer 类型就是为此而生的:
sub Foo_init() returns Pointer is native('foo') { * } sub Foo_free(Pointer) is native('foo') { * }
这个能够正常工做,可是你可能梦想可使用某种比 Pointer 更好的类型一块儿工做,实际上任何声明为“CPointer”的类均可以充当这个角色,这意味着你能够经过编写一个工做在句柄上的类暴露库,就像这样:
class FooHandle is repr('CPointer') { has $!initialized; # 这里是一些实际的*原生调用*函数 sub Foo_init() returns FooHandle is native('foo') { * } sub Foo_free(FooHandle) is native('foo') { * } # 这里是咱们要使用的方法,它们可让类暴露本身给外界 method new() { Foo_init(); $!initialized = True; } method free() { if $!initialized { Foo_free(self); $!initialized = False; # 防止double free错误 } } # 当对象被垃圾收集时释放数据 method DESTROY() { self.free; } }
注意 CPointer 声明的类除了保持一个 C 指针,并不能作更多的事情,这意味着你的类不能有额外的 attributes 。然而,对于简单的库来讲这是一个用来暴露面向对象接口的灵活的方式。
固然,你老是能够定义一个空的类:
class DoorHandle is repr('CPointer') { }
而后把这个类用在你须要使用指针的地方,为了更好的类型安全以及代码的可读性。再一次提示,使用类型对象来表示空。
C 库能够暴露指向 C 函数的指针,做为函数的返回值或者结构体的成员(好比 structs、unions)。
例子中调用了一个由函数“f”返回的函数指针“$fptr”,使用 signature 定义了函数指望的参数以及返回值:
sub f() returns Pointer is native('mylib') { * } my $fptr = f(); my $nfptr = nativecast(:(Str, size_t --> int32), $fptr); say $nfptr("test", 4);
NativeCall 对数组也一些支持,你可使用机器字长大小的整数,doubles以及string,普通numeric类型,pointer数组,struct数组,数组的数组。
Perl 6 的数组,支持放在其中的东西是缓式求值的,它分配内存的方式和 C 数组有着根本的不一样,因此 NativeCall 库提供了更为原始的数组类型 CArray,当你须要使用 C 数组的时候使用 CArray 吧。
下面是一些传递 C 数组的例子:
sub RenderBarChart(Str, int32, CArray[Str], CArray[num]) is native('chart') { * } my @titles := CArray[Str].new; @titles[0] = 'Me'; @titles[1] = 'You'; @titles[2] = 'Hagrid'; my @values := CArray[num].new; @values[0] = 59.5e0; @values[1] = 61.2e0; @values[2] = 180.7e0; RenderBarChart('Weight (kg)', 3, @titles, @values);
注意咱们对@titles
使用了绑定,而不是赋值,若是你使用赋值,会将一个放进一个 Perl 6 的数组,而后它就不会工做了。若是这令你发疯,忘记你所知道的关于@
魔符的事情,使用 NativeCall 的时候直接使用$
吧。:-)
my $title = CArray[Str].new; $titles[0] = 'Me'; $titles[1] = 'You'; $titles[2] = 'Hagrid';
获取数组形式的返回值也是相同的方式。
有些库的 APIs 可能接受一个数组做为缓冲区,缓冲区将会由 C 函数填充,并返回实际填充的元素数量:
sub get_n_items(CArray[int32], int32) returns int32 is native('ints') { * }
在这些状况下,在传递参数给原生子例程以前,务必确保 CArray 至少含有将会被C函数填充的元素数量,不然 C 函数将破坏 Perl 的内存致使不可预料的行为发生:
my $ints = CArray[int32].new; my $number_of_ints = 10; $ints[$number_of_ints - 1] = 0; # 将数组的大小扩展到10 my $n = get_n_items($ints, $number_of_ints);
搞清楚数组的内存管理是很是重要的,当你建立一个数组,那么你能够向其中添加元素,数组为根据你的需求自动扩展,然而,这可能致使数组在内存中被移动(然而,向一个已经存在的元素赋值时不会引起这个),这意味着当你向一个 C 库传递数组的时候,你最好清楚你在作什么。
做为对比,当C库返回一个数组的时候,NativeCall 就不能管理这一块内存,它也不知道数组的结尾,大概,库的 API 可能会告诉这一点(好比,你知道当你看到一个 null 元素的时候,你就不能继续往前读了)。注意,NativeCall 不为你提供任何的保护,一旦事情作错了,你将会获得一个段错误并致使内存崩溃。这不是 NativeCall 的缺点,这就是整个原生世界工做的方式,很惊奇,好吧,来个拥抱吧,祝你好运!:-)
感谢表示多态性,你能够声明一个普通的 Perl 6类,实际上,C 编译器会把它们放在相似的结构体定义中,以相同的方式保存 attributes。全部你须要作到就是使用“repr”trait:
class Point is repr('CStruct') { has num64 $.x; has num64 $.y; }
声明的属性只能是 NativeCall 已知的能够转换成结构体字段的类型,目前,结构体中能够包含机器字长的整数,doubles,strings以及其它 NativeCall 对象(CArrays,还有 CPointer 以及 CStruct reprs)。除此以外,你能够作一些跟类同样的经常使用的设置,你设置能够拥有一些从 roles 中获得的或者继承自其它类的属性。固然,方法也彻底没有问题,Go wild!
CStruct 对象以引用的形式传递到原生函数,而且原生函数必须返回 CStruct 对象的引用,对于这些引用的内存管理规则跟数组很像,由于一个结构体的大小是不变的。当你建立一个结构体,内存也一并帮你分配好,当指向 CStruct 实例的变量生命期结束,GC会负责将它释放。当 CStruct-based 类型做为原生函数的返回值时,GC并不帮你管理它的内存。
当前,NativeCall 并不把对象成员放到容器里面,因此不能对对象进行赋值操做,做为替代,你能够将私有的成员绑定到新的值上:$!struct-member := StructObj.new;
。
正如你预测的那样,空(null)值是由结构体类型的类型对象实现的。
一样地,咱们能够声明一个 Perl 6 类,它的属性拥有和 C 编译器中联合体(union)
的相同的内存布局,这可使用CUnion
声明:
class MyUnion is repr('CUnion') { has int32 $.flags32; has int64 $.flags64; } say nativesizeof(MyUnion.new); # 输出 8 同 max(sizeof(MyUnion.flags32), sizeof(MyUnion.flags64))
你能够引用 CStructs 和 CUnions,或者将它们嵌入到其余的 CStructs 和 CUnions 里面,前者咱们像往常同样使用has
来声明,然后者则使用HAS
替代:
class MyStruct is repr('CStruct') { has Point $.point; # 引用 has int32 $.flags; } say nativesizeof(MyStruct.new); # 输出 16 同 sizeof(struct Point *) + sizeof(int32_t) class MyStruct2 is repr('CStruct') { HAS Point $.point; # 嵌入 has int32 $.flags; } say nativesizeof(MyStruct2.new); # 输出 24 同 sizeof(struct Point) + sizeof(int32_t)
TBD more
你可使用Pointer
做为传递的参数,这不但对原生类型可用,一样包括CArray
以及CStruct
自定义类型,NativeCall 将不会显式为他们分配内存,即便使用new运算符也不会。这适用于那种 C 函数返回指针或者CStruct
包含指针的状况。
你须要主动调用.deref
获取它的内置类型。
my Pointer[int32] $p; # 一个int32类型的指针 my Pointer[MyStruct] $p2 = some_c_function(); my MyStruct $mc = $p2.deref; say $mc.field1;
TBD
NativeCall 也支持把函数做为原生函数的参数,一个经常使用的状况就是事件驱动模型中,使用函数指针做为回调。当经过 NativeCall 绑定了这些函数,只须要提供对等的 signature 做为函数参数的约束。
# void SetCallBack(int (*callback)(char const *)) my sub SetCallBack(&callback(Str --> int32)) is native('mylib') { * }
注意:原生代码负责传递给 Perl 6 回调的值的内存管理,换句话说,NativeCall 将不会释放传递给回调的字符串占用的内存。
TDB morenative
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
将会始终保持那个值。
假设你写的原生库为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
的整数变量。
NativeCall 也支持使用来自 c++ 的类以及方法,就像这个例子展现的那样(还有相关的 c++ 文件),注意现阶段还不像 C 同样支持测试和开发。
git仓库zavolaj(NativeCall 最初开发的地方)列举了一些如何使用 NativeCall 的例子。
更多的例子能够在[DBIsh](https://github.com/perl6/DBIish/tree/master/lib/DBDish)仓库找到。
这是一个如何使用MYSQL客户端库的例子,这个工程包装了这些函数作成一个DBI兼容的接口,(???)你可能须要安装下面的库,在基于Debian的发行版上能够这么安装:
sudo apt-get install libmysqlclient
在运行例子以前,你须要一些准备工做:
$ mysql -u root -p CREATE DATABASE zavolaj; CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass'; GRANT CREATE ON zavolaj.* TO 'testuser'@'localhost'; GRANT DROP ON zavolaj.* TO 'testuser'@'localhost'; GRANT INSERT ON zavolaj.* TO 'testuser'@'localhost'; GRANT DELETE ON zavolaj.* TO 'testuser'@'localhost'; GRANT SELECT ON zavolaj.* TO 'testuser'@'localhost'; GRANT LOCK TABLES ON zavolaj.* TO 'testuser'@'localhost'; GRANT SELECT ON mysql.* TO 'testuser'@'localhost'; # or maybe otherwise GRANT ALL PRIVILEGES ON zavolaj.* TO 'testuser'@'localhost';
经过一个简单的mysql链接, 你将会看到下面的结果:
$ mysql -utestuser -ptestpass USE zavolaj; SHOW TABLES; SELECT * FROM nom;
win32-api-call.p6脚本文件展现了一些经过 Perl 6 调用 windows API 的例子,参见这里。