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:
In C, function parameters are passed by value, and are effectively mutable local variables. That means that when
functionWithArray
receiveschar *letters
,letters
is a local variable containing a pointer value to the buffer ofletters
in memory. Importantly, that means thatletters
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 localletters
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 becauseletters
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,
The implicit conversion of an
Array<T>
to anUnsafeMutablePointer<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:
Have
functionWithArray
return an entirely new array and length from C — you mention this isn't possible forfunctionWithArray
specifically because of the other values it needs to return, but theoretically you can also create a Cstruct
which wraps up all of the return values together and return one of those insteadRewrite
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 thecapacity
given to you with results. Of course, this will only work if you know in Swift ahead of time how much spacefunctionWithArray
will need, which you might not- Alternatively, you can also use
Array.init(unsafeUninitializedCapacity:initializingWith:)
to combine these operations by havingArray
preallocate some space, and you can pass in theinout UnsafeMutableBufferPointer<CChar>
to C where you can allocate memory if you need to and assign to the buffer pointer, then write out to theinout Int
how many array elements you allocated and initialized. This does also require a capacity, though, and is a more complicated solution
- Alternatively, you can also use
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.