如何从Swift中调用C语言?

147

有没有办法从Swift调用C例程?

许多iOS / Apple库仅支持C语言,我仍然希望能够调用它们。

例如,我想能够从Swift调用Objective-C运行时库。

特别是,如何桥接iOS C头文件?

6个回答

114

当然可以与苹果的C库进行交互。 这里讲解了如何进行交互。

基本上,C类型、C指针等都会被翻译成Swift对象,例如在Swift中,C中的int是一个CInt

我为另一个问题构建了一个小例子,可以用作介绍如何在C和Swift之间桥接的简单说明:

main.swift

import Foundation

var output: CInt = 0
getInput(&output)

println(output)

UserInput.c

#include <stdio.h>

void getInput(int *output) {
    scanf("%i", output);
}

cliinput-Bridging-Header.h

void getInput(int *output);

这里是原始答案。


1
我是ios/swift的新手。我想在swift文件中使用来自#include <asl.h>的logging c函数。有人可以帮忙吗? - Dmitry Konovalov
1
它是否与C++,.cpp文件兼容? - Carlos.V
1
要直接使用C++文件,您应该创建一个Objective-C包装器。您需要在实现中(应该单独驻留在.mm文件中)包含Objective-C++代码(它只是一个文件中的Objective-C和C++),而接口(应该在自己的.h头文件中)应该包含纯Objective-C代码,因此您必须在实现中将C++类型转换为Objective-C类型,以将它们暴露给Swift。然后,您可以使用Objective-C桥接头导入该头文件。 - William T Froggard
2
不正确。您可以与任何 C 库进行交互,而不仅仅是限于 Apple 的库。 - Slipp D. Thompson
更新的文档链接 https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis - MechEthan

9
编译器将C API转换为Swift,就像它为Objective-C所做的那样。
import Cocoa

let frame = CGRect(x: 10, y: 10, width: 100, height: 100)

import Darwin

for _ in 1..10 {
    println(rand() % 100)
}

请参阅文档中的与Objective-C API交互


1
谢谢提供这个例子,你说得对,我能看出那可能会有用。然而它并没有真正回答我的问题,我的问题是如何调用苹果的C库。但这绝对是最接近的。 - Blaze
请在该文档的其他地方查找。例如,CoreFoundation风格的“对象”(CFTypeRef)将被转换为Swift对象。大多数ObjCRuntime.h函数对于Swift来说并没有意义。 - rickster
大多数ObjCRuntime.h函数对Swift来说没有意义,为什么这么说呢?我想我需要找到一种导入头文件并桥接的方法。虽然这似乎有些笨拙,但我想这是正确的方法。 - Blaze

7

如果你和我一样对XCode很陌生,并且想尝试Leandro的答案中发布的片段:

  1. 文件->新建->项目
  2. 选择命令行工具作为项目预设并将项目命名为"cliinput"
  3. 右键单击项目导航器(左侧的蓝色面板)并选择"新建文件..."
  4. 在下拉对话框中将文件命名为"UserInput"。取消选中"同时创建头文件"的框。一旦你点击"下一步",XCode会问你是否要为你创建Bridging-Header.h文件。选择"是"。
  5. 复制并粘贴Leandro答案中的代码。一旦你点击播放按钮,它应该在终端中编译并运行,在xcode中内置于底部面板中。如果你在终端中输入一个数字,将返回一个数字。

4
这篇文章 同样对使用clang的模块支持进行此操作有很好的解释。 该文章的主题是如何为CommonCrypto项目执行此操作,但通常适用于您想要从Swift中使用的任何其他C库。
我曾经尝试过为zlib执行此操作。 我创建了一个新的iOS框架项目,并创建了一个名为zlib的目录,其中包含一个module.modulemap文件,其中包含以下内容:
module zlib [system] [extern_c] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/zlib.h"
    export *
}

然后在Targets -> Link Binary With Libraries下,选择添加项目并添加libz.tbd。

您可能希望此时进行构建。

然后我能够编写以下代码:

import zlib

public class Zlib {
    public class func zlibCompileFlags() -> UInt {
        return zlib.zlibCompileFlags()
    }
}

除非在上述情况下我将Swift类函数命名与C函数相同,否则您无需在前面放置zlib库名称。如果没有限定符,Swift函数会被重复调用,直到应用程序停止。


3
在C++中,会出现以下错误提示:
  "_getInput", referenced from: 

您需要一个C++头文件。 在您的函数中添加C语言链接,然后在bridge-header中包含头文件:

Swift 3

UserInput.h

#ifndef USERINPUT_H
#define USERINPUT_H    

#ifdef __cplusplus
extern "C"{
#endif

getInput(int *output);

#ifdef __cplusplus
}
#endif

UserInput.c

#include <stdio.h>

void getInput(int *output) {
    scanf("%i", output);
}    

main.swift

import Foundation
var output: CInt = 0
getInput(&output)
print(output)

cliinput-Bridging-Header.h

#include "UserInput.h"

以下是涉及IT技术的翻译内容,这里有原始视频


如果不起作用,请尝试在您的桥接头文件中添加__OBJC检查,例如 #ifdef __OBJC @import UIKit; #endif - Chris Yim

1

当涉及指针时,情况似乎有所不同。以下是我调用C POSIX read系统调用的部分代码:

enum FileReadableStreamError : Error {
case failedOnRead
}

// Some help from: https://dev59.com/slkT5IYBdhLWcg3wKsaq
// and https://gist.github.com/kirsteins/6d6e96380db677169831
override func readBytes(size:UInt32) throws -> [UInt8]? {
    guard let unsafeMutableRawPointer = malloc(Int(size)) else {
        return nil
    }

    let numberBytesRead = read(fd, unsafeMutableRawPointer, Int(size))

    if numberBytesRead < 0 {
        free(unsafeMutableRawPointer)
        throw FileReadableStreamError.failedOnRead
    }

    if numberBytesRead == 0 {
        free(unsafeMutableRawPointer)
        return nil
    }

    let unsafeBufferPointer = UnsafeBufferPointer(start: unsafeMutableRawPointer.assumingMemoryBound(to: UInt8.self), count: numberBytesRead)

    let results = Array<UInt8>(unsafeBufferPointer)
    free(unsafeMutableRawPointer)

    return results
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接