10

I'm trying to interact with an old C terminal app from Swift. I've successfully integrated the source code and bridged the headers from C to Swift. The code compiles and runs from Xcode 6.3 beta. I've renamed the terminal app's main entry point to:

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

Nevertheless, I'm struggling to pass the arguments from Swift to this C function. My challenge is to convert the arguments in the right format. Typical input from Swift would look like:

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

I've tried messing with "cStringUsingEncoding(NSUTF8StringEncoding)" and "withUnsafePointer", but no luck so far. Any help is greatly appreciated!

Martin R
  • 488,667
  • 78
  • 1,132
  • 1,248
Mark
  • 103
  • 5

2 Answers2

12

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.

Community
  • 1
  • 1
Martin R
  • 488,667
  • 78
  • 1,132
  • 1,248
  • Thanks, works like a charm. For others trying to do something similar, I should add that C passes the arguments vector starting from index 1 instead of 0. I updated my arguments to: let args = ["", "-c", "1.2.3.4", "-p", "8000"]. – Mark Apr 06 '15 at 15:27
  • If I have a command line game, do you think it's possible to run it and pass arguments to it, and extract its stdout to a text area for example in swift? Or do I have to do the game from all over using its function? @Mark – MasterWizard Dec 14 '15 at 21:51
  • @AhmedNassar: You cannot run external programs in iOS, as far as I know. – Martin R Dec 14 '15 at 21:55
  • Not external, I have the C program in my project, Can I pass to it's main function the arguments? – MasterWizard Dec 14 '15 at 21:56
  • @AhmedNassar: That should be possible with above method. You may have to rename the main function. – Martin R Dec 14 '15 at 21:57
  • But I dont want to pass all my arguments at once since I'm waiting for the stdout, how could I do this? Sorry If I'm asking a lot of silly questions, Im not experienced in C. – MasterWizard Dec 14 '15 at 22:18
1

Building on Martin’s answer, if you find yourself doing this a lot, you could wrap the dup/free part into a function in a similar style to String.withCString:

import Darwin

func withCStrings
  <R, S: SequenceType where S.Generator.Element == String>
  (strings: S, @noescape body:  (UnsafeBufferPointer<UnsafeMutablePointer<Int8>>) -> R) 
  -> R  {

    let cstrings = map(strings) { strdup($0) } + [nil]

    let result = cstrings.withUnsafeBufferPointer(body)

    for ptr in cstrings { free(ptr) }

    return result
}

let execvargs = ["/usr/bin/say"] + dropFirst(Process.arguments)

let execvresult = withCStrings(execvargs) {
    execv($0[0], $0.baseAddress)
}
Airspeed Velocity
  • 38,892
  • 7
  • 100
  • 111