Swift: convert const char ** output parameter to String - ios

I'm interacting with a C++ library (with the header in C) which uses const char ** as an output parameter.
After executing a method in that library, the value I need is written in that variable, for example:
CustomMethod(const char **output)
CustomMethod(&output)
// Using the `output` here
Normally, in Swift it's possible to pass just a standard Swift String as a parameter and it will be transparently transformed into the const char * (Interacting with C Pointers - Swift Blog).
For example, I already use the following construct a lot with the same library:
// C
BasicMethod(const char *input)
// Swift
let string = "test"
BasicMethod(string)
However, when it comes to working with const char **, I couldn't just pass a pointer to the Swift String, as I'd expected:
// C
CustomMethod(const char **output)
// Swift
var output: String?
CustomMethod(&output)
Getting an error:
Cannot convert value of type 'UnsafeMutablePointer<String?>' to
expected argument type 'UnsafeMutablePointer<UnsafePointer?>'
(aka 'UnsafeMutablePointer<Optional<UnsafePointer>>')
The only way I could make it work is by manipulating the pointers directly:
// C
CustomMethod(const char **output)
// Swift
var output: UnsafePointer<CChar>?
CustomMethod(&output)
let stringValue = String(cString: json)
Is there any way to use the automatic Swift string to const char ** conversion, or does it only work with const char *?

The bridged C function expects a mutable pointer to a CChar pointer, so you'll need to provide one, there's no automatic bridging here.
var characters: UnsafePointer<CChar>?
withUnsafeMutablePointer(to: &characters) {
CustomMethod($0)
}
if let characters = characters {
let receivedString = String(cString: characters)
print(receivedString)
}
Same code, but in a more FP manner:
var characters: UnsafePointer<CChar>?
withUnsafeMutablePointer(to: &characters, CustomMethod)
var receivedString = characters.map(String.init)
print(receivedString)

Related

Calling C function from Swift shows different results in CLion than in Xcode [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 9 months ago.
Improve this question
I am trying to call a C function from Swift , but I do not know exactly how to define variables to pass parameters. This is the function declaration:
/* Function Declarations */
extern void compute_feature_set(const double input[11025],
double M_data[], int M_size[2],
double D_data[], int D_size[2],
double DD_data[],
int DD_size[2],
double VT[10], double *mp,
double r_42[42], double *fM);
The data is an array of floats. So I tried ​​:
let s = data.compactMap{ Double($0)}
var mSize = Array<Int32>(repeating:Int32(0.0), count:2)
var dSize = Array<Int32>(repeating:Int32(0.0), count:2)
var dD_Size = Array<Int32>(repeating:Int32(0.0), count:2)
var mData = Array<Double>(repeating:0.0, count:48)
var dData = Array<Double>(repeating:0.0, count:48)
var dD_Data = Array<Double>(repeating:0.0, count:48)
var vt = Array<Double>(repeating:0.0, count:10)
var mp = Double(0.0)
var r = Array<Double>(repeating:0.0, count:42)
var fM = Double(0)
compute_feature_set(s, &cout, &mSize, &vx, &dSize, &dD_Data, &dD_Size, &vcta, &mp, &r, &fM)
When I run the code in Clion with the following function it works fine and the output matches the expected values:
static void main_compute_feature_set(void)
{
static double dv[11025];
double DD_data[48];
double D_data[48];
double M_data[48];
double r_42[42];
double VT[10];
double fM;
double mp;
int DD_size[2];
int D_size[2];
int M_size[2];
/* Initialize function 'compute_feature_set' input arguments. */
/* Initialize function input argument 'input'. */
/* Call the entry-point 'compute_feature_set'. */
argInit_11025x1_real_T(dv);
compute_feature_set(dv, M_data, M_size, D_data, D_size,
DD_data, Dd_size, VT,
&mp, r_42, &fM);
}
However, when I run my implementation in Swift, I get very different results.
You could try passing pointers of the Arrays, rather than the Arrays directly.
Using Imported C Functions in Swift | Apple Developer Documentation
Call Functions with Pointer Parameters
Whenever possible, Swift avoids giving you direct access to pointers. When importing C function parameters, however, Swift maps pointer parameters to standard library pointer types.
The following tables use Type as a placeholder type name to indicate syntax for the mappings.
For return types, variables, and arguments, the following mappings apply:
C Syntax
Swift Syntax
const Type *
UnsafePointer<Type>
Type *
UnsafeMutablePointer<Type>
double[] is pretty much equivalent to double * in this case.
Looks like the problem with your code is passing data to your function. You use compactMap to make an Array of Double and then pass the pointer of this array. But Array and Double are struct in Swift so you pass the pointer of struct with structs instead of array of double values.
To convert your data to array of bytes you should use withUnsafeBytes e.g.:
Swift:
let data = Data([0xaa, 0xbb, 0xcc, 0xdd])
data.withUnsafeBytes {
passData($0)
}
C/ObjC:
void passData(const double input[11025]) {
NSLog(#"%x", input[0]); // Prints: ddccbbaa
}

Calling C function from Swift

I am trying to call a C function from Swift , but I do not know exactly how to define variables to pass parameters.
The function c is:
DBFGetFieldInfo( DBFHandle psDBF, int iField, char * pszFieldName, int * pnWidth, int * pnDecimals );
The main problem is pszFieldName, pnWidth and pnDecimals inout parameters. I tried made ​​:
var dbf:DBFHandle = DBFOpen(pszPath, "rb")
var fName:[CChar] = []
var fieldWidth:Int32 = 0
let fieldDecimals:Int32 = 0
let fieldInfo:DBFFieldType = DBFGetFieldInfo(dbf, i, fName, &fieldWidth, &fieldDecimals)
but it gives me an error
Cannot invoke 'DBFGetFieldInfo' with an argument list of type '(DBFHandle, Int32, [CChar], inout Int32, inout Int32)'
Expected an argument list of type '(DBFHandle, Int32, UnsafeMutablePointer<Int8>, UnsafeMutablePointer<Int32>, UnsafeMutablePointer<Int32>)'
Any ideas?
UnsafeMutablePointer<Int8>, UnsafeMutablePointer<Int32>, UnsafeMutablePointer<Int32>
You need to convert your variables to the appropriate types required by the method signature.
C Syntax:
const Type *
Type *
Swift Syntax:
UnsafePointer
UnsafeMutablePointer
This is covered by Apple in their Using Swift with Cocoa and Objective-C reference located here.
C Syntax -----> Swift Syntax
const Type * -----> UnsafePointer
Type * -----> UnsafeMutablePointer
The number of input and the types should be the same
To create an UnsafeMutablePointer<Int8> from a string use:
String(count: 10, repeatedValue: Character("\0")).withCString( { cString in
println()
// Call your function here with cString
})

Issue with converting NSString * to String and passing Character as parameter in Swift iOS

I am calling a Objective method from Swift by using a Bridging header.
-(NSString *) PatternSetCreator: (char)Signature detection_time_in_sec:(int)detection_time_in_sec patternLength:(int)patternLength maxPatternSetSize:(int)maxPatternSetSize
There are two issuse:
a) I am not able to pass a single character as parameter while calling this method from Swift
b) I am not exactly sure how to get the return type NSString and assign it to a String variable
A single C char in Swift is represented as CChar, a typealias for Int8.
(similarly, C int in Swift is CInt, a typealias for Int32)
If you want a specific character and are using Swift 1.2, there’s an initializer for UInt8 that takes a UnicodeScalar. Annoyingly, though, you have to then convert it to a Int8 to make it compatible with the C method:
let ch = CChar(UInt8(ascii: "x"))
let i = CInt(100)
let s = obj.PatternSetCreator(ch,
detection_time_in_sec: i,
patternLength: i,
maxPatternSetSize: i)
You should not need to do anything special to turn the returned NSString to a String. The bridging will do that automatically.
(or rather, it’ll return a String! – but if the objective c code is guaranteed to return a valid string every time and never a null pointer, the definition can be changed to -(nonnull NSString *) PatternSetCreator: etc… which means it will return a String instead)

