简析Swift和C的交互Word文件下载.docx
- 文档编号:21471961
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:16
- 大小:30.48KB
简析Swift和C的交互Word文件下载.docx
《简析Swift和C的交互Word文件下载.docx》由会员分享,可在线阅读,更多相关《简析Swift和C的交互Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。
也可以手动添加BridgeHeader,位置在项目的Build Settings中的 SwiftCompiler–CodeGeneration子项里。
指向你的BridgeHeader 文件名就可以了。
一般这个文件是ProjectName-Bridging-Header.h。
情况基本和与Objective-C混编没区别。
剩下的工作就很简单了。
在.c文件填上传说中的牛逼算法。
在ProjectName-Bridging-Header.h中加上该函数原型或者引入相关的头文件。
在Swift中调用的名字和C名字一样就可以了,比如你定义了一个 intmycsort()那么在Swift中就是 funcmycsort()->
CInt。
这时候问题来了。
一个漂亮的问题。
我的C 函数名字和Swift标准库冲突怎么办?
比如我定义了一个函数就叫 println,我们知道Swift里也有个 println。
这样,如果直接调用会提示Ambiguoususeof'println'
。
没辙了么?
这里有个我发现的UndocumentedFeatuer或者说是UndocumentedAttribute。
你转载好歹提下我好吧。
(发现方法是通过 Xcode 查看定义,然后通过nm命令发现符号,对照llvmir 确认的。
那就是@asmname("
func_name_in_c")。
用于函数声明前。
使用方法:
1.int
println()
{
....
}
1.@asmname("
println"
)
func
c_println()
->
CInt
//
声明,不需要
{}
函数体
2.c_println()
//
调用
也就是C中的同名函数,我们可以给赋予一个别名,然后正常调用。
这么一看就基本没有问题了。
至于类型问题,待会说,详细说。
CFramework
很多时候我们拿到的是第三方库,格式大概是个Framework。
比如 SDL2.framework。
举这个例子是因为我想对来说比较熟悉 SDL2。
直接用Finder 找到这个 .framework文件,拖动到当前项目的文件列表里,这样它将作为一个可以展开的文件夹样式存在于我们的项目中。
在ProjectName-Bridging-Header.h 中引入其中需要的.h。
比如我们引入SDL2.framework,那么我们就需要写上#import<
SDL2/SDL.h>
然后在Swift文件里正常调用就好了。
所以其实说到底核心就是那个ProjectName-Bridging-Header.h,因为它是作为参数传递给 Swift编译器的,所以Swit 文件里可以从它找到定义的符号。
但是,这个桥接头文件的一切都是隐式的,类型自动对应,所以很多时候需要我们在Swift里调用并封装。
或者使用@asmname(...)避免名字冲突。
第三部分类型转换
前面说到了 C中有指针,而Swift 中没有,同时基本类型还有很多不同。
牢记一个万能函数 reinterpretCast<
T,U>
(T)->U,只要T, Usizeof运算相等就可以直接转换。
这个在之前的标准库函数里有提到。
调用C代码的利器!
基本类型对应
int=>
CInt
char =>
CChar /CSignedChar
char* =>CString
unsigned long = >CUnsignedLong
wchar_t=> CWideChar
double =>
CDouble
T*=>CMutablePointer
void*=>
CMutableVoidPointer
const T*=> CConstPointer
constvoid*=>CConstVoidPointer
…
继续这个列表,你肯定会想这么多数值类型,怎么搞。
其实大都是被 typealias 定义到UInt8,Double这些的。
放心。
C中数值类型全部被明确地用别名定义到带size的Swift 数值类型上。
完全是一样用的。
其实真正的Pointer 类型只是UnsafePointer<
T>
大小与 C保证一致,而对于这里不同类型的Pointer,其实都是 UnsafePointer 到它们的隐式类型转换。
还有个指针相关类型是 COpaquePointer,不过没试验怎么用。
UPDATE:
我们在调用的时候,更多地用到COpaquePointer,我将再坑一篇介绍它。
同时NilType,也就是nil 有到这些指针的隐式类型转换。
所以可以当做任何一种指针的NULL用。
还有个需要提到的类型是CString,他的内存layout等于UnsafePointer<UInt8>
,下面说。
CString
用于表示char*,\0结尾的c字符串,实际上似乎还看到了判断是否ASCII的选项,但是没试出来用法。
实现了StringLiteralConvertible和LogicValue。
可以从字符串常量直接赋值获得CString。
LogicValue也就是是ifa_c_str{},实际是用于判断是否为NULL,可用,但是不稳定,老 crash。
运算符支持==,判断两字符串是否相当,猜测实际是 strcmp 实现,对比NULL会 crash。
Orz。
CString和 String的转换通过一个extension实现,也是很方便。
1.extension
String
{
2.
static
func
fromCString(cs:
CString)
->
String
3.
static
func
fromCString(up:
UnsafePointer<
CChar>
->
String
4.}
5.//
还有两个方便的工具方法。
Rust
背景的同学一定仰天长啸。
太相似了。
6.extension
String
7.
func
withCString<
Result>
(f:
(CString)
Result)
Result
8.
withCString<Result>
(f:
(UnsafePointer<
CChar>
Result)
Result
9.}
在我们的 BridgingHeader头文件中 char *的类型会对应为 UnsafePointer<
CChar>
而实际上CString更适合。
所以在Swift代码中,往往我们要再次申明下这个函数。
或者用一个函数封装下,转换成我们需要的类型。
例如,假设在 Bridging Header中我们声明了char*foo();
,那么,在 Swift代码中我们可以用上面提到的方法:
1.@asmname("foo"
func
c_foo()
->
CString
2.//
注意这里没有
{},只是声明
3.let
ret
=
c_foo()
当然也可以直接调用原始函数然后类型转换:
1.let
raw
=
foo()
UnsafePointer<
Int8>
<
=>
UnsafePointer<CChar>
2.let
ret
String.fromCString(ret)
如果这里要转成 CString就略复杂了,因为CString构造函数接受的参数是UnsafePointer<UInt8>
而CChar是 Int8 的别名,所以还牵扯到Genrics类型转换,不够方便。
如果非要作为 CString处理,可以用reinterpretCast(),直接转换。
但是请一定要知道自己在转换什么,确保类型的sizeof相同,确保转换本身有意义。
例如获得环境变量字符串:
1.let
key
"PATH"
2.//
这里相当于把
UnsafePointer<
CChar>
转为了
UnsafePointer<
UInt8>
然后到
CString
3.let
path_str:
CString
reinterpretCast(key.withCString(getenv))
Unmanaged
这个先挖坑,随后填上。
VaList
这个也是坑,随后填上。
第三部分 C调用Swift
如果项目里加入了C文件,那么它可以调用我们的Swift 函数么?
答案是可以的,而且令人吃惊地透明。
这也许是因为Apple 所宣传的,SmallRuntime 概念吧。
极小的语言运行时。
和Objective-C混编类似,配置好 BridgingHeader的项目,在 .c .h .m文件中都可以使用一个叫做ProjectName-Swift.h 的头文件,其中包含 Swift代码导出的函数等。
参考之前的 Objective-C和 C交互我们可以知道,说到底交互就是链接过程,只要链接的时候能找到符号就可以。
不过不能高兴太早,Swift是带类、枚举、协议、多态、泛型等的高级语言,符号处理明显要比C中的复杂的多,现代语言一般靠namemangle 来解决这个问题。
也就是说一个Swift函数,在编译到.o的时候,名字就不是原来那么简单了。
比如__TFV5hello4Rectg9subscriptFOS_9DirectionSi这样的名字。
Xcode自带了个工具,可以查看这些mangledname 到底是什么东西:
1.xcrun
swift-demangle
__TFV5hello4Rectg9subscriptFOS_9DirectionSi
2._TFV5hello4Rectg9subscriptFOS_9DirectionSi
--->
hello.Rect.subscript.getter
(hello.Direction)
Swift.Int
当我们从 C调用的时候,应该规避这样的名字。
还记得前面的 @asmname么?
没错,它可以用于指定Swift函数的符号名,我猜测应该有指定mangledname 的作用,但是不是特别确定。
这里随便指定个例子先。
1.@asmname("
say_hello")
func
say_hello()
Double
println("
This
is
say_hello()
in
swift")
return
3.14
4.}
然后在.c文件中:
1.#include
<ProjectName-Swift.h>
3.extern
double
say_hello();
4.
5.int
some_func()
6.
say_hello();
//
or
capture
its
value
and
process
it
return
0
8.}
对于函数而言 extern 必须手动加上,对于class、protocol ,会在生成的头文件里。
按照这个思路,其实很容易实现Swift调用C中调用了Swift函数的函数。
这意味着,可以通过简单的方法封装支持向 C 传递 Swift block作为回调函数。
难度中上,对于有过类似扩展编写经验的人来说很简单。
第四部分 编译过程
其实调用基本就这么多, Objective-C 那篇文章中说的编译过程同样有效。
我C-cC-v下:
编译所有X.swift 文件到X.o(with -emit-objc-header,-import-objc-header)(其中包含.swiftmodule 子过程):
由于选项里有-emit-objc-header,所以之后的C文件可以直接import对应的ProjectName-Swift.h
编译X.c到X.o
链接所有.o生成可执行文件
仔细理解上面的简简单单四行编译过程说明,你就明白为什么 .swfit和.c可以互相调用了。
其中两个Header文件起到了媒介的作用,一个将.c/.m文件中的定义暴露给Swift,另一个将.swift中的定义暴露给 .c/.m。
再看类型对应
标准类型这里就不提了,上面的文章讲的很明白了。
7 种指针类型
从代码看,我认为Swift对应C 的指针时候,存在一个最原始的类型RawPointer,但是它是内部表示,不可以直接使用。
所以略过。
但它是基础,可以认为它相当于Word 类型(机器字长)。
COpaquePointer
不透明指针。
之前我以为它很少会用到,不过现在看来想错了,虽然类型不安全,但是很多场合只能用它。
它是直接对应RawPointer的。
字长相等。
“Incomputerprogramming,an opaquepointerisa specialcase of an opaquedatatype,adatatypedeclaredtobeapointertoa record ordata structureofsome unspecifiedtype.”——来自Wikipedia
几乎没有任何操作方法,不带类型,主要用于BridgingHeader中表示 C中的复杂结构指针
比如一个例子,libcurl中的CURL*的处理,其实就是对应为COpaquePointer。
UnsafePointer
泛型指针。
直接对应 RawPointer。
处理指针的主力类型。
常量中的C_ARGV 的类型也是它UnsafePointer<CString>
支持大量操作方法:
通过.memory 属性 { get set}操作指针指向的内容
支持subscript,直接对应于C的数组,例如C_ARGV[1]
通过alloc(num:
Int) 分配数组空间
initialize(val:
T) 直接初始化
offset操作.succ() .pred()
可以从任意一种指针直接调用构造函数获得
隐式类型转换为非COpaquePointer之外的任意一种指针
AutoreleasingUnsafePointer
之前特地写文介绍过这个指针类型。
NSError 的处理就主要用它。
SwiftNSErrorInternals(解析 Swift对 NSError操作)
内部实现用了语言内置特性,从名字也可以看出来,这个应该是非常棒的一个指针,可以帮助管理内存,逼格也高。
内存直接对应 RawPointer可以传递给C 函数。
通过.memory属性{ get set}操作指针指向的内容
直接从 &T 类型获得,使用方法比较诡异,建议参考文章
CMutablePointer CConstPointer
分别对应于C 中的T*、constT *。
不可直接传递给C 函数,因为表示结构里还有一个owner域,应该是用来自动管理生命周期的。
sizeof操作返回16。
但是可以有隐式类型转换。
操作方法主要是funcwithUnsafePointer<
U>(f:
UnsafePointer<
T>->
U) ->
U,用Trailing Closure语法非常方便。
CMutableVoidPointerCConstVoidPointer
分别对应于C中的void *、constvoid*。
其他内容同上一种。
小结指针
以上7 种指针类型可以分未两类,我给他们起名为第一类指针 和第二类指针。
(你看我在黑马克思耶,算了这个梗太深,参考马克思主义政治经济学)
-可以直接用于C函数声明的第一类指针
COpaquePointerUnsafePointer<T>
AutoreleasingUnsafePointer<
T>
是对RawPointer的封装,直接对应于C的指针,它们的sizeof 都是单位字长
-不可用于声明第二类指针
CMutablePointer<
CConstPointer<T>CMutableVoidPointerCConstVoidPointer
直接从Swift对象的引用获得(一个隐藏特性,引用隐式转换)(主要构造方法)
包含了一个 owner字段,可以管理生命周期,理论上在Swift中使用
通过.withUnsafePointer 方法调用
所有指针都实现了 LogicValue协议,可以直接 ifa_pointer判断是否为NULL。
nil类型实现了到所有指针类型的隐式类型转换,等价于 C中的`NULL,可以直接判断。
什么时候用什么?
这个问题我也在考虑中,以下是我的建议。
对应复杂结构体,不操作结构体字段的:
COpaquePointer例如CURL *
日常操作:
UnsafePointer<
T>
同时需要在Swift和 C中操作结构体字段,由Swift管理内存:
AutoreleasingUnsafePointer<T>
Swift中创建对象,传递给C:
第二类指针
工具类型
CVarArg CVaListPointerVaListBuilder
用于处理 C语言中的可变参数 valist函数。
1.protocol
CVarArg
func
encode()
Word[]
3.}
表示该类型可以作为可变参数,相当多的类型都实现了这个。
1.struct
CVaListPointer
var
value:
UnsafePointer<Void>
init(fromUnsafePointer
from:
UnsafePointer<
Void>
@conversion
__conversion()
CMutableVoidPointer
5.}
对应于C,直接给C函数传递,声明、定义时使用。
1.class
VaListBuilder
init()
append(arg:
CVarArg)
va_list()
CVaListPointer
5.}
工具类,方便地创建CVaListPointer。
还有一些工具函数:
1.func
getVaList(args:
CVarArg[])
CVaListPointer
2.func
withVaList<
R>
(args:
CVarArg[],
f:
(CVaListPointer)
R)
R
3.func
withVaList<
R>
(builder:
VaListBuilder,
(CVaListPointer)
R)
非常方便。
UnsafeArray
1.struct
UnsafeArray<
:
Collection,
Generator
startIndex:
Int
get
}
endIndex:
Int
subscript
(i:
Int)
T
get
5.
init(start:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 简析 Swift 交互