1

I'm trying to figure out how to call variadic C functions that write to pointers from Swift, such as vsscanf, but I don't understand how to actually construct the list of pointers to Swift variables.

I figure that if I have a string, I can get an UnsafePointer<CChar> and call vsscanf on it, but... how do I tell it where to actually write the data? How do I construct the CVaListPointer to pass to vsscanf?

var a: Int
var b: Float
"(5, 3.14)".withCString{buffer in
 let r = vsscanf(buffer, "(%d, %f)", /* how do I put a and b here? */)
}

Basically, doing the same thing as here (C):

#include <stdio.h>
#include <stdarg.h>
int parse(const char *buffer, char *format, ...)
{
 va_list args;
 va_start(args, format);
 int result = vsscanf(buffer, format, args);
 va_end(args);
 return result;
}
int main(int argc, char const *argv[])
{
 int a;
 float b;
 char s[] = "(5, 3.14)";
 int r = parse(s, "(%d, %f)", &a, &b);
 printf("a: %d, b: %f\n", a, b);
 // "a: 5, b: 3.140000"
 return 0;
}

UPDATE: Thanks to the answers so far, I feel like I understand how this works a bit better, but what I'm still struggling with is populating the CVaListPointer in an easier way. Here's an example of parsing a triplet of Doubles out of a string:

func parseVec3(s: String) -> (Double, Double, Double)? {
 var x: Double = 0
 var y: Double = 0
 var z: Double = 0
 
 let r = s.withCString { buffer in
 withUnsafeMutablePointer(to: &x) { ptrx in
 withUnsafeMutablePointer(to: &y) { ptry in
 withUnsafeMutablePointer(to: &z) { ptrz in
 withVaList([ptrx, ptry, ptrz]) { va in
 return vsscanf(buffer, "(%lf %lf %lf)", va)
 }
 }
 }
 }
 }
 
 return r == 3 ? (x, y, z) : nil
}
if let v = parseVec3(s: "(1 2 3)") {
 print(v)
}

Now, this does work. But my problem is that I'm parsing a file where the bulk of the lines (thousands upon thousands of them) are six groups of triplets of numbers. The towering structure of withUnsafeMutablePointer would look downright ridiculous. I'm sure I could parse it using some more Swift-native approach (or just regex) but I was hoping to just use vsscanf because parsing this file in C is outrageously simple:

int main(int argc, char const *argv[])
{
 char s[] = "(1 2 3) (5 9 1) (0 5 8)";
 Vec3 a, b, c = {0, 0, 0};
 sscanf(s,
 "(%f %f %f) (%f %f %f) (%f %f %f)",
 &(a.x), &(a.y), &(a.z),
 &(b.x), &(b.y), &(b.z),
 &(c.x), &(c.y), &(c.z)
 );
 printf("a: (x: %f, y: %f, z: %f)\n", a.x, a.y, a.z);
 printf("b: (x: %f, y: %f, z: %f)\n", b.x, b.y, b.z);
 printf("c: (x: %f, y: %f, z: %f)\n", c.x, c.y, c.z);
 return 0;
}

Doing this with the withUnsafeMutablePointer approach in Swift as above would result in 11 with<Whatever> scopes, and that's only half of the floats parsed...

I figure I should be able to do something like this, but I can't figure out how to get the pointer offset to the other struct members:

func parseVec3_3(s: String) -> Vector3? {
 var output = Vector3(x: 0, y: 0, z: 0)
 
 var r: CInt = 0
 s.withCString { buffer in
 withUnsafeMutablePointer(to: &output) { ptr in
 withVaList([ptr, /* offset pointer here */]) { va in
 r = vsscanf(buffer, "(%lf %lf %lf)", va)
 }
 }
 }
 
 return r == 3 ? output : nil
}
asked Dec 9, 2022 at 16:03
2
  • 1
    These are 2 questions in 1, but here you go, I've updated my answer. Commented Dec 14, 2022 at 19:27
  • Thanks. I have some more things I'm wondering on the subject but it's definitely turning into another question. I'll ponder it for a while. Commented Dec 15, 2022 at 9:02

2 Answers 2

1

Like this:

var a: Int = 0
var b: Float = 0
withUnsafePointer(to: &a) { pointerToA in
 withUnsafePointer(to: &b) { pointerToB in
 withVaList([pointerToA, pointerToB]) { va_list in
 "(5, 3.14)".withCString { buffer in
 let r = vsscanf(buffer, "(%d, %f)", va_list)
 }
 }
 }
}
print(a)
print(b)

outputs

5
3.14

Update
The answer for OP edit including question about Vector3(x: 0, y: 0, z: 0) should become:

public struct Vector3: Equatable {
 public var x: CGFloat
 public var y: CGFloat
 public var z: CGFloat
 public init(x: CGFloat, y: CGFloat, z: CGFloat) {
 self.x = x
 self.y = y
 self.z = z
 }
}
func parseVec3_3(s: String) -> Vector3? {
 var output = Vector3(x: 0, y: 0, z: 0)
 var r: CInt = 0
 s.withCString { buffer in
 withUnsafePointer(to: &output) { (outputPointer: UnsafePointer<Vector3>) in
 outputPointer.withMemoryRebound(to: CGFloat.self, capacity: 3) {
 withVaList([OpaquePointer(0ドル), OpaquePointer(0ドル.advanced(by: 1)), OpaquePointer(0ドル.advanced(by: 2))]) { va in
 r = vsscanf(buffer, "(%lf %lf %lf)", va)
 }
 }
 }
 }
 return r == 3 ? output : nil
}
if let vector: Vector3 = parseVec3_3(s: "(1.0 2.0 3.0)") {
 print(vector)
}

outputs

Vector3(x: 1.0, y: 2.0, z: 3.0)
answered Dec 10, 2022 at 20:31
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I edited my question with some additional details, now that I know more about what to ask for.
0

From the documentation on CVarArgs:

To create a wrapper for the c_api function, write a function that takes CVarArg arguments, and then call the imported C function using the withVaList(_:_:) function.

Swift only imports C variadic functions that use a va_list for their arguments. C functions that use the ... syntax for variadic arguments are not imported, and therefore can’t be called using CVarArg arguments.

Your wrapper function could look like:

func vsscanfSwiftWrapper(
 buffer: UnsafePointer<CChar>,
 format: UnsafePointer<CChar>,
 _ arguments: CVarArg...
) -> CInt {
 withVaList(arguments) { vaList in
 vsscanf(buffer, format, vaList)
 }
}
answered Dec 9, 2022 at 16:57

2 Comments

Right, but how do I call vsscanfSwiftWrapper? I don't understand how to get CVarArgs that are pointers to my variables.
If you're asking how to get pointers to a and b instead of the values themselves, I would look at the documentation for UnsafeMutablePointer.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.