Convert a Swift Array of String to a to a C String Array Pointer

Convert a Swift Array of String to a to a C string array pointer

You can proceed similarly as in How to pass an array of Swift strings to a C function taking a char ** parameter. It is a bit different because of the different
const-ness of the argument array, and because there is a terminating
nil (which must not be passed to strdup()).

This is how it should work:

let array: [String?] = ["name1", "name2", nil]

// Create [UnsafePointer<Int8>]:
var cargs = array.map { $0.flatMap { UnsafePointer<Int8>(strdup($0)) } }
// Call C function:
let result = command(&cargs)
// Free the duplicated strings:
for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }

Convert a C String Array to a Swift String Array

The problem is not in the Swift code (or related to any Swift 4.2 changes).
The real problem is that the C function returns the “2-dimensional array” variable

char paths[FILES_MAX][PATH_MAX];

as a char ** to the caller – but that are incompatible types: one is an
array of arrays (with all characters being in contiguous memory), the other is a pointer to a pointer. There should be a compiler warning like

incompatible pointer types returning 'char [...][...]' from a function with result type 'char **'

You could return paths from the function if you declare something like

typedef char PathName[PATH_MAX];

PathName *getAllFilePaths(const char path []) { ... }

But that would be imported to Swift as

typealias PathName = (Int8, Int8, ... , Int8)

public func getAllFilePaths(_ path: UnsafePointer<Int8>!) -> UnsafeMutablePointer<PathName>!

where PathName is a tuple of PATH_MAX characters – quite cumbersome
to use from Swift! Another problem is that the caller does not know
how many arrays elements have been filled.

Better define paths as an array of char pointers:

static char *paths[FILES_MAX] = { NULL };

and fill it with a NULL-terminated list of char pointers. Here is a
over-simplified example:

char **getAllFilePaths(const char path []) {
paths[0] = "foo";
paths[1] = "bar";
paths[2] = NULL;
return paths;
}

In your real application you do not add string literals, so you'll probably
have to duplicate the strings

    paths[j] = strdup(someString);

which means that at some point the strings must be free()d again, to avoid
memory leaks.

Converting array of C strings to Swift string array

There is no built-in method as far as I know.
You have to iterate over the returned pointer array, converting C strings to Swift Strings, until a nil pointer is found:

if var ptr = f() {
var strings: [String] = []
while let s = ptr.pointee {
strings.append(String(cString: s))
ptr += 1
}
// Now p.pointee == nil.

print(strings)
}

Remark: Swift 3 uses optional pointers for pointers that can be nil.
In your case, f() returns an implicitly unwrapped optional because
the header file is not "audited": The compiler does not know whether
the function can return NULL or not.

Using the "nullability annotations" you can provide that information
to the Swift compiler:

const char * _Nullable * _Nullable f(void);
// Imported to Swift as
public func f() -> UnsafeMutablePointer<UnsafePointer<Int8>?>?

if the function can return NULL, and

const char * _Nullable * _Nonnull f(void);
// Imported to Swift as
public func f() -> UnsafeMutablePointer<UnsafePointer<Int8>?>

if f() is guaranteed to return a non-NULL result.

For more information about the nullability annotations, see for example
Nullability and Objective-C in the Swift blog.

Swift conversion of Array of Strings to C array of C Strings not working

That withArrayOfCStrings function works with C functions taking a char * const * argument, i.e. a pointer to constant pointers to characters.

If you change the C declaration to

GDALVectorTranslateOptions* GDALVectorTranslateOptionsNew(char* const* papszArgv,
GDALVectorTranslateOptionsForBinary* psOptionsForBinary)

then it compiles and runs as expected.

If you do not have the option to modify the C code then you can change the helper method to

public func withArrayOfCStrings<R>(_ args: [String],
_ body: (UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>) -> R) -> R {
var cStrings = args.map { strdup($0) }
cStrings.append(nil)

defer {
cStrings.forEach { free($0) }
}

return body(&cStrings)
}

The body is now called with a UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> argument, which corresponds to a C char ** argument.

Convert Swift String array to c char**

getDREFSs expects a pointer to an array of optional Int8 pointers.
Also the second argument must be converted to UInt8.

So this would compile:

public func get(drefs: [String]) -> Int {
var cDrefs = [UnsafePointer<Int8>?]()
for dref in drefs {
cDrefs.append(dref.cString(using: .utf8))
}
let result = getDREFs(&cDrefs, UInt8(drefs.count))
return Int(result)
}

