Swift: передача неинициализированной структуры C в импортированную функцию C

Я знаю об этом ответе, но это не одно и то же - это передача указателя для инициализации с выделением.

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

typedef struct myStruct { unsigned char var [50]; } myStruct;

Есть функция, которой вы передаете адрес структуры - обычно это стек, а не куча, таким образом:

myStruct mine;
initMyStruct(&mine);

Это инициализирует содержимое структуры - вызывающая сторона не знает внутренний формат памяти, следовательно, запутывание.

В Swift я создаю класс, который будет инкапсулировать структуру и интерфейс с другими функциями C, которые работают с ним.

class MyClass {

    var mine : myStruct

    init() {

        initMyStruct(&mine)
        // Error: Variable 'self.msg' passed by reference before being initialized

    }

}

Я не могу на всю жизнь понять, как инициализировать структуру, или использовать точку вместо этого, если это альтернатива.

mine = myStruct()
// Fails because you aren't providing the value of the member 'var'

mine = myStruct((CUnsignedChar(), CUnsignedChar(), /*... repeat to 50 */))
// Cannot find an overload for 'init' that accepts the arguments

Обратите внимание, что это передача адреса уже распределенной (основанной на стеке) структуры функции, которая была импортирована как

CInt initMyStruct(str: CMutablePointer<myStruct>)

Он НЕ передает указатель, который будет выделен рассматриваемой библиотекой C, что, как представляется, является более распространенным требованием и на него есть ответ в другом месте.

В настоящее время Swift также не поддерживает массивы фиксированного размера.

Я не вижу, как удовлетворить инициализацию структуры или заставить Свифта думать, что это будет сделано вызовом.

Любая помощь с благодарностью!

6 ответов

Во-первых, давайте определим наш C-код для тестирования:

typedef struct my_struct {
    unsigned char buffer[10];
} my_struct;

void my_struct_init(my_struct *my_s) {
    for (int i = 0; i < 10; i++) {
        my_s->buffer[i] = (char) i;
    }
}

В Swift у нас есть два варианта:

1. Структура в стеке

var my_s: my_struct = ...

Однако мы должны как-то его инициализировать. Каждая структура имеет инициализатор по умолчанию

var my_s: my_struct = my_struct(buffer: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

Обратите внимание, что в этом случае buffer[10] был переведен на Swift как 10-tuple,

Теперь мы можем позвонить:

my_struct_init(&my_s)
print("Buffer: \(my_s.buffer)") // Buffer: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

Однако чем сложнее структура, тем сложнее использовать инициализатор по умолчанию.

2. Строить на куче

Это похоже на использование malloc а также free в С:

var my_s_pointer = UnsafeMutablePointer<my_struct>.allocate(capacity: 1)
print("Buffer: \(my_s.buffer)") // Buffer: (some random values)

my_struct_init(my_s_pointer)
print("Buffer: \(my_s_pointer.memory.buffer)") // Buffer: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

my_s_pointer.deallocate()

Объедините оба подхода

Следующая функция инициализирует любую структуру:

func initStruct<S>() -> S {
    let struct_pointer = UnsafeMutablePointer<S>.allocate(capacity: 1)

    let struct_memory = struct_pointer.pointee
    struct_pointer.dealloate()

    return struct_memory
}

var my_s: my_struct = initStruct()
my_struct_init(&my_s)

Верьте или нет, вы можете инициализировать C Struct так же, как Swift Struct. XCode даже автоматически заполняет имена участников для вас!

C структура как стремительная структура

В этом частном случае

var when = timespec(tv_sec:0, tv_nsec:0)

Будет устанавливать when эпохи

А вот магический инициализатор, который дает вам пустую структуру:

func blankof<T>(type:T.Type) -> T {
    var ptr = UnsafePointer<T>.alloc(sizeof(T))
    var val = ptr.memory
    ptr.destroy()
    return val
}

Приведенный выше пример будет:

var when = blankof(timespec)

Начиная с Swift 1.2 (бета-версия Xcode 6.3), импортированные структуры C теперь имеют инициализатор по умолчанию в Swift, который инициализирует все поля структуры в ноль.

В твоем случае:

class MyClass {

    var mine = myStruct() // <-- Initializes all elements to zero

    init() {
        initMyStruct(&mine) // <-- No error anymore because `mine` is initialized
    }
}

У меня та же проблема с библиотекой C, и я задал этот вопрос на форумах Apple. Пока без разрешения.

Чтобы обойти эту проблему, не изменяя исходную библиотеку C, я создал функции Create/Destroy в Bridging.c /.h

Bridging.h

typedef struct myStruct { unsigned char var [50]; } myStruct;
myStruct * CreateMyStruct();
void DestroyMyStruct(myStruct **s);
void DoSomething(myStruct *s);

Bridging.c

myStruct * CreateMyStruct() {
    myStruct * mine = malloc(sizeof(myStruct));
    if (mine)
        initMyStruct(mine);
    return mine;
}

void DestroyMyStruct(myStruct **s) {
    if (*s)
        free(*s);
    *s = NULL;  // set the swift variable to nil
}

void DoSomething(myStruct *s)
{
    // ...
}

Example.swift

var mine : UnsafePointer<myStruct> = CreateMyStruct();
DoSomething(mine);
DestroyMyStruct(&mine);

Это не использует преимущества ARC, но, по сути, позволяет обойти проблему без изменения исходной библиотеки.

Как насчет этого

var mine : UnsafePointer<myStruct> = nil
initMyStrict(&mine)

Я не знаю, что происходит внутри initMyStruct... но предположим, что это функция, которая принимает в качестве параметра строку, которую вы хотите скопировать в поле var

Я никогда не использовал swift... но в этом коде вы можете использовать свою структуру в качестве члена класса C++...
Может быть, вы могли бы использовать этот класс, который вместо этого просто инкапсулирует структуру?

#include <cstdio>

typedef struct myStruct { unsigned char var [50]; } myStruct;

void initMyStruct(myStruct *mine, char *ini)
{
    int i = 0;
    do
    {
        mine->var[i] = (unsigned char) ini[i];
    } while(ini[i++]);
}

class MyClass
{
   public:
   myStruct st;

   MyClass(char *parameter)
   {
        initMyStruct(&st, parameter);
   }

};

int main()
{
    MyClass c("Hakuna Matata!");
    printf("%s\n", c.st.var);
}

Если он использует некоторые функции cstring, это может вызвать некоторые проблемы, поскольку указатель имеет тип unsigned char*, а не char*...

Другие вопросы по тегам