如何调用-编译生成的灵活链接库-Rust-Python-解密 (如何调用编写好的函数求列表中的最大值最小值)
楔子
让/target=_blankclass=infotextkey>Python愈加平凡,随着Rust的盛行,反而让Python的消费劲提高了不少。由于有越来越多的Python工具,都选用了Rust启动开发,并且性能也优于同类型的其它工具。比如:
总之如今Rust+Python曾经成为了一个趋向,并且Rust也提供了一系列成熟好用的工具,比如PyO3、Maturin,专门为Python编写裁减。不过关于PyO3咱们以后再聊,本篇文章先来引见如何将Rust代码编译成灵活库,而后交给Python的ctypes模块调用。
由于经过ctypes调用灵活库是最繁难的一种方式,它只对操作系统有要求,只需操作系统分歧,那么任何提供了ctypes模块的Python解释器都可以调用。
当然这也正面要求,Rust提供的接口不能太复杂,由于ctypes提供的交互才干还是比拟有限的,最显著的疑问就是不同言语的数据类型不同,一些复杂的交互方式还是比拟难做到的,还有多线程的控制疑问等等。
举个例子
上方咱们举个例子感触一下Python和Rust的交互环节,首先经过如下命令创立一个Rust名目:
cargonewpy_lib--lib
创立完之后修正Cargo.toml,在外面参与如下内容:
[lib]#编译之后的灵活库的称号name="py_lib"#示意编译成一个和C言语二进制接口(ABI)兼容的灵活链接库crate-type=["cdylib"]
cdylib示意生成灵活库,假构想生成静态库,那么就指定为staticlib。
上方开局编写源代码,在生成名目之后,src目录下会有一个lib.rs,它是整个库的入口点。咱们的代码比拟繁难,间接写在lib.rs外面即可。
#[no_mangle]pubextern"C"fnadd(a:i32,b:i32)->i32{a+b}#[no_mangle]pubextern"C"fnget_square_root(v:i32)->f64{(vasf64).sqrt()}
在定义函数时要求经常使用pubextern"C"启动申明,它示意创立一个外部可见、遵照C言语调用商定的函数,由于Python经常使用的是CABI。
此外还要给函数参与一个#[no_mangle]属性,让编译器在将Rust函数导出为C函数时,不要扭转函数的称号。确保在编译成灵活库后,函数名坚持不变,否则在调用灵活库时就找不到指定的函数了。
代码编写成功,咱们经过cargobuild启动编译,而后在target/debug目录下就会生成相应的灵活库。由于库的称号咱们指定为py_lib,那么生成的库文件名就叫libpy_lib.dylib。
编译器生成灵活库后,会智能加上一个lib前缀(系统除外),至于后缀则与操作系统有关。
而后咱们经过Python启动调用。
importctypes#经常使用ctypes很繁难,间接import出去#而后经常使用ctypes.CDLL这个类来加载灵活链接库#或许经常使用ctypes.cdll.LoadLibrary也是可以的py_lib=ctypes.CDLL("../py_lib/target/debug/libpy_lib.dylib")#加载之后就失掉了灵活链接库对象,咱们起名为py_lib#而后经过属性访问的方式去调用外面的函数print(py_lib.add(11,22))"""33"""#假设不确定函数能否存在,那么倡导经常使用反射#由于函数不存在,经过.的方式失掉是会抛意外的get_square_root=getattr(py_lib,"get_square_root",None)ifget_square_root:print(get_square_root)"""<_FuncPtrobjectat0x7fae30a2b040>"""#不存在sub函数,所以失掉的结果为Nonesub=getattr(py_lib,"sub",None)print(sub)"""None"""
所以经常使用ctypes去调用灵活链接库十分繁难,环节很繁难:
咱们以上就展示了如何经过ctypes模块来调用Rust编译生成的灵活库,但显然目前还是远远不够的,比如说:
fromctypesimportCDLLpy_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")square_root=py_lib.get_square_root(100)print(square_root)#0
100的平方根是10,但却前往了0。这是由于ctypes在解析前往值的时刻自动是依照整型来解析的,但以后的函数前往的是浮点型,因此函数在调用之前要求显式地指定其前往值类型。
不过在这之前,咱们要求先来看看Python类型和Rust类型之间的转换相关。
数值类型
经常使用ctypes调用灵活链接库,关键是调用库外面经常使用Rust编写好的函数,但这些函数是要求参数的,还有前往值。而不同言语的变量类型不同,Python不能间接往Rust编写的函数中传参,因此ctypes提供了少量的类,帮咱们将Python的类型转成Rust的类型。
上方来测试一下,首先编写Rust代码:
#[no_mangle]pubextern"C"fnadd_u32(a:u32)->u32{a+1}#[no_mangle]pubextern"C"fnadd_isize(a:isize)->isize{a+1}#[no_mangle]pubextern"C"fnadd_f32(a:f32)->f32{a+1.}#[no_mangle]pubextern"C"fnadd_f64(a:f64)->f64{a+1.}#[no_mangle]pubextern"C"fnreverse_bool(a:bool)->bool{!a}
编译之后Python启动调用。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")print(py_lib.add_u32(123))"""124"""print(py_lib.add_isize(666))"""667"""try:print(py_lib.add_f32(3.14))exceptExceptionase:print(e)"""<class'TypeError'>:Don'tknowhowtoconvertparameter1"""#咱们看到报错了,通知咱们不知道如何转化第1个参数#由于Python的数据和C的数据不一样,所以不能间接传递#但整数是个例外,除了整数,其它数据都要求经常使用ctypes包装一下#另外整数最好也包装一下,由于不同整数之间,精度也有区别print(py_lib.add_f32(c_float(3.14)))"""1"""#只管没报错,然而结果不对,结果应该是3.14+1=4.14,而不是1#由于ctypes调用函数时自动经常使用整型来解析,但该函数前往的不是整型#要求通知ctypes,add_f32函数前往的是c_float,请依照c_float来解析py_lib.add_f32.restype=c_floatprint(py_lib.add_f32(c_float(3.14)))"""4.140000343322754"""#f32和f64是不同的类型,占用的字节数也不一样#所以c_float和c_double之间无法混用,只管都是浮点数py_lib.add_f64.restype=c_doubleprint(py_lib.add_f64(c_double(3.14)))"""4.140000000000001"""py_lib.reverse_bool.restype=c_boolprint(py_lib.reverse_bool(c_bool(True)))print(py_lib.reverse_bool(c_bool(False)))"""FalseTrue"""
不复杂,以上咱们就成功了数值类型的传递。
字符类型
字符类型有两种,一种是ASCII字符,实质上是个u8;一种是Unicode字符,实质上是个u32。
编写Rust代码:
#[no_mangle]pubextern"C"fnget_char(a:u8)->u8{a+1}#[no_mangle]pubextern"C"fnget_unicode(a:u32)->u32{letchr=char::from_u32(a).unwrap();ifchr=='憨'{'批'asu32}else{a}}
咱们知道Rust专门提供了4个字节char类型来示意unicode字符,但关于外部导出函数来说,经常使用char是不安保的,所以间接经常使用u8和u32就行。
编译之后,Python调用:
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")#u8除了可以经常使用c_byte包装之外,还可以经常使用c_char#并且c_byte外面只能接纳整数,而c_char除了整数,还可以接纳长度为1的字节串print(c_byte(97))print(c_char(97))print(c_char(b"a"))"""c_byte(97)c_char(b'a')c_char(b'a')"""#以上三者是等价的,由于char说白了就是个u8#指定前往值为c_byte,会前往一个整数py_lib.get_char.restype=c_byte#c_byte(97)、c_char(97)、c_char(b"a")都是等价的#由于它们实质上都是u8,至于97也可以解析为u8print(py_lib.get_char(97))#98#指定前往值为c_char,会前往一个字符(长度为1的bytes对象)py_lib.get_char.restype=c_charprint(py_lib.get_char(97))#b'b'py_lib.get_unicode.restype=c_wcharprint(py_lib.get_unicode(c_wchar("嘿")))#嘿#间接传一个u32整数也可以,由于unicode字符底层就是个u32print(py_lib.get_unicode(ord("憨")))#批
以上就是字符类型的操作,比拟繁难。
字符串类型
再来看看字符串,咱们用Rust成功一个函数,它接纳一个字符串,而后前往大写方式。
usestd::ffi::{CStr,CString};usestd::os::raw::c_char;#[no_mangle]pubextern"C"fnto_uppercase(s:*constc_char)->*mutc_char{//将*constc_char转成&CStrlets=unsafe{CStr::from_ptr(s)};//将&CStr转成&str//而后调用to_uppercase转成大写,失掉Stringlets=s.to_str().unwrap().to_uppercase();//将String转成*mutchar前往CString::new(s).unwrap().into_raw()}
解释一下外面的CStr和CString,在Rust中,CString用于创立C格调的字符串(以 开头),领有自己的内存。关键的是,CString领有值的一切权,当实例退出作用域时,它的析构函数会被调用,相关内存会被智能监禁。
而CStr,它和CString之间的相关就像str和String的相关,所以CStr普通以援用的方式产生。并且CStr没有new方法,不能间接创立,它要求经过from_ptr方法从原始指针转化失掉。
而后指针类型是*const和*mut,区分示意指向C格调字符串的首字符的无法变指针和可变指针,它们的区别关键在于指向的数据能否可以被修正。假设不要求修正,那么经常使用*const会更安保一些。
咱们编写Python代码测试一下。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")s="hello古明地觉".encode("utf-8")#自动是依照整型解析的,所以不指定前往值类型的话,会失掉脏数据print(py_lib.to_uppercase(c_char_p(s)))"""31916096"""#指定前往值为c_char_p,示意依照char*来解析py_lib.to_uppercase.restype=c_char_pprint(py_lib.to_uppercase(c_char_p(s)).decode("utf-8"))"""HELLO古明地觉"""
从外表上看仿佛挺顺利的,但面前暗藏着内存暴露的危险,由于Rust外面创立的CString还驻留在堆区,必定要将它监禁掉。所以咱们还要写一个函数,用于监禁字符串。
usestd::ffi::{CStr,CString};usestd::os::raw::c_char;#[no_mangle]pubextern"C"fnto_uppercase(s:*constc_char)->*mutc_char{lets=unsafe{CStr::from_ptr(s)};lets=s.to_str().unwrap().to_uppercase();CString::new(s).unwrap().into_raw()}#[no_mangle]pubextern"C"fnfree_cstring(s:*mutc_char){unsafe{ifs.is_null(){return}//基于原始指针创立CString,拿到堆区字符串的一切权//而后退出作用域,智能监禁CString::from_raw(s)};}
然起初看看Python如何调用:
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")s="hello古明地觉".encode("utf-8")#Rust前往的是原始指针,这里必定要拿到它保留的地址#所以指定前往值为c_void_p,假设指定为c_char_p,#那么会间接转成bytes对象,这样地址就拿不到了py_lib.to_uppercase.restype=c_void_p#拿到地址,此时的ptr是一个普通的整数,但它和指针保留的地址是一样的ptr=py_lib.to_uppercase(c_char_p(s))#将ptr转成c_char_p,失掉value属性,即可失掉详细的bytes对象print(cast(ptr,c_char_p).value.decode("utf-8"))"""HELLO古明地觉"""#内容咱们拿到了,但堆区的字符串还没有监禁,所以调用free_cstringpy_lib.free_cstring(c_void_p(ptr))
经过CString的into_raw,可以基于CString创立原始指针*mut,而后Python将指针指向的堆区数据拷贝一份,失掉bytes对象。
但这个CString照旧驻留在堆区,所以Python不能将前往值指定为c_char_p,由于它会间接创立bytes对象,这样就拿不到指针了。因此将前往值指定为c_void_p,调用函数会失掉一串整数,这个整数就是指针保留的地址。
咱们经常使用cast函数可以将地址转成c_char_p,失掉它的value属性拿到详细的字节串。再经过c_void_p创立原始指针交给Rust,调用CString的from_raw,可以基于*mut创立CString,从而将一切权夺回来,而后退出作用域时监禁堆内存。
给函数传递指针
假设裁减函数外面接纳的是指针,那么Python要怎样传递呢?
#[no_mangle]pubextern"C"fnadd(a:*muti32,b:*muti32)->i32{//定义为*mut,那么可以修正指针指向的值,定义为*const,则不能修正ifa.is_null()||b.is_null(){0}else{letres=unsafe{*a+*b};unsafe{//这里将*a和*b给改掉*a=666;*b=777;}res}}
定义了一个add函数,接纳两个i32指针,前往解援用后相加的结果。然而在前往之前,咱们将*a和*b的值也修正了。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")a=c_int(22)b=c_int(33)#计算print(py_lib.add(pointer(a),pointer(b)))#55#咱们看到a和b也被修正了print(a,a.value)#c_int(666)666print(b,b.value)#c_int(777)777
十分繁难,那么疑问来了,能不能前往一个指针呢?答案是当然可以,只不过存在一些留意事项。
由于Rust自身的内存安保准则,间接从函数前往一个指向本地部分变量的指针是不安保的。由于该变量的作用域仅限于函数自身,一旦函数前往,该变量的内存就会被回收,从而产生悬空指针。
为了防止这种状况产生,咱们应该在堆上调配内存,但这又产生了之前CString的疑问。Python在拿到值之后,堆内存照旧驻留在堆区。因此Rust假构想前往指针,那么同时还要定义一个监禁函数。
#[no_mangle]pubextern"C"fnadd(a:*consti32,b:*consti32)->*muti32{//前往值的类型是*muti32,所以res不能间接前往,因此它是i32letres=unsafe{*a+*b};//创立智能指针(将res装箱),而后前往原始指针Box::into_raw(Box::new(res))}#[no_mangle]pubextern"C"fnfree_i32(ptr:*muti32){if!ptr.is_null(){//转成Box<i32>,同时拿到一切权,在退出作用域时监禁堆内存unsafe{let_=Box::from_raw(ptr);}}}
而后Python启动调用:
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")a,b=c_int(22),c_int(33)#指定类型为c_void_ppy_lib.add.restype=c_void_p#拿到指针保留的地址ptr=py_lib.add(pointer(a),pointer(b))#将c_void_p转成POINTER(c_int)类型,也就是c_int*#经过它的contents属性拿到详细的值print(cast(ptr,POINTER(c_int)).contents)#c_int(55)print(cast(ptr,POINTER(c_int)).contents.value)#55#监禁堆内存py_lib.free_i32(c_void_p(ptr))
这样咱们就拿到了指针,并且也不会产生内存暴露。然而独自定义一个监禁函数还是有些费事的,所以Rust智能提供了一个free函数,专门用于监禁堆内存。举个例子:
usestd::ffi::{CStr,CString};usestd::os::raw::c_char;#[no_mangle]pubextern"C"fnto_uppercase(s:*constc_char)->*mutc_char{lets=unsafe{CStr::from_ptr(s)};lets=s.to_str().unwrap().to_uppercase();CString::new(s).unwrap().into_raw()}#[no_mangle]pubextern"C"fnadd(a:*consti32,b:*consti32)->*muti32{letres=unsafe{*a+*b};Box::into_raw(Box::new(res))}
这是产生过的两个函数,它们的内存都放开在堆区,但咱们将内存监禁函数删掉了,由于Rust智能提供了一个free函数,专门用于堆内存的监禁。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")#前往值类型指定为c_void_p,示意万能指针py_lib.to_uppercase.restype=c_void_ppy_lib.add.restype=c_void_pptr1=py_lib.to_uppercase(c_char_p("Serpen教员".encode("utf-8")))ptr2=py_lib.add(pointer(c_int(123)),pointer(c_int(456)))#函数调用终了,将地址转成详细的类型的指针print(cast(ptr1,c_char_p).value.decode("utf-8"))"""SERPEN教员"""print(cast(ptr2,POINTER(c_int)).contents.value)"""579"""#监禁堆内存,间接调用free函数即可,十分繁难py_lib.free(c_void_p(ptr1))py_lib.free(c_void_p(ptr2))
以上咱们就成功了指针的传递和前往,但关于整数、浮点数而言,间接前往它们的值即可,没必要前往指针。
传递数组
上方来看看如何传递数组,由于数组在作为参数传递的时刻会退步为指针,所以数组的长度信息就失落了,经常使用sizeof计算出来的结果就是一个指针的大小。因此将数组作为参数传递的时刻,应该将以后数组的长度信息也传递过去,否则或许会访问合法的内存。
咱们成功一个配置,Rust接纳一个Python数组,启动原地排序。
usestd::slice;#[no_mangle]pubextern"C"fnsort_array(arr:*muti32,len:usize){assert!(!arr.is_null());unsafe{//失掉一个切片&mut[i32]letslice=slice::from_raw_parts_mut(arr,len);slice.sort();//排序}}
而后Python启动调用:
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")#一个列表mpa-from-tpl="t">传递结构体
结构体应该是Rust外面最关键的结构之一了,它要如何和外部交互呢?
usestd::ffi::c_char;#[repr(C)]pubstructGirl{pubname:*mutc_char,pubage:u8,}#[no_mangle]pubextern"C"fncreate_struct(name:*mutc_char,age:u8)->Girl{Girl{name,age}}
由于结构体实例要前往给外部,所以它的字段类型必定是兼容的,不能定义C了解不了的类型。而后还要设置#[repr(C)]属性,来保障结构体的内存规划和C是兼容的。
上方经过cargobuild命令编译成灵活库,Python担任调用。
![Python](https://tyye.cn/zb_users/theme/tpure/style/images/lazyload.png)
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")classGirl(Structure):_fields_=[("name",c_char_p),("age",c_uint8),]#指定create_struct的前往值类型为Girlpy_lib.create_struct.restype=Girlgirl=py_lib.create_struct(c_char_p("S教员".encode("utf-8")),c_uint8(18))print(girl.name.decode("utf-8"))#S教员print(girl.age)#18
调用成功,并且此时是没有内存暴露的。
当经过FFI将数据从Rust传递到Python时,假设传递的是指针,那么会触及内存监禁的疑问。但假设传递的是值,那么它会复制一份给Python,而原始的值(这里是结构体实例)会被智能销毁,所以无需担忧。
而后是结构体外部的字段,只管外面的name字段是*mutc_char,但它的值是由Python传过去的,而不是在Rust外部创立的,因此没有疑问。
但假设将Rust代码改一下:
usestd::ffi::{c_char,CString};#[repr(C)]pubstructGirl{pubname:*mutc_char,pubage:u8,}#[no_mangle]pubextern"C"fncreate_struct()->Girl{letname=CString::new("S教员").unwrap().into_raw();letage=18;Girl{name,age}}
这时就难堪了,此时的字符串是Rust外面创立的,转成原始指针之后,Rust将不再治理相应的堆内存(由于into_raw将一切权转移走了),此时就要求手动堆内存了。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")classGirl(Structure):_fields_=[("name",c_char_p),("age",c_uint8),]#指定create_struct的前往值类型为Girlpy_lib.create_struct.restype=Girlgirl=py_lib.create_struct()print(girl.name.decode("utf-8"))#S教员print(girl.age)#18#间接传递girl即可,会监禁girl外面的字段在堆区的内存py_lib.free(girl)
此时就不会产生内存暴露了,在free的时刻,将变量girl传出来,监禁掉外部字段占用的堆内存。
当然,Rust也可以前往结构体指针,经过Box<T>成功。
#[no_mangle]pubextern"C"fncreate_struct()->*mutGirl{letname=CString::new("S教员").unwrap().into_raw();letage=18;Box::into_raw(Box::new(Girl{name,age}))}
留意:之前是name字段在堆上,但结构体实例在栈上,如今name字段和结构体实例都在堆上。
而后Python调用也很繁难,关键是监禁的疑问。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")classGirl(Structure):_fields_=[("name",c_char_p),("age",c_uint8),]#此时前往值类型就变成了c_void_p#以后往指针时,倡导将前往值设置为c_void_ppy_lib.create_struct.restype=c_void_p#拿到指针(一串整数)ptr=py_lib.create_struct()#将指针转成指定的类型,而类型显然是POINTER(Girl)#调用POINTER(T)的contents方法,拿到相应的结构体实例girl=cast(ptr,POINTER(Girl)).contents#访问详细内容print(girl.name.decode("utf-8"))#S教员print(girl.age)#18#监禁堆内存,这里的监禁分为两步,并且顺序不能错#先free(girl),监禁掉外部字段(name)占用的堆内存#而后free(c_void_p(ptr)),监禁掉结构体实例girl占用的堆内存py_lib.free(girl)py_lib.free(c_void_p(ptr))
不难了解,只是在监禁结构体实例的时刻要求多留意,假设外部有字段占用堆内存,那么要求先将这些字段监禁掉。而监禁的方式是将结构体实例作为参数传给free函数,而后再传入c_void_p监禁结构体实例。
回调函数
最后看一下Python如何传递函数给Rust,由于Python和Rust之间经常使用的是CABI,所以函数必定遵照C的规范。
//calc接纳三个参数,前两个参数是*consti32//最后一个参数是函数,它接纳两个*consti32,前往一个i32#[no_mangle]pubextern"C"fncalc(a:*consti32,b:*consti32,op:extern"C"fn(*consti32,*consti32)->i32)->i32{op(a,b)}
而后看看Python如何传递回调函数。
fromctypesimport*py_lib=CDLL("../py_lib/target/debug/libpy_lib.dylib")#基于Python函数创立C函数,经过@CFUNCTYPE()启动装璜#CFUNCTYPE第一个参数是前往值类型,残余的参数是参数类型@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defadd(a,b):#a、b为int*,经过.contents.value拿到详细的值returna.contents.value+b.contents.value@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defsub(a,b):returna.contents.value-b.contents.value@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defmul(a,b):returna.contents.value*b.contents.value@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defdiv(a,b):returna.contents.value//b.contents.valuea=pointer(c_int(10))b=pointer(c_int(2))print(py_lib.calc(a,b,add))#12print(py_lib.calc(a,b,sub))#8print(py_lib.calc(a,b,mul))#20print(py_lib.calc(a,b,div))#5
成功成功了向Rust传递回调函数,当然例子举得有点刻意了,比如参数类型指定为i32即可,没有必要经常使用指针。
小结
以上咱们就引见了Python如何调用Rust编译的灵活库,再次强调一下,经过ctypes调用灵活库是最繁难、最繁难的方式。它和Python的版本有关,也不触及底层的C裁减,它只是将Rust编译成CABI兼容的灵活库,而后交给Python启动调用。
因此这也正面要求,函数的参数和前往值的类型应该是C可以示意的类型,比如Rust函数不能前往一个trt对象。总之在调用灵活库的时刻,库函数外部的逻辑可以很复杂,然而参数和前往值最好要繁难。
假设你发现Python代码存在少量的CPU密集型计算,并且不怎样触及复杂的Python数据结构,那么无妨将这些计算交给Rust。
以上就是本文的内容,后续有空咱们引见如何用Rust的PyO3来为Python编写裁减。PyO3的定位相似于Cython,用它来写裁减十分的繁难,后续无时机咱们详细聊一聊。
Python 编译生成 pyc 仅仅为了提升加载速度,并不是为了防止破解,反编译后和原来一模一样。 pyinstaller,py2exe,只是把 pyc 打个包,同样很弱。 代码混淆也只能增加看懂代码的难度,但并不能防止破解。 所以最为稳妥的办法只有修改Python解释器,对源代码进行加密,解释器加载源代码时再解密,这种方法虽然可以防止破解,但给自己带来麻烦不说,发布程序是需要打包自己修改后的解释器,相当麻烦。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。