Home > front end >  Modifying swift [CChar] arrays in C functions without returning value
Modifying swift [CChar] arrays in C functions without returning value

Time:03-28

I started learning C and wanted to try some of the Swift-C interoperability.

I have a small C function which reads me a file and concatenates some useful letters into a char* variable. After some testing, I cannot find a way to pass my obtained char* data back to swift. I have written a small dummy code to illustrate what I am trying to achieve.

var letters: [CChar] = []

functionWithArray(&letters)

print("Back in swift: \(letters)")

And the C function is:

void functionWithArray(char* letters) {
    
    int arrayLenght = 5;
    int testLenght = 10; // Expand array to this value (testing)
    int currentArrayPosition = 0; //Keep track of the assigned values
    
    letters = malloc(sizeof(char)*arrayLenght);
    
    while (currentArrayPosition < testLenght) {
        
        if (currentArrayPosition == arrayLenght) {
            arrayLenght  ;
            letters = realloc(letters, sizeof(char)*arrayLenght);
        }
        
        letters[currentArrayPosition] = *"A";
          currentArrayPosition;
    }
    
    printf("End of C function: %s\n", letters);
}

I get this as an output:

End of C function: AAAAAAAAAA

Back in swift: []

Program ended with exit code: 0

As you can see, inside the C function I've got the desired result, but back in swift I could not find a way to obtain the modified array. I do not return letters directly with the function because I need to return more values from that function. I'm new to C so please be kind.

CodePudding user response:

There are two main issues with your approach here — one in C and one in Swift:

  1. In C, function parameters are passed by value, and are effectively mutable local variables. That means that when functionWithArray receives char *letters, letters is a local variable containing a pointer value to the buffer of letters in memory. Importantly, that means that letters is assignable, but not in the way that you think:

    letters = malloc(sizeof(char)*arrayLenght);
    

    allocates an entirely new buffer through malloc, and assigns the newly-created pointer value to your local letters variable. Before the assignment, letters is a pointer to the buffer you were getting from Swift; after, to an unrelated buffer in memory. These two buffers are completely unrelated to one another, and because letters is just a local variable, this assignment is not propagaged in any way outside of the function.

    Note that this is just a rule of C: as you learn more C, you'll likely discover that in order to assign a variable from inside of a function to outside of a function, you need to wrap the variable in another layer of pointers and write through that pointer (e.g., you would need to receive char **letters and assign *letters = malloc(...) to have any effect on a variable being passed in — and the variable couldn't be passed in directly, but rather, its address would need to be passed in).

    However, you can't generally make use of this fact because,

  2. The implicit conversion of an Array<T> to an UnsafeMutablePointer<T> (e.g. [CChar]UnsafeMutablePointer<CChar> in Swift == char * in C) does not allow you to assign an entirely new buffer to the array instance. You can write into the contents of the buffer by writing to pointer values, but you cannot allocate a new block of memory and reassign the contents of the array to that new block

Instead, you'll need to either:

  1. Have functionWithArray return an entirely new array and length from C — you mention this isn't possible for functionWithArray specifically because of the other values it needs to return, but theoretically you can also create a C struct which wraps up all of the return values together and return one of those instead

  2. Rewrite functionWithArray to receive an array and a length, and pre-reserve enough space in the array up-front to fill it appropriately:

    var letters: [CChar] = []
    letters.reserveCapacity(/* however much you need */)
    functionWithArray(&letters, letters.capacity)
    

    In functionWithArray, don't reassign letters, but instead fill it up to the capacity given to you with results. Of course, this will only work if you know in Swift ahead of time how much space functionWithArray will need, which you might not

    • Alternatively, you can also use Array.init(unsafeUninitializedCapacity:initializingWith:) to combine these operations by having Array preallocate some space, and you can pass in the inout UnsafeMutableBufferPointer<CChar> to C where you can allocate memory if you need to and assign to the buffer pointer, then write out to the inout Int how many array elements you allocated and initialized. This does also require a capacity, though, and is a more complicated solution

Of these two approaches, if functionWithArray really does need to dynamically reallocate memory and grow the buffer, then (1) is likely going to be easier.

  • Related