13

I'm aware of this answer, but this is not the same thing - thats passing a pointer to be initialised with an allocation.

I'm interfacing with a C library that has the following structure definition:

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

There is a function to which you pass the address of the structure - normally stack based not heap, thus:

myStruct mine;
initMyStruct(&mine);

This initialises the content of the struct - the caller does not know the internal format of the memory, hence the obfuscation.

In Swift, I'm creating a class which will encapsulate the structure and interface with other C functions which operates on it.

class MyClass {

    var mine : myStruct

    init() {

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

    }

}

I can't for the life of me work out how to initialise the structure, or use a point instead if that is an alternative.

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

Please note, this is passing the address of an already allocated (stack based) structure to a function that has been imported as

CInt initMyStruct(str: CMutablePointer<myStruct>)

It is NOT passing a pointer to be allocated by the C library in question which seems to be a more common requirement and is answered elsewhere.

Swift currently doesn't support fixed size arrays either.

I can't see how to satisfy the initialisation of the structure or make Swift think that it is going to be done so by the call.

Any help greatly appreciated!

Community
  • 1
  • 1
Dave Meehan
  • 3,133
  • 1
  • 17
  • 24
  • Can you initialize it with some dummy information and then overwrite it later? – Jiminion Jun 10 '14 at 16:55
  • That's the whole problem - I can't figure out what syntax, if any, it wants as an initialisation for a `unsigned char [50]`. Nothing seems to be acceptable. If I can't initialise it, I want a way to tell it to ignore the uninitialised pass-by-reference. I've tried the compiler options to no avail. Thanks for the help. – Dave Meehan Jun 10 '14 at 21:51
  • do you really need to use the initMyStruct? What is inside that initMyStruct? – nightshade Jun 11 '14 at 00:09
  • @nightshade it doesn't matter what is inside, its important to the other C library functions - internal state data. I know in fact that is cast to a union, so the 50 bytes is just allocating sufficient storage for different requirements, depending on what happens later in the code. But thats not important to the problems that Swift has with the semantics. – Dave Meehan Jun 11 '14 at 15:37

6 Answers6

12

First, let's define our C code for testing:

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;
    }
}

In Swift, we have two options:

1. Struct on the stack

var my_s: my_struct = ...

However, we have to initialize it somehow. Every struct has a default initializer

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

Note that in this case the buffer[10] has been translated to Swift as a 10-tuple.

Now we can call:

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

However, the more complex is the struct, the more difficult is to use the default initializer.

2. Struct on the heap

This is similar to using malloc and free in C:

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()

Combine both approaches

The following function will initialize any struct:

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)
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Two questions: 1) Shouldn't it be `UnsafePointer.alloc(1)`? As I understand it, the size specifies the required number of elements, not bytes. - 2) Does `var struct_memory = struct_pointer.memory` retain the memory somehow? Or how does can you work with it after freeing the memory in `struct_pointer.destroy()`? – Martin R Jul 17 '14 at 09:58
  • @MartinR 1) I am not sure. When I was writing this, the method was not documented. 2) If it's a struct, it's a value type so it gets copied. Maybe I should add some constraints for `S` to exclude object types. – Sulthan Jul 17 '14 at 10:02
  • Oh well, I can't add constraints to exclude object types. I can exclude value types (`S : AnyObject`) but not viceversa... Currently, I also have problems to distinguish between `.dealloc` and `.destroy` for pointer types. Shame it's not documented at all. – Sulthan Jul 17 '14 at 10:46
  • 1) It still seems to be undocumented. But my experiments (using the "Guard Malloc" option) seems to confirm that the size is the number of elements. - 2) You are right, a struct is copied, that explains it. – Martin R Jul 17 '14 at 11:13
  • 1
    UnsafePointer should be UnsafeMutablePointer – vbezhenar Oct 09 '14 at 20:25
  • 1
    `.destroy()` should be `.dealloc(1)`, or it will leak. – rintaro Dec 09 '14 at 17:21
3

Believe it or not, you can initialize C Struct just like Swift Struct. Xcode even autocompletes member names for you!

C Struct as Swift Struct

In this Particular case,

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

Will set when to epoch.

And here is a magical initializer that gives you any blank struct:

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

The above example would be:

var when = blankof(timespec)
dankogai
  • 1,627
  • 12
  • 7
3

As of Swift 1.2 (Xcode 6.3 beta), imported C structs now have a default initializer in Swift, which initializes all of the struct's fields to zero.

In your case:

class MyClass {

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

    init() {
        initMyStruct(&mine) // <-- No error anymore because `mine` is initialized
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
2

I have the same issue with a C library and have asked this question over on Apple's Forums. So far no resolution.

To work around the issue without modifying the original C library, I have created Create/Destroy functions in 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);

This does not take advantage of ARC, but at lease it gets past the issue without modifying the original library.

Dave
  • 145
  • 1
  • 9
1

How about this

var mine : UnsafePointer<myStruct> = nil
initMyStrict(&mine)
kishdude
  • 93
  • 8
0

I don't know what is going on inside initMyStruct... but suppose it is a function that takes as parameter the string you want to copy to field var

I have never used swift... but on this code here you could have your struct as a c++ class member...
Maybe you could use this class that just encapsulates the struct instead?

#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);
}

If it uses some cstring functions it might cause some problems because the pointer is unsigned char* and not char*...

nightshade
  • 638
  • 5
  • 15
  • Wrapping the struct in a C++ or Obj-C class might be a solution to fool the Swift compiler into thinking its initialised, but is a layer of abstraction I'd rather not use. If that were the case, why not just write the whole solution in C++/Obj-C and allow it to be imported into Swift? Seems not really to be the point, but it might reflect the current state of the compiler – Dave Meehan Jun 11 '14 at 15:38
  • have you tried adding a default constructor in the struct then? Maybe swift will call it when you declare a variable.. if error persist maybe you could just initialize the fields to some random values and fix them with initstruct... or call initstruct from the constructor – nightshade Jun 11 '14 at 17:38
  • The C Library is open-source, but that doesn't allow me to write a constructor (unless a Swift extension is possible, not tried). – Dave Meehan Jun 13 '14 at 13:36
  • I thought you could use C++, you can put constructors for struct in c++ – nightshade Jun 14 '14 at 06:33