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 differentconst
-ness of the argument array, and because there is a terminatingnil
(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 String
s, 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
Type Conversion When Using Protocol in Swift
Swift Spritekit Adding Button Programmatically
Troubles With Starting Value Using Uislider
Cllocationcoordinate2D Can't Be Instantiated
What's the Difference Between Struct Based and Class Based Singletons
Swift - Unit Testing Private Variables and Methods
Is There a Zip Function to Create Tuples with More Than 2 Elements
Color Attribute Is Ignored in Nsattributedstring with Nslinkattributename
Change Color Searchbar Result Icon Swift
How to Print Out the Method Name and Line Number in Swift
Wkwebview Does Not Load Links to Pdfs
Retrieving Image from Firebase Storage Using Swift
Swift 2 to 3 Migration for Prepareforsegue
Mathematical Functions in Swift
How to Create Dictionary That Can Hold Anything in Key? or All the Possible Type It Capable to Hold