I am working with a library written in C, Firebird, in my Swift project. I have a fixed size C array, of size 1, in a struct
typedef struct
{
...
ISC_SHORT sqln /* number of fields allocated */
ISC_SHORT sqld; /* actual number of fields */
XSQLVAR sqlvar[1]; /* first field address */
} XSQLDA;
Imported in Swift as a tuple
public struct XSQLDA {
...
public var sqln: ISC_SHORT /* number of fields allocated */
public var sqld: ISC_SHORT /* actual number of fields */
public var sqlvar: (XSQLVAR) /* first field address */
}
The purpose of my code is to retrieve data describing a statement. For the statement SELECT foo, bar FROM MY_TABLE;
, XSQLDA sqlvar array will contains two XSQLVAR, with description of the foo
column and bar
column.
In C, the struct needs an allocated size of sizeof (XSQLDA) + (n - 1) * sizeof (XSQLVAR)
, as defined in the following macro in the library header
#define XSQLDA_LENGTH(n) (sizeof (XSQLDA) + (n - 1) * sizeof (XSQLVAR))
Creating a XSQLDA struct in Swift
let pointer = UnsafeMutableRawPointer.allocate(
byteCount: MemoryLayout<XSQLDA>.stride + (Int(n) - 1) * MemoryLayout<XSQLVAR>.stride,
alignment: MemoryLayout<XSQLVAR>.alignment)
var xsqldaPointer = pointer.bindMemory(to: XSQLDA.self, capacity: 1)
Passing this pointer to this struct to a C function of the library
isc_dsql_describe(&self.status, &self.statement, 1, xsqldaPointer);
This function will populate the sqlvar array of XSQLDA structure.
After this call, I iterate over the elements in the array
var sqlVariable: XSQLVAR = .init()
for i in 0...Int(xsqldaPointer.pointee.sqld) {
withUnsafePointer(to: &xsqldaPointer.pointee.sqlvar) { ptr in
sqlVariable = ptr.advanced(by: i).pointee
print(String(cString: &sqlVariable.sqlname.0))
}
}
Over the iterations, the address of the ptr
, aka sqlvar th element, even advanced, stay the same. It seems that Swift can only access the first one.
Running the same function in C works properly. I looked in the memory with BitSlicer, and there are only one element when I run the Swift code, and n
elements with the C code.
According to the documentation, the C code is
for (i=0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
for (i = 1; i < my_struct_instance->count; i++) {
var = my_struct_instance->arr[i];
}
I think the problem is coming from memory protection from Swift, but not sure. Any idea ?
I don't understand why to declare a fixed size array of size 1, which will contains an unknown number of elements. Why not a pointer ?
The function documentation is there, and a code example of this function is there. I'm aware that the documentation is about Interbase, by embarcadero. I looked the C code examples in the github, and the C API guide referenced by Firebird there