从 Swift 调用 C 可变参数函数

Call C variadic function from Swift

我正在尝试使用以下签名从 Swift 调用 C 函数(外部 API):

extern void k(int, const char*, ...)

我这样调用 C 函数:

func send(query: String, args: CVarArg...) {
    let query2 = UnsafeMutablePointer(mutating: query.cString(using: String.defaultCStringEncoding))
    let result = k(0, query2, args)
}

但是出现错误'k' is unavailable: Variadic function is unavailable

我不确定这是否可行,因为 C API 不接受 va_list (http://swiftdoc.org/v3.1/protocol/CVarArg/)。如果需要,我不介意在 C 中编写包装函数。

您不能调用从中获取变量参数列表的 C 函数 Swift。您需要一个带有 va_list 参数的变体 (类似于 printfvprintf):

int kv(int n, const char *s, va_list args) {
    // ...
}

原函数k可以转发到kv:

int k(int n, const char* s, ...) {
    va_list args;
    va_start(args, s);
    int result = kv(n, s, args);
    va_end(args);
    return result;
}

现在可以从 Swift 调用 kv:

func send(query: String, args: CVarArg...) {
    let result = withVaList(args) {
        kv(0, query, [=12=])
    }
}

(您可以将 Swift String 直接传递给采用 const char * 参数,编译器插入必要的代码到 创建一个临时表示为以 null 结尾的 C 字符串。)

为每个可能的参数数量创建一个包装器:

void k1(int x, const char *param1) {
    k(x, param1);
}
void k2(int x, const char *param1, const char *param2) {
    k(x, param1, param2);
}
void k3(int x, const char *param1, const char *param2, const char *param3) {
    k(x, param1, param2, param3);
}

这可能是您唯一的选择,但您可以使其更通用...

我们可以创建一个采用 va_list 的函数,对参数进行计数并调用特定的 k1k2 等变体,以提供 "nicer" API 对于 Swift:

void genericK(int x, va_list params) {
    const char * paramArray[10];        
    int numParams = 0;

    NSString* arg = nil;
    while ((arg = va_arg(params, NSString *))) {
        paramArray[numParams++] = [arg cStringUsingEncoding:[NSString defaultCStringEncoding]];
    }

    switch (numParams) {
    case 1:
        k(x, paramArray[0], NULL);
        break;
    case 2:
        k(x, paramArray[0], paramArray[1], NULL);
        break;
    case 3:
        k(x, paramArray[0], paramArray[1], paramArray[2], NULL);
        break;
    default:
        assert(false);
    }
}

然后您可以在 Swift 中创建另一个包装器,它再次采用可变数量的参数:

func k(_ x: Int32, _ params: String...) {
    withVaList(params) {
        genericK(x, [=12=])
    }
}

k(0, "testA", "testB", "testC")

解决方案有一个问题 - 您将数量或参数限制为固定数量。不过这应该不是什么大问题...