Error[8]: Undefined offset: 305, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

概述原文链接:http://www.cocoachina.com/applenews/devnews/2014/0619/8884.html 其中 @asmname 的两个用法源于我的猜测验证,用到了 Xcode, lldb, nm, llvm ir 等工具或格式。   其中 name mangling 部分源自 WWDC。   相关的分析主要基于我 dump 出的 Swift 标准库声明代码,位于

原文链接:http://www.cocoachina.com/applenews/devnews/2014/0619/8884.HTML


其中 @asmname 的两个用法源于我的猜测验证,用到了 Xcode,lldb,nm,llvm ir 等工具或格式。

其中 name mangling 部分源自 WWDC。

相关的分析主要基于我 dump 出的 Swift 标准库声明代码,位于 我的Github andelf/Defines-Swift

之前好像简单说过 Swift 和 Objective-C 的交互问题。其实我们也可以用 Swift 调用纯 C 代码或者基于 C 的第三方库。(这里不会也永远不会考虑 C++ 的情况,因为不支持,不过可以用 C 写 wrapper,这个没有任何问题。)

Swift 官方文档中,以及那本已经被迅速翻译为中文的 ibooks 书中,都提到了 Swift 调用 Objective-C 和 C 是有很好支持的。不过没有细节。

本内容包括 Swift 调用 C 和相应的 C 调用 Swift,项目混编。

这里主要面向 MacOSX 应用。iOS 或许可以适用。

先复习下区别。

第一部分 预备知识

语言区别

说到底就是 C 少了很多东西。但是多了个指针。

对于 C 来说,最头疼的莫过于指针,而 Swift 是一门没有指针的语言。是不是要吐血?相对来说指针不但代表者指针 *** 作传参,还有指针运算等等。

第二部分 调用 C

这里主要讨论函数的调用。对于结构、枚举、类的兼容性暂时没尝试。

C 标准库

好消息是,对于标准库中的 C 函数,根本不需要考虑太多导入头文件神马的。比如 strlen、putchar、vprintf。当然 vprintf 需要多说几句,后面说。

请直接 import Darwin 模块。

这些标准库函数表示为 Darwin.C.header.name。

实际上由于 Swift 模块结构是平坦的,他们均位于 Darwin 中,所以基本上是直接用的。

然后 CoreFoundation 用到了 Darwin ( @exported 导入,所以相当于这些名字也在 CoreFoundation 中)。

然后 Foundation 用到了 CoreFoundation (也是 @exported 导入。)

所以其实你导入 Foundation 的时候,这些 C 函数都是直接可用的。比如 putchar 一类。

多说一句,Cocoa 当然也包含 Foundation。

C 函数

好吧假设你有个牛逼到顶天的算法是 C 写的,不对,假设是别人写的,牛逼到你只能凑合用却看不懂然后自己也写不出没空迁移的地步。

我们直接创建一个 Swift 项目,然后 New file,添加一个 .c 文件。

这时候 Xcode 会d出对话框询问是否配置 BrIDge header,确认就可以了。也可以手动添加 BrIDge header,位置在项目的 Build Settings 中的 Swift Compiler – Code Generation 子项里。指向你的 BrIDge header 文件名就可以了。

一般这个文件是 Projectname-BrIDging-header.h。情况基本和与 Objective-C 混编没区别。

剩下的工作就很简单了。在 .c 文件填上传说中的牛逼算法。在 Projectname-BrIDging-header.h 中加上该函数原型或者引入相关的头文件。

在 Swift 中调用的名字和 C 名字一样就可以了,比如你定义了一个 int mycsort() 那么在 Swift 中就是 func mycsort() -> CInt。

这时候问题来了。一个漂亮的问题。

我的 C 函数名字和 Swift 标准库冲突怎么办?比如我定义了一个函数就叫 println,我们知道 Swift 里也有个 println。

这样,如果直接调用会提示 Ambiguous use of 'println'。没辙了么?

这里有个我发现的 Undocumented Featuer 或者说是 Undocumented Attribute。你转载好歹提下我好吧。(发现方法是通过 Xcode 查看定义,然后通过 nm 命令发现符号,对照 llvm ir 确认的。)

那就是 @asmname("func_name_in_c")。用于函数声明前。使用方法:

      intprintln(){....}  
    @asmname("println")funcc_println()->CInt//声明,不需要{}函数体    c_println()//调用  

