Быстрое соединение 2D-массива с типом UnsafePointer‹UnsafeMutablePointer‹Double›?›?

Привет, я пытаюсь обернуть C API с помощью Swift 4.

Swift импортировал функцию со следующей подписью.

public typealias indicator = @convention(c) (Int32, UnsafePointer<UnsafePointer<Double>?>?, UnsafePointer<Double>?, UnsafePointer<UnsafeMutablePointer<Double>?>?) -> Int32

согласно документам библиотеки C, подпись выглядит следующим образом:

int indicator(int size, double const *const *inputs, double const *options, double *const *outputs);

Стоит отметить, что int возвращается из функции в стиле c типа ошибки функции, фактический возврат находится в указателе outputs

Итак, если я создам следующие типы Swift

let inputs: [[Double]] = [] let options: [Double] = [] var outputs: [[Double]] = []

с некоторыми соответствующими значениями, тогда я смогу сделать что-то вроде: (примечание info.pointee.indicator - это импортированная функция)

internal func calculateIndicator(options opts: [Double], input inputs: [[Double]], output outPuts: inout [[Double]]) -> [[Double]]? {
    guard let sz = inputs.first?.count else {fatalError("Must supply a [[Double]] input param")}

    let inputPointer = UnsafePointer<[Double]>(inputs)
    let optionsPointer = UnsafePointer<Double>(opts)
    var outputPointer = UnsafeMutablePointer<[Double]>(&outPuts)

    let val = info.pointee.indicator(Int32(sz), inputPointer, optionsPointer, outputPointer)

    // do something with the outputs and return the values
}

однако компилятор жалуется на следующую ошибку:

Cannot invoke 'indicator' with an argument list of type '(Int32, UnsafePointer<[Double]>, UnsafePointer<Double>, UnsafeMutablePointer<[Double]>)'

Это имеет смысл, поскольку я передаю неправильные типы (я думаю).

Итак, если оставить в стороне вопросы управления памятью, как мне преобразовать типы [[Double]], например, в указатель UnsafePointer<UnsafeMutablePointer<Double>>?

в соответствии с документами здесь Вызов функций с параметрами указателя Я должен быть в состоянии сделать это с неявным соединением, но, похоже, нет, возможно, мне следует просто создать типы указателей, а не пытаться преобразовать их из Swift?

Заранее спасибо, я уверен, что упускаю что-то простое.

Сам C API выглядит следующим образом:

typedef int (*indicator_function)(int size,
  double const *const *inputs,
  double const *options,
  double *const *outputs);

typedef struct indicator_info {
  char *name;
  char *full_name;
  indicator_start_function start;
  indicator_function indicator;
  int type, inputs, options, outputs;
  char *input_names[MAXINDPARAMS];
  char *option_names[MAXINDPARAMS];
  char *output_names[MAXINDPARAMS];
} indicator_info;

Доступ к функции indicator осуществляется через структуру выше.

Данный экземпляр индикаторной функции выглядит следующим образом

int add(int size,
  TI_REAL const *const *inputs,
  TI_REAL const *options,
  TI_REAL *const *outputs);

person lbdl    schedule 20.12.2018    source источник
comment
«Swift импортировал функцию со следующей сигнатурой» Нет. Согласно этому, indicator не является функцией, которую вы вызываете. Это тип (сигнатура) функции, которую вы передаете. Покажите C API, где мы на самом деле должны его передать.   -  person matt    schedule 20.12.2018
comment
ну, это псевдоним типа для целой группы функций, каждая из которых имеет следующую сигнатуру public func indicator_abs(_ size: Int32, _ inputs: UnsafePointer<UnsafePointer<Double>?>!, _ options: UnsafePointer<Double>!, _ outputs: UnsafePointer<UnsafeMutablePointer<Double>?>!) -> Int32. Я отредактировал вопрос.   -  person lbdl    schedule 20.12.2018


Ответы (2)


проблема здесь заключается в том, что C API требует этих параметров double *const *outputs и double const *const *inputs или, в терминах Swift, [[Double]] типов.

Эта сигнатура функции C импортируется Swift в следующие типы соответственно.

UnsafePointer<UnsafeMutablePointer<Double>?> 
UnsafePointer<UnsafePointer<Double>?>

Хотя легко перейти от [T] к UnsafePointer<T>, не так просто перейти к громоздким UnsafePointer<UnsafePointer<T>> и UnsafePointer<UnsafeMutablePointer<T>>. Также нет никакой документации, связанной с этими преобразованиями, которую я могу найти.

Я нашел отличный пост в блоге об указателях на UInt8 массивов Оле Бегеманна, который дал мне большую часть пути, блог Передача массива строк из Swift в C.

При этом он создает указатель UnsafeMutableBufferPointer на тип [String], а затем повторно связывает память, как показано ниже, а затем продолжает использовать смещения массивов CChar, вы можете прочитать об этом в упомянутой выше статье.

