Search code examples
cswiftpointerslanguage-interoperability

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


I'm trying to interact with an old C terminal app/library from Swift. I've successfully integrated the source code and bridged the headers from C to Swift. The code compiles and runs, I can access all functions from C - library into swift.

There is a structure in C - library which I need to initialize[Function already exists which takes pointer] and assign values to structure variables[Manually].

C-structure:

Struct args{
char ** var1;
unsigned char * var2;
char * var3;
}

and Initialization function call:

init(args * ptr);

How to call the function inside swift and assign values to var1 and var2?

1.Will following snippet successfully initialize the structure?

let Ptr = UnsafeMutablePointer<args>.allocate(capacity: 1)
var args = args()
Ptr.pointee = args
init(Ptr)

2.How to assign values to var1, var2 & var3 assuming we successfully initialize?

They are mapped as:

var1: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!
var2: UnsafeMutablePointer<Uint8>!
var3: UnsafeMutablePointer<Int8>!    

For example var1 = {"a", "b"}, var2 = {1,2,3} and var3 = "a"

I've tested following links and did not work:

How to pass an array of Swift strings to a C function taking a char ** parameter : gives 'inout[UnsafeMutablePointer?] to type UnsafeMutablePointer?>!' error

Convert a Swift Array of String to a to a C string array pointer : gives 'inout[UnsafeMutablePointer?] to type UnsafeMutablePointer?>!' error

No built-in support for arrays of C strings : this one needs more efforts and hoping to get easier version

github - Swift wrappers for C functions taking char** arguments : gives 'inout[UnsafeMutablePointer] to type UnsafeMutablePointer?>!' error


Solution

  • 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.