也就是 C 中的同名函数,我们可以给赋予一个别名,然后正常调用。这么一看就基本没有问题了。至于类型问题,待会说,详细说。

C Framework

很多时候我们拿到的是第三方库,格式大概是个 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,U sizeof 运算相等就可以直接转换。这个在之前的标准库函数里有提到。调用 C 代码的利器!

基本类型对应

int => CInt

char => CChar / CSignedChar

char* => CString

unsigned long = > CUnsignedLong

wchar_t => CWIDeChar

double => CDouble

T* => CMutablePointer

voID* => CMutableVoIDPointer

const T* => CConstPointer

const voID* => 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 *,实现了 StringliteralConvertible 和 LogicValue。可以从字符串常量直接赋值获得 CString。LogicValue 也就是是 if a_c_str {},实际是用于判断是否为 NulL,可用,但是不稳定,老 crash。 结尾的 c 字符串,实际上似乎还看到了判断是否 ASCII 的选项,但是没试出来用法。

运算符支持 ==,判断两字符串是否相当,猜测实际是 strcmp 实现,对比 NulL 会 crash。Orz。

CString 和 String 的转换通过一个 extension 实现,也是很方便。

extensionString{

    static    funcfromCString(cs:CString)->Stringstatic    funcfromCString(up:UnsafePointer<CChar>)->String}    //还有两个方便的工具方法。Rust背景的同学一定仰天长啸。太相似了。    extensionString{    funcwithCString<Result>(f:(CString)->Result)->Result    funcwithCString<Result>(f:(UnsafePointer<CChar>)->Result)->Result    }    在我们的 BrIDging header 头文件中 char * 的类型会对应为 UnsafePointer<CChar>,而实际上 CString 更适合。所以在 Swift 代码中,往往我们要再次申明下这个函数。或者用一个函数封装下,转换成我们需要的类型。  

例如,假设在 BrIDging header 中我们声明了 char * foo();,那么,在 Swift 代码中我们可以用上面提到的方法:

@asmname(

    "foo")funcc_foo()->CString//注意这里没有{},只是声明    letret=c_foo()    当然也可以直接调用原始函数然后类型转换:  

letraw=foo()

    //UnsafePointer<Int8><=>UnsafePointer<CChar>letret=String.fromCString(ret)    如果这里要转成 CString 就略复杂了,因为 CString 构造函数接受的参数是 UnsafePointer<UInt8>, 而 CChar 是 Int8 的别名,所以还牵扯到 Genrics 类型转换,不够方便。  

如果非要作为 CString 处理,可以用 reinterpretCast(),直接转换。但是请一定要知道自己在转换什么,确保类型的 sizeof 相同,确保转换本身有意义。

例如获得环境变量字符串:

letkey=

    "PATH"//这里相当于把UnsafePointer<CChar>转为了UnsafePointer<UInt8>然后到CString    letpath_str:CString=reinterpretCast(key.withCString(getenv))    Unmanaged  

这个先挖坑,随后填上。

VaList

这个也是坑,随后填上。

第三部分 C 调用 Swift

如果项目里加入了 C 文件,那么它可以调用我们的 Swift 函数么?答案是可以的,而且令人吃惊地透明。这也许是因为 Apple 所宣传的,Small Runtime 概念吧。极小的语言运行时。

和 Objective-C 混编类似,配置好 BrIDging header 的项目,在 .c .h .m 文件中都可以使用一个叫做 Projectname-Swift.h 的头文件,其中包含 Swift 代码导出的函数等。

参考之前的 Objective-C 和 C 交互我们可以知道,说到底交互就是链接过程,只要链接的时候能找到符号就可以。

不过不能高兴太早,Swift 是带类、枚举、协议、多态、泛型等的高级语言,符号处理明显要比 C 中的复杂的多,现代语言一般靠 name mangle 来解决这个问题。也就是说一个 Swift 函数,在编译到 .o 的时候,名字就不是原来那么简单了。比如 __TFV5hello4Rectg9subscriptFOS_9DirectionSi 这样的名字。

Xcode 自带了个工具, 可以查看这些 mangled name 到底是什么东西:

