Context: I'm trying to call a complicated C function from Swift. The function is part of the unit testing of a C library (hence the motley crew of parameters). The library is only available as a dylib
so I can't touch the C source and, to make matters interesting, the dylib
was generated by a FORTRAN compiler and is a military product subject to export restrictions.
This is the C signature of the testing function, as presented in the library documentation:
extern void TestInterface(char cIn, char* cOut,
int intIn, int* intOut,
__int64 longIn, __int64* longOut,
double realIn, double* realOut,
char strIn[512], char strOut[512],
int int1DIn[3], int int1DOut[3],
__int64 long1DIn[3], __int64 long1DOut[3],
double real1DIn[3], double real1DOut[3],
int int2DIn[2][3], int int2DOut[2][3],
__int64 long2DIn[2][3], __int64 long2DOut[2][3],
double real2DIn[2][3], double real2DOut[2][3]);
I've placed this prototype in a bridging-header and Swift accepts it (with the exception of __int64
needing changed to __int64_t
). My troubles begin! I've searched high and low for examples of how Swift sees this function .. the examples I have found have been trivial in comparison, for example: Apple Developer Documentation so:
- Question 1 -- Is it possible to see how Swift imported the above? Seeing that would give me good hints related to how Swift should pass parameters in and out. Currently, such info is only implied by the errors generated, like:
Cannot convert value of type '[[Double]]' to expected argument type 'UnsafeMutablePointer<(Double, Double, Double)>?'
I find myself twisting around the language to supply parameters when calling this monstrosity. Even the first parameter, a simple char
, has me providing, at the call site:
CChar(("A" as Character).asciiValue!)
.. I could use Int8(65)
, less typing, but it obscures the intent even more.
My brain hurt when I used pointers in C decades ago, and since Swift goes Unsafe..
when pointers are involved I, frankly, am out of my depth. So:
- Question 2 -- What would be the best resource to learn how to, for example, manipulate the Swift
var real2D: [[Double]] = Array(repeating: Array(repeating: 0.0, count: 3), count: 2)
into a form that I could pass into thedouble real2DIn[2][3]
parameter of that function?
I expect once I can get the lightbulb in my head switched on, this will all seem easy and obvious; I'm not there yet.
The functions in the library that are not related to unit testing are all much simpler and, for now, as far as I've got, I have managed to tame them with @convention(c)
..
func envGetInfo(dllHandle: UnsafeMutableRawPointer) -> String {
guard let envGetInfoPointer = dlsym(dllHandle, "EnvGetInfo") else {
fatalError("dlsym failure: \(String(cString: dlerror()))")
}
var info128 = Array(repeating: Int8(0), count: 128)
typealias EnvGetInfoFunction = @convention(c) (UnsafeMutablePointer<Int8>) -> Void
let envGetInfo = unsafeBitCast(envGetInfoPointer, to: EnvGetInfoFunction.self)
envGetInfo(&info128); info128[127] = 0
return String(cString: info128).trimmingCharacters(in: .whitespaces)
}
- This is rather ugly; would it be less so if this function was also defined in the bridging header? What criteria should considered when deciding between using the bridging-header and @convention(c)?
I've spun this topic off as a separate task while I continue learning and searching for my own answers; I suspect I'm struggling with a complicated version of something, essentially, simple. If I become knowledgable before I get responses here, I'll share my findings.