public func withArrayOfCStrings<R>(
   _ args: [String],
   _ body: ([UnsafeMutablePointer<CChar>?]) -> R) -> R {
      let argsCounts = Array(args.map { $0.utf8.count + 1 })
      let argsOffsets = [ 0 ] + scan(argsCounts, 0, +)
      let argsBufferSize = argsOffsets.last!

      var argsBuffer: [UInt8] = []
      argsBuffer.reserveCapacity(argsBufferSize)
      for arg in args {
          argsBuffer.append(contentsOf: arg.utf8)
          argsBuffer.append(0)
      }

     return argsBuffer.withUnsafeMutableBufferPointer { (argsBuffer) in
         let ptr = UnsafeMutableRawPointer(argsBuffer.baseAddress!).bindMemory(
         to: CChar.self, capacity: argsBuffer.count)
         var cStrings: [UnsafeMutablePointer<CChar>?] = argsOffsets.map { ptr + $0 }
         cStrings[cStrings.count - 1] = nil
         return body(cStrings)
     }
}

Поскольку экранирование мостовых указателей является неопределенным поведением в соответствии с документами, необходимо выполнить сопряжение и вызов внутри замыкания, как предлагает Оле Бергманн в своей статье.

Для этого создадим аналогичную функцию:

func indicatorWithArrays<R>(inputs ins:[[Double]],
                            options opts: [Double],
                            outputs out: [[Double]],
                            ti body: ([UnsafePointer<Double>?], 
                                      UnsafePointer<Double>?, 
                                      [UnsafeMutablePointer<Double>?]) -> R) -> R

это общий тип R его возвращаемого типа, как и раньше.

Внутри функции мы соединяем входы и выходы в UnsafeBufferPointer, а затем вызываем map для результирующих буферов, чтобы создать переменную типа [UnsafePointer<Double>], которую затем можно передать в тело замыкания.

return ins.withUnsafeBufferPointer { (inputsBuffer) in
    var inPuts: [UnsafePointer<Double>?] = inputsBuffer.map { UnsafePointer($0) }                                      
    return out.withUnsafeBufferPointer { (outputsBuffer) in
            var outPtrPtr: [UnsafeMutablePointer<Double>?] 
                = outputBuffer.map { UnsafeMutablePointer(mutating: $0) }                 
            return body(inPuts, opts, outPtrPtr)
        }
    }

Передача параметра [UnsafePointer<Double>] замыканию body неявно связывает требуемые параметры UnsafePointer<UnsafePointer<Double>> и UnsafePointer<UnsafeMutablePointer<Double>>, требуемые импортированным API C.

Функция indicatorWithArrays вызывается следующим образом и позволяет нам затем использовать мостовые указатели в импортированной функции C:

return indicatorWithArrays(inputs: input, options: opts, outputs: resArray) { (input, opts, outputs) in
            let sz = inputs?.first?.count ?? 0
            switch TIReturnType(rawValue: tulipInfo.info.pointee.indicator(Int32(sz), input, opts, outputs)) {
            case .ti_okay?:
                for (index, item) in outputs.enumerated() {
                    let buff = UnsafeBufferPointer(start: item, count: resArray[index].count)
                    resArray[index] = Array(buff)
                }
                return resArray
            case nil:
                return nil
            }
        }

где звонок: tulipInfo.info.pointee.indicator(Int32(sz), input, opts, outputs)

Магия заключается в передаче замыканий между функциями и, таким образом, обеспечении того, чтобы мы не ускользнули от мостовых указателей. Решение Оле Бергманна подходит для String типов, но, надеюсь, оно поможет кому-то еще, кто застрял с типом. [[T]]

person lbdl    schedule 04.01.2019

Предполагая, что у вас есть, как вы указываете в своем комментарии, функция C, которую Swift набрал следующим образом:

public func indicator_abs(_ size: Int32, 
    _ inputs: UnsafePointer<UnsafePointer<Double>?>!, 
    _ options: UnsafePointer<Double>!, 
    _ outputs: UnsafePointer<UnsafeMutablePointer<Double>?>!) -> Int32

... тогда я думаю, вы можете назвать это следующим образом:

    let inputs = [1.0, 2.0]
    let options = [1.0, 1.0]
    var outputs = [0.0, 0.0]

    let result:Int32 = withUnsafePointer(to: inputs) { inputsPtr in
        withUnsafePointer(to: &outputs) { outputsPtr in
            indicator_abs(2,inputsPtr,options,outputsPtr)
        }
    }
person matt    schedule 20.12.2018
comment
спасибо, но есть пара вещей, параметры inputs и outputs имеют тип [[Double]], а также в стиле C result - это тип ошибки функции, в которой фактический вывод требуется в outputs. Так что я не думаю, что это сработает, потому что я не думаю, что мы сможем избежать outputsPtr - person lbdl; 20.12.2018