xcrunswift-demangle__TFV5hello4Rectg9subscriptFOS_9DirectionSi

    _TFV5hello4Rectg9subscriptFOS_9DirectionSi--->hello.Rect.subscript.getter(hello.Direction)->Swift.Int    当我们从 C 调用的时候,应该规避这样的名字。还记得前面的 @asmname 么?没错,它可以用于指定 Swift 函数的符号名,我猜测应该有指定 mangled name 的作用,但是不是特别确定。  

这里随便指定个例子先。

@asmname(

    "say_hello")funcsay_hello()->Double{"Thisissay_hello()inswift"    println()return    3.14}    然后在 .c 文件中:  

#include<Projectname-Swift.h>

        double    externsay_hello();    int    some_func(){//orcaptureitsvalueandprocessit    say_hello();return    0}    对于函数而言 extern 必须手动加上,对于 class 、 protocol ,会在生成的头文件里。  

按照这个思路,其实很容易实现 Swift 调用 C 中调用了 Swift 函数的函数。这意味着,可以通过简单的方法封装支持向 C 传递 Swift block 作为回调函数。难度中上,对于有过类似扩展编写经验的人来说很简单。

第四部分 编译过程

其实调用基本就这么多, Objective-C 那篇文章中说的编译过程同样有效。我 C-c C-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 的。字长相等。

“In computer programming,an opaque pointer is a special case of an opaque data type,a datatype declared to be a pointer to a record or data structure of some unspecifIEd type.”—— 来自 Wikipedia

几乎没有任何 *** 作方法,不带类型,主要用于 BrIDging header 中表示 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 的处理就主要用它。Swift NSError Internals(解析 Swift 对 NSError *** 作)

内部实现用了语言内置特性,从名字也可以看出来,这个应该是非常棒的一个指针,可以帮助管理内存,逼格也高。内存直接对应 RawPointer 可以传递给 C 函数。

直接从 &T 类型获得,使用方法比较诡异,建议参考文章

CMutablePointer CConstPointer

分别对应于 C 中的 T *、const T *。不可直接传递给 C 函数,因为表示结构里还有一个 owner 域,应该是用来自动管理生命周期的。sizeof *** 作返回 16。但是可以有隐式类型转换。

*** 作方法主要是 func withUnsafePointer<U>(f: UnsafePointer<T> -> U) -> U,用 Trailing Closure 语法非常方便。

CMutableVoIDPointer CConstVoIDPointer

分别对应于 C 中的 voID *、const voID *。其他内容同上一种。

小结指针

以上 7 种指针类型可以分未两类,我给他们起名为 第一类指针 和 第二类指针 。(你看我在黑马克思耶,算了这个梗太深,参考马克思主义政治经济学)

-可以直接用于 C 函数声明的 第一类指针

copaquePointer UnsafePointer<T> autoreleasingUnsafePointer<T>

是对 RawPointer 的封装,直接对应于 C 的指针,它们的 sizeof 都是单位字长

-不可用于声明 第二类指针

CMutablePointer<T> CConstPointer<T> CMutableVoIDPointer CConstVoIDPointer

直接从 Swift 对象的引用获得(一个隐藏特性,引用隐式转换)(主要构造方法)

包含了一个 owner 字段,可以管理生命周期,理论上在 Swift 中使用

通过 .withUnsafePointer 方法调用

所有指针都实现了 LogicValue 协议,可以直接 if a_pointer 判断是否为 NulL。

nil 类型实现了到所有指针类型的隐式类型转换,等价于 C 中的 `NulL,可以直接判断。

什么时候用什么?这个问题我也在考虑中,以下是我的建议。

对应复杂结构体,不 *** 作结构体字段的: copaquePointer 例如 CURL *

日常 *** 作: UnsafePointer<T>

同时需要在 Swift 和 C 中 *** 作结构体字段,由 Swift 管理内存:autoreleasingUnsafePointer<T>

Swift 中创建对象,传递给 C: 第二类指针

工具类型

CVararg CVaListPointer VaListBuilder

用于处理 C 语言中的可变参数 vaList 函数。

protocolCVararg{

    funcencode()->Word[]    }    表示该类型可以作为可变参数,相当多的类型都实现了这个。  

structCVaListPointer{

    var    value:UnsafePointer<VoID>init(fromUnsafePointerfrom:UnsafePointer<VoID>)    @conversionfunc__conversion()->CMutableVoIDPointer    }    对应于 C,直接给 C 函数传递,声明、定义时使用。  

VaListBuilder{

classinit()    funcappend(arg:CVararg)    funcva_List()->CVaListPointer    }    工具类,方便地创建 CVaListPointer。  

还有一些工具函数:

funcgetVaList(args:CVararg[])->CVaListPointer

    funcwithVaList<R>(args:CVararg[],f:(CVaListPointer)->R)->R    funcwithVaList<R>(builder:VaListBuilder,f:(CVaListPointer)->R)->R    非常方便。  

UnsafeArray

structUnsafeArray<T>:Collection,Generator{

    var    startIndex:Int{get}var    endindex:Int{get}subscript(i:Int)->T{get}    init(start:UnsafePointer<T>,length:Int)    funcnext()->T?    funcgenerate()->UnsafeArray<T>    }    处理 C 数组的工具类型,可以直接 for-in 处理。当然,只读的,略可惜。  

Unmanaged

structUnmanaged<T>{

    var    _value:Tinit(_private:T)    funcfromOpaque(value:copaquePointer)->Unmanaged<T>    functoOpaque()->copaquePointer    static    funcpassRetained(value:T)->Unmanaged<T>static    funcpassUnretained(value:T)->Unmanaged<T>functakeUnretainedValue()->T    functakeRetainedValue()->T    funcretain()->Unmanaged<T>    funcrelease()    funcautorelease()->Unmanaged<T>    }    顾名思义,手动管理 RC 的。避免 Swift 插入的 ARC 代码影响程序逻辑。  

C 头文件的导入行为

宏定义

数字常量 CInt,CDouble (带类型后缀则为对应类型,如 1.0f ) 字符常量 CString 其他宏 展开后,无定义

枚举 enum

创建 enum 类型,并继承自 CUnsignedInt 或 CInt (enum 是否有负初始值)

可以通过 .value 访问。

结构体 struct

创建 struct 类型,只有默认 init ,需要加上所有结构体字段名创建。

可变参数函数

转为 CVaListPointer。手动重声明更好。这里举 Darwin 模块的例子说。

funcvprintf(_:CString,_:CVaListPointer)->CInt

    从 C 调用 Swift  

只能调用函数。

之前说过,用 @asmname("name") 指定 mangled name 即可。

然后 C 语言中人工声明下函数。很可惜自动导出头文件不适用于 C 语言,只适用于 Objective-C 。

目测暂时无法导出结构体,因为 Swift 闭源不提供相关头文件。靠猜有风险。

全局变量不支持用 @asmname("name") 控制导出符号名。目测可以尝试用 mangled name 访问,但是很不方便。

示例

我尝试调用了下 libcurl 。

项目地址在andelf/curl-swift

BrIDging header 只写入 #include<curl/curl.h> 即可。包含编译脚本(就一句命令)。

@asmname(

    "curl_easy_setopt")funccurl_easy_setopt(curl:copaquePointer,option:CURLoption,param:CString)->CURLcode"curl_easy_setopt"    @asmname()funccurl_easy_setopt(curl:copaquePointer,param:CBool)->CURLcode    lethandle=curl_easy_init()        //thisshouldbeaconstcstring.curl_easy_perform()willusethis.    "http://www.baIDu.com"    leturl:CString=    curl_easy_setopt(handle,CURLOPT_URL,url)    true    curl_easy_setopt(handle,CURLOPT_VERBOSE,)    letret=curl_easy_perform(handle)    leterror=curl_easy_strerror(ret)    "error=\(error)"    println()值得注意的是其中对单个函数的多态声明, curl_easy_setopt 实际上第三个参数是 voID *。  

以及对 url 的处理,实际上 libcurl 要求设置的 url 参数一直保持到 curl_easy_perform 时,所以这里用 withUnsafePointer 或者 withCString 是不太可取的方法。实际上或许可以用 Unmanaged<T> 来解决。

总结

我觉得说这么多,调用 C 已经再没有别的内容可说了。其他的就是编程经验的问题,比如如何实现 C 回调 Swift 或者 Swift 回调 C 。可以参考其他语言的做法。解决方法不只一种。

[+++]

总结

以上是内存溢出为你收集整理的简析Swift和C的交互全部内容,希望文章能够帮你解决简析Swift和C的交互所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
简析Swift和C的交互_app_内存溢出

简析Swift和C的交互

简析Swift和C的交互,第1张

概述原文链接:http://www.cocoachina.com/applenews/devnews/2014/0619/8884.html 其中 @asmname 的两个用法源于我的猜测验证,用到了 Xcode, lldb, nm, llvm ir 等工具或格式。   其中 name mangling 部分源自 WWDC。   相关的分析主要基于我 dump 出的 Swift 标准库声明代码,位于

原文链接:http://www.cocoachina.com/applenews/devnews/2014/0619/8884.HTML


其中 @asmname 的两个用法源于我的猜测验证,用到了 Xcode,lldb,nm,llvm ir 等工具或格式。

其中 name mangling 部分源自 WWDC。

相关的分析主要基于我 dump 出的 Swift 标准库声明代码,位于 我的Github andelf/Defines-Swift

之前好像简单说过 Swift 和 Objective-C 的交互问题。其实我们也可以用 Swift 调用纯 C 代码或者基于 C 的第三方库。(这里不会也永远不会考虑 C++ 的情况,因为不支持,不过可以用 C 写 wrapper,这个没有任何问题。)

Swift 官方文档中,以及那本已经被迅速翻译为中文的 ibooks 书中,都提到了 Swift 调用 Objective-C 和 C 是有很好支持的。不过没有细节。

本内容包括 Swift 调用 C 和相应的 C 调用 Swift,项目混编。

这里主要面向 MacOSX 应用。iOS 或许可以适用。

先复习下区别。

第一部分 预备知识

语言区别

说到底就是 C 少了很多东西。但是多了个指针。

对于 C 来说,最头疼的莫过于指针,而 Swift 是一门没有指针的语言。是不是要吐血?相对来说指针不但代表者指针 *** 作传参,还有指针运算等等。

第二部分 调用 C

这里主要讨论函数的调用。对于结构、枚举、类的兼容性暂时没尝试。

C 标准库

好消息是,对于标准库中的 C 函数,根本不需要考虑太多导入头文件神马的。比如 strlen、putchar、vprintf。当然 vprintf 需要多说几句,后面说。

请直接 import Darwin 模块。

这些标准库函数表示为 Darwin.C.header.name。

实际上由于 Swift 模块结构是平坦的,他们均位于 Darwin 中,所以基本上是直接用的。

然后 CoreFoundation 用到了 Darwin ( @exported 导入,所以相当于这些名字也在 CoreFoundation 中)。

然后 Foundation 用到了 CoreFoundation (也是 @exported 导入。)

所以其实你导入 Foundation 的时候,这些 C 函数都是直接可用的。比如 putchar 一类。

多说一句,Cocoa 当然也包含 Foundation。

C 函数

好吧假设你有个牛逼到顶天的算法是 C 写的,不对,假设是别人写的,牛逼到你只能凑合用却看不懂然后自己也写不出没空迁移的地步。

我们直接创建一个 Swift 项目,然后 New file,添加一个 .c 文件。

这时候 Xcode 会d出对话框询问是否配置 BrIDge header,确认就可以了。也可以手动添加 BrIDge header,位置在项目的 Build Settings 中的 Swift Compiler – Code Generation 子项里。指向你的 BrIDge header 文件名就可以了。

一般这个文件是 Projectname-BrIDging-header.h。情况基本和与 Objective-C 混编没区别。

剩下的工作就很简单了。在 .c 文件填上传说中的牛逼算法。在 Projectname-BrIDging-header.h 中加上该函数原型或者引入相关的头文件。

在 Swift 中调用的名字和 C 名字一样就可以了,比如你定义了一个 int mycsort() 那么在 Swift 中就是 func mycsort() -> CInt。

这时候问题来了。一个漂亮的问题。

我的 C 函数名字和 Swift 标准库冲突怎么办?比如我定义了一个函数就叫 println,我们知道 Swift 里也有个 println。

这样,如果直接调用会提示 Ambiguous use of 'println'。没辙了么?

这里有个我发现的 Undocumented Featuer 或者说是 Undocumented Attribute。你转载好歹提下我好吧。(发现方法是通过 Xcode 查看定义,然后通过 nm 命令发现符号,对照 llvm ir 确认的。)

那就是 @asmname("func_name_in_c")。用于函数声明前。使用方法:

      intprintln(){....}  
    @asmname("println")funcc_println()->CInt//声明,不需要{}函数体    c_println()//调用  

也就是 C 中的同名函数,我们可以给赋予一个别名,然后正常调用。这么一看就基本没有问题了。至于类型问题,待会说,详细说。

C Framework

很多时候我们拿到的是第三方库,格式大概是个 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,U sizeof 运算相等就可以直接转换。这个在之前的标准库函数里有提到。调用 C 代码的利器!

基本类型对应

int => CInt

char => CChar / CSignedChar

char* => CString

unsigned long = > CUnsignedLong

wchar_t => CWIDeChar

double => CDouble

T* => CMutablePointer

voID* => CMutableVoIDPointer

const T* => CConstPointer

const voID* => 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 *,实现了 StringliteralConvertible 和 LogicValue。可以从字符串常量直接赋值获得 CString。LogicValue 也就是是 if a_c_str {},实际是用于判断是否为 NulL,可用,但是不稳定,老 crash。 结尾的 c 字符串,实际上似乎还看到了判断是否 ASCII 的选项,但是没试出来用法。

运算符支持 ==,判断两字符串是否相当,猜测实际是 strcmp 实现,对比 NulL 会 crash。Orz。

CString 和 String 的转换通过一个 extension 实现,也是很方便。

extensionString{

    static    funcfromCString(cs:CString)->Stringstatic    funcfromCString(up:UnsafePointer<CChar>)->String}    //还有两个方便的工具方法。Rust背景的同学一定仰天长啸。太相似了。    extensionString{    funcwithCString<Result>(f:(CString)->Result)->Result    funcwithCString<Result>(f:(UnsafePointer<CChar>)->Result)->Result    }    在我们的 BrIDging header 头文件中 char * 的类型会对应为 UnsafePointer<CChar>,而实际上 CString 更适合。所以在 Swift 代码中,往往我们要再次申明下这个函数。或者用一个函数封装下,转换成我们需要的类型。  

例如,假设在 BrIDging header 中我们声明了 char * foo();,那么,在 Swift 代码中我们可以用上面提到的方法:

@asmname(

    "foo")funcc_foo()->CString//注意这里没有{},只是声明    letret=c_foo()    当然也可以直接调用原始函数然后类型转换:  

letraw=foo()

    //UnsafePointer<Int8><=>UnsafePointer<CChar>letret=String.fromCString(ret)    如果这里要转成 CString 就略复杂了,因为 CString 构造函数接受的参数是 UnsafePointer<UInt8>, 而 CChar 是 Int8 的别名,所以还牵扯到 Genrics 类型转换,不够方便。  

如果非要作为 CString 处理,可以用 reinterpretCast(),直接转换。但是请一定要知道自己在转换什么,确保类型的 sizeof 相同,确保转换本身有意义。

例如获得环境变量字符串:

letkey=

    "PATH"//这里相当于把UnsafePointer<CChar>转为了UnsafePointer<UInt8>然后到CString    letpath_str:CString=reinterpretCast(key.withCString(getenv))    Unmanaged  

这个先挖坑,随后填上。

VaList

这个也是坑,随后填上。

第三部分 C 调用 Swift

如果项目里加入了 C 文件,那么它可以调用我们的 Swift 函数么?答案是可以的,而且令人吃惊地透明。这也许是因为 Apple 所宣传的,Small Runtime 概念吧。极小的语言运行时。

和 Objective-C 混编类似,配置好 BrIDging header 的项目,在 .c .h .m 文件中都可以使用一个叫做 Projectname-Swift.h 的头文件,其中包含 Swift 代码导出的函数等。

参考之前的 Objective-C 和 C 交互我们可以知道,说到底交互就是链接过程,只要链接的时候能找到符号就可以。

不过不能高兴太早,Swift 是带类、枚举、协议、多态、泛型等的高级语言,符号处理明显要比 C 中的复杂的多,现代语言一般靠 name mangle 来解决这个问题。也就是说一个 Swift 函数,在编译到 .o 的时候,名字就不是原来那么简单了。比如 __TFV5hello4Rectg9subscriptFOS_9DirectionSi 这样的名字。

Xcode 自带了个工具, 可以查看这些 mangled name 到底是什么东西:

xcrunswift-demangle__TFV5hello4Rectg9subscriptFOS_9DirectionSi

    _TFV5hello4Rectg9subscriptFOS_9DirectionSi--->hello.Rect.subscript.getter(hello.Direction)->Swift.Int    当我们从 C 调用的时候,应该规避这样的名字。还记得前面的 @asmname 么?没错,它可以用于指定 Swift 函数的符号名,我猜测应该有指定 mangled name 的作用,但是不是特别确定。  

这里随便指定个例子先。

@asmname(

    "say_hello")funcsay_hello()->Double{"Thisissay_hello()inswift"    println()return    3.14}    然后在 .c 文件中:  

#include<Projectname-Swift.h>

        double    externsay_hello();    int    some_func(){//orcaptureitsvalueandprocessit    say_hello();return    0}    对于函数而言 extern 必须手动加上,对于 class 、 protocol ,会在生成的头文件里。  

按照这个思路,其实很容易实现 Swift 调用 C 中调用了 Swift 函数的函数。这意味着,可以通过简单的方法封装支持向 C 传递 Swift block 作为回调函数。难度中上,对于有过类似扩展编写经验的人来说很简单。

第四部分 编译过程

其实调用基本就这么多, Objective-C 那篇文章中说的编译过程同样有效。我 C-c C-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 的。字长相等。

“In computer programming,an opaque pointer is a special case of an opaque data type,a datatype declared to be a pointer to a record or data structure of some unspecifIEd type.”—— 来自 Wikipedia

几乎没有任何 *** 作方法,不带类型,主要用于 BrIDging header 中表示 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 的处理就主要用它。Swift NSError Internals(解析 Swift 对 NSError *** 作)

内部实现用了语言内置特性,从名字也可以看出来,这个应该是非常棒的一个指针,可以帮助管理内存,逼格也高。内存直接对应 RawPointer 可以传递给 C 函数。

直接从 &T 类型获得,使用方法比较诡异,建议参考文章

CMutablePointer CConstPointer

分别对应于 C 中的 T *、const T *。不可直接传递给 C 函数,因为表示结构里还有一个 owner 域,应该是用来自动管理生命周期的。sizeof *** 作返回 16。但是可以有隐式类型转换。

*** 作方法主要是 func withUnsafePointer<U>(f: UnsafePointer<T> -> U) -> U,用 Trailing Closure 语法非常方便。

CMutableVoIDPointer CConstVoIDPointer

分别对应于 C 中的 voID *、const voID *。其他内容同上一种。

小结指针

以上 7 种指针类型可以分未两类,我给他们起名为 第一类指针 和 第二类指针 。(你看我在黑马克思耶,算了这个梗太深,参考马克思主义政治经济学)

-可以直接用于 C 函数声明的 第一类指针

copaquePointer UnsafePointer<T> autoreleasingUnsafePointer<T>

是对 RawPointer 的封装,直接对应于 C 的指针,它们的 sizeof 都是单位字长

-不可用于声明 第二类指针

CMutablePointer<T> CConstPointer<T> CMutableVoIDPointer CConstVoIDPointer

直接从 Swift 对象的引用获得(一个隐藏特性,引用隐式转换)(主要构造方法)

包含了一个 owner 字段,可以管理生命周期,理论上在 Swift 中使用

通过 .withUnsafePointer 方法调用

所有指针都实现了 LogicValue 协议,可以直接 if a_pointer 判断是否为 NulL。

nil 类型实现了到所有指针类型的隐式类型转换,等价于 C 中的 `NulL,可以直接判断。

什么时候用什么?这个问题我也在考虑中,以下是我的建议。

对应复杂结构体,不 *** 作结构体字段的: copaquePointer 例如 CURL *

日常 *** 作: UnsafePointer<T>

同时需要在 Swift 和 C 中 *** 作结构体字段,由 Swift 管理内存:autoreleasingUnsafePointer<T>

Swift 中创建对象,传递给 C: 第二类指针

工具类型

CVararg CVaListPointer VaListBuilder

用于处理 C 语言中的可变参数 vaList 函数。

protocolCVararg{

    funcencode()->Word[]    }    表示该类型可以作为可变参数,相当多的类型都实现了这个。  

structCVaListPointer{

    var    value:UnsafePointer<VoID>init(fromUnsafePointerfrom:UnsafePointer<VoID>)    @conversionfunc__conversion()->CMutableVoIDPointer    }    对应于 C,直接给 C 函数传递,声明、定义时使用。  

VaListBuilder{

classinit()    funcappend(arg:CVararg)    funcva_List()->CVaListPointer    }    工具类,方便地创建 CVaListPointer。  

还有一些工具函数:

funcgetVaList(args:CVararg[])->CVaListPointer

    funcwithVaList<R>(args:CVararg[],f:(CVaListPointer)->R)->R    funcwithVaList<R>(builder:VaListBuilder,f:(CVaListPointer)->R)->R    非常方便。  

UnsafeArray

structUnsafeArray<T>:Collection,Generator{

    var    startIndex:Int{get}var    endindex:Int{get}subscript(i:Int)->T{get}    init(start:UnsafePointer<T>,length:Int)    funcnext()->T?    funcgenerate()->UnsafeArray<T>    }    处理 C 数组的工具类型,可以直接 for-in 处理。当然,只读的,略可惜。  

Unmanaged

structUnmanaged<T>{

    var    _value:Tinit(_private:T)    funcfromOpaque(value:copaquePointer)->Unmanaged<T>    functoOpaque()->copaquePointer    static    funcpassRetained(value:T)->Unmanaged<T>static    funcpassUnretained(value:T)->Unmanaged<T>functakeUnretainedValue()->T    functakeRetainedValue()->T    funcretain()->Unmanaged<T>    funcrelease()    funcautorelease()->Unmanaged<T>    }    顾名思义,手动管理 RC 的。避免 Swift 插入的 ARC 代码影响程序逻辑。  

C 头文件的导入行为

宏定义

数字常量 CInt,CDouble (带类型后缀则为对应类型,如 1.0f ) 字符常量 CString 其他宏 展开后,无定义

枚举 enum

创建 enum 类型,并继承自 CUnsignedInt 或 CInt (enum 是否有负初始值)

可以通过 .value 访问。

结构体 struct

创建 struct 类型,只有默认 init ,需要加上所有结构体字段名创建。

可变参数函数

转为 CVaListPointer。手动重声明更好。这里举 Darwin 模块的例子说。

funcvprintf(_:CString,_:CVaListPointer)->CInt

    从 C 调用 Swift  

只能调用函数。

之前说过,用 @asmname("name") 指定 mangled name 即可。

然后 C 语言中人工声明下函数。很可惜自动导出头文件不适用于 C 语言,只适用于 Objective-C 。

目测暂时无法导出结构体,因为 Swift 闭源不提供相关头文件。靠猜有风险。

全局变量不支持用 @asmname("name") 控制导出符号名。目测可以尝试用 mangled name 访问,但是很不方便。

示例

我尝试调用了下 libcurl 。

项目地址在andelf/curl-swift

BrIDging header 只写入 #include<curl/curl.h> 即可。包含编译脚本(就一句命令)。

@asmname(

    "curl_easy_setopt")funccurl_easy_setopt(curl:copaquePointer,option:CURLoption,param:CString)->CURLcode"curl_easy_setopt"    @asmname()funccurl_easy_setopt(curl:copaquePointer,param:CBool)->CURLcode    lethandle=curl_easy_init()        //thisshouldbeaconstcstring.curl_easy_perform()willusethis.    "http://www.baIDu.com"    leturl:CString=    curl_easy_setopt(handle,CURLOPT_URL,url)    true    curl_easy_setopt(handle,CURLOPT_VERBOSE,)    letret=curl_easy_perform(handle)    leterror=curl_easy_strerror(ret)    "error=\(error)"    println()值得注意的是其中对单个函数的多态声明, curl_easy_setopt 实际上第三个参数是 voID *。  

以及对 url 的处理,实际上 libcurl 要求设置的 url 参数一直保持到 curl_easy_perform 时,所以这里用 withUnsafePointer 或者 withCString 是不太可取的方法。实际上或许可以用 Unmanaged<T> 来解决。

总结

我觉得说这么多,调用 C 已经再没有别的内容可说了。其他的就是编程经验的问题,比如如何实现 C 回调 Swift 或者 Swift 回调 C 。可以参考其他语言的做法。解决方法不只一种。

总结

以上是内存溢出为你收集整理的简析Swift和C的交互全部内容,希望文章能够帮你解决简析Swift和C的交互所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1086287.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存