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
}
-
1These are 2 questions in 1, but here you go, I've updated my answer.Kamil.S– Kamil.S2022年12月14日 19:27:47 +00:00Commented 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.SimonL– SimonL2022年12月15日 09:02:05 +00:00Commented Dec 15, 2022 at 9:02
2 Answers 2
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)
1 Comment
From the documentation on CVarArgs:
To create a wrapper for the
c_api
function, write a function that takesCVarArg
arguments, and then call the imported C function using thewithVaList(_:_:)
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 usingCVarArg
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)
}
}
2 Comments
Explore related questions
See similar questions with these tags.