Swift String to unsigned char [](CUnsignedChar)

Hello i'm trying to use an old legacy C library that uses buffers (unsigned char []) to transform the data. The main issue here is that I couldn't find a way to transform a String to a CUnsignedChar and then be able to alloc that buffer to a UnsafeMutablePointer
If you want to convert a Swift string to an immutable C string to pass to a C function, try this:
let s: String = "hello, world!"
s.nulTerminatedUTF8.withUnsafeBufferPointer { p -> Void in
puts(UnsafePointer<Int8>(p.baseAddress))
Void()
}
You may need to use UnsafePointer<UInt8> if your function takes an unsigned char *.
I turn String to NSData first ,then turn NSData to CUnsignedChar array
if let data = str.dataUsingEncoding(NSUTF8StringEncoding){
var rawData = [CUnsignedChar](count: data.length,repeatedValue: 0)
data.getBytes(&rawData, length: data.length)
}

Use C function in Swift

I want to use a C function in Swift, which has the following method definition:
int startTest(char *test1, char* test2)
If I call this method from my Swift code like this
startTest("test1", "test2")
I get the following error message:
'String' is not convertible to 'UnsafeMutablePointer<Int8>'
If I change my method definition to:
int startTest(const char *test1, const char* test2)
and call that method like this:
var test1 = "test1"
var test2 = "test2"
startTest(&test1, &test2)
I get
'String' is not identical to 'Int8'
So my question is: how can I use the C function? (it is part of a library, so changing the method call could be problematic).
Thanks in advance!
In the case of
int startTest(const char *test1, const char* test2);
you can call the function from Swift simply as
let result = startTest(test1, test2)
(without the address-of operators). The Swift strings are converted automatically
to C Strings for the function call
In the case of
int startTest(char *test1, char* test2);
you need to call the function with a (variable) Int8 buffer, because the Swift
compiler must assume that the strings might be modified from the C function.
Example:
var cString1 = test1.cStringUsingEncoding(NSUTF8StringEncoding)!
var cString2 = test2.cStringUsingEncoding(NSUTF8StringEncoding)!
let result = startTest(&cString1, &cString2)

Resources