But a quick test shows that is does not work if called with
multiple strings. The reason is that the arrays
returned by dref.cString(using: .utf8)
can already be deallocated (and the pointer invalid)
when the C function is called.

Here is a working version, a slight modification of
Convert a Swift Array of String to a to a C string array pointer for
this particular case:

public func get(drefs: [String]) -> Int {
var cargs = drefs.map { UnsafePointer<Int8>(strdup($0)) }
let result = getDREFs(&cargs, UInt8(drefs.count))
for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }
return Int(result)
}

How to pass an array of Swift strings to a C function taking a char ** parameter

The C function

int initialize(int argc, char **argv);

is mapped to Swift as

func initialize(argc: Int32, argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32

This is a possible solution:

let args = ["-c", "1.2.3.4", "-p", "8000"]

// Create [UnsafeMutablePointer<Int8>]:
var cargs = args.map { strdup($0) }
// Call C function:
let result = initialize(Int32(args.count), &cargs)
// Free the duplicated strings:
for ptr in cargs { free(ptr) }

It uses the fact that in strdup($0)
the Swift string $0 is automatically converted to a C string,
as explained in String value to UnsafePointer<UInt8> function parameter behavior.

Assign an array of Swift strings to a C structure variable taking a char ** value

This is quite a broad question, so here are some references and observations with a few examples. Hopefully these are helpful.

Please see Apple's documentation for UnsafeMutablePointer struct and also String and NSString:

https://developer.apple.com/documentation/swift/unsafemutablepointer

https://developer.apple.com/documentation/swift/string

https://developer.apple.com/documentation/foundation/nsstring

Another useful reading is Apple's docs about C and Swift interop: https://developer.apple.com/documentation/swift/imported_c_and_objective_c_apis

In this answer I'm also leaving out a lot of memory management aspects as well as things such as keeping track of the size of var1 and var2 arrays, since I don't know the specifics of your library.

Regarding the snippet for initializing the structure, you can't use the type name as the variable name and init will confuse the Swift compiler because it's reserved for naming class initializers. Let's name the variable myArgs instead of args and assume the C library initialization function is named initialize; if it's indeed init, one can easily write a wrapper named differently. Another problem with the snippet is that myArgs will remain unchanged after initialization, Ptr will actually get initialized, so you would have to use Ptr to access the initialized args structure. Thus we can omit Ptr and use implicit bridging to pass myArgs to the initialization function. The snippet becomes

var myArgs = args()
initialize(&myArgs)

Now you can access the members as follows:

// Assuming var1 is an array of at least 2 C strings.
// See Swift documentation about optionals on how to deal with
// cases when this assumption breaks down
let s1 = String(cString: myArgs.var1[0]!) // 1st element of var1
let s2 = String(cString: myArgs.var1[1]!) // 2nd element of var1
myArgs.var2.pointee // 1st element of var2
(myArgs.var2 + 1).pointee // 2nd element of var2
let s = String(cString: myArgs.var3) // value of var3

Now let's set var1 to be {"aa", "bbb"}:

            var var1Buffer = 
UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
var var1a : NSString = "aa"
var var1b : NSString = "bbb"
var var1aBuffer = UnsafeMutablePointer<Int8>.allocate(
capacity: var1a.length + 1)
var var1bBuffer = UnsafeMutablePointer<Int8>.allocate(
capacity: var1b.length + 1)
if (var1a.getCString(var1aBuffer, maxLength: var1a.length + 1,
encoding: String.Encoding.utf8.rawValue)
&& var1b.getCString(var1bBuffer, maxLength: var1b.length + 1,
encoding: String.Encoding.utf8.rawValue)) {
var1Buffer[0] = var1aBuffer
var1Buffer[1] = var1bBuffer
myArgs.var1 = var1Buffer
} else { print("Encoding failed...")}

Here is an example of setting var2 to be an array of 5 elements equal to 200:

var var2Buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 5);
var2Buffer.initialize(repeating: 200, count: 5)
myArgs.var2 = var2Buffer

And setting the value of var3:

let newVar3 : NSString = "This is new variable 3"
var var3Buffer = UnsafeMutablePointer<Int8>.allocate(capacity: newVar3.length + 1)
if (newVar3.getCString(var3Buffer, maxLength: newVar3.length + 1, encoding: String.Encoding.utf8.rawValue)) {
myArgs.var3 = var3Buffer
} else { print("Encoding failed...") }

The above examples assume UTF8 encoding.



Related Topics



Leave a reply



Submit