Swift to C bridging: String to UnsafePointer<Int8>? is not automatically bridged? - c

While trying to interface with a C library (Vulkan) I am faced with the following error while trying to assign a Swift(4.2) native String to C String
error: cannot assign value of type 'String' to type 'UnsafePointer<Int8>?'
I'm doing a simple assignment
var appInfo = VkApplicationInfo()
appInfo.pApplicationName = "Hello world"
Wasn't Swift supposed to handle these through its automatic bridging?

The automatic creation of a C string representation from a Swift String is only done when calling a function taking a UnsafePointer<Int8> argument (compare String value to UnsafePointer<UInt8> function parameter behavior), and the C string is only valid for the duration of the function call.
If the C string is only need for a limited lifetime then you can do
let str = "Hello world"
str.withCString { cStringPtr in
var appInfo = VkApplicationInfo()
appInfo.pApplicationName = cStringPtr
// ...
}
For a longer lifetime you can duplicate the string:
let str = "Hello world"
let cStringPtr = strdup(str)! // Error checking omitted for brevity
var appInfo = VkApplicationInfo()
appInfo.pApplicationName = UnsafePointer(cStringPtr)
and release the memory if it is no longer needed:
free(cStringPtr)

Related

How do I pass a multiline string from Python to C using ctypes?

I have the following simple C function:
void pyentry(const char *config)
{
printf("%s\n",config);
fflush(stdout);
}
My ctypes definition is as follows:
libc = ct.CDLL("./amr.so")
entry = wrap_function(libc, 'pyentry', ct.POINTER(Checkpoint), [ct.c_wchar_p])
json = "this is a test"
start = entry(json)
Where wrap_function is simply a wrapper for more easily defining ctypes access to C functions:
def wrap_function(lib, funcname, restype, argtypes):
func = lib.__getattr__(funcname)
func.restype = restype
func.argtypes = argtypes
return func
I have compiled as a shared library and I am trying to call it, but in C it is only printing the first character of the string I send in. I am assuming this is because I have the wrong argument tpyes in my ctypes definition, but I am not having any luck figuring out out the right one.
Can someone tell me why my C function is only seeing the first character in the passed string?
Try:
entry = wrap_function(libc, 'pyentry', None, [ct.POINTER(ct.c_char)])
json = "this is a test".encode('utf-8')
pyentry takes const char* and returns void. So argtypes and restype could be [ct.POINTER(ct.c_char)] and None.
And char* points a sequence of bytes, not Python string. So json should be converted to bytes.

Swift: Turning a String into UnsafeMutablePointer<Int8>

I have a C function mapped to Swift defined as
predictX010(inputString: UnsafePointer<emxArray_char_T>!, ypred: UnsafeMutablePointer<emxArray_real_T>!)
I want to input a string in the inputString, but in order to do that I have to play around with emxArray_char_T which is
emxArray_char_T.init(data: UnsafeMutablePointer<Int8>!, size: UnsafeMutablePointer<Int32>!, allocatedSize: Int32, numDimensions: Int32, canFreeData: boolean_T)
My string will consist of let x = ":1580222503,GCP001,007,Male,30,Left,1,IL8 and IL10,0000; 0,281411,-78,521074,-3,344657,132,347776,-93,25,44" and I just cannot figure out how to input it in data of the emxArray_char_T
First you should write a wrapper function in your bridging header that accepts char pointers then cast/create the emxArray_char_T in c IE:
// casting the func as void because you didn't specify
void predictWrapper(const char *aChar, char *bChar) {
// do your casting and call original func predictX010(...)
}
Then in swift (This isn't going to be pretty)
var arg: String = "some arg"
var arg2: String = "another arg"
// use closure to prevent dangling pointer
arg.withCString{ body in // body is UnsafePointer<Int8>
arg2.withCString{ body2 in // we'll cast this to UnsafeMutablePointer
var arg2Mutable = UnsafeMutablePointer<Int8>(mutating: body2)
//call your wrapper
predictWrapper(body, arg2Mutable)
}
}
You may be able to use the original types and function, but i've always found it easier (less banging my head on the desk) to use the most standard c types you can in swift and casting to custom/complex types in c

How to pass-by-reference from Swift to C library

I am creating a cross-platform library for Windows and macOS that is written in C, and I need to pass a variable by reference. How do I define the function in C so that it can receive a variable passed by reference, and how do I call the function in Swift?
For example, if I were to define a function in my C library like:
int sayBye(const char* greetingString){
greetingString = "bye";
return 0;
}
and call it in Swift like:
var greeting: String = "hello"
let responseCode = sayBye(greeting)
print(greeting)
I want the print statement to output "bye".
Constraints: "bye" cannot be returned in the return statement.
In short, I want to modify the contents of the string "greeting" after it has been passed by reference to a C-function.
Note: I'm not sure if, in the code above, I am defining the C function correctly or if I am calling it correctly in swift, and I am open to recommendations.
Found something that worked.
In the C library:
int sayBye(const char** greetingString){
*greetingString = "bye";
return 0;
}
Called in Swift:
var greeting = ("hello" as NSString).utf8String
let responseCode = sayBye(&greeting)
let stringFromPointer = String.init(cString: greeting!)
print(stringFromPointer)

Assign an array of Swift strings to a C structure variable taking a char ** value

I'm trying to interact with an old C terminal app/library from Swift. I've successfully integrated the source code and bridged the headers from C to Swift. The code compiles and runs, I can access all functions from C - library into swift.
There is a structure in C - library which I need to initialize[Function already exists which takes pointer] and assign values to structure variables[Manually].
C-structure:
Struct args{
char ** var1;
unsigned char * var2;
char * var3;
}
and Initialization function call:
init(args * ptr);
How to call the function inside swift and assign values to var1 and var2?
1.Will following snippet successfully initialize the structure?
let Ptr = UnsafeMutablePointer<args>.allocate(capacity: 1)
var args = args()
Ptr.pointee = args
init(Ptr)
2.How to assign values to var1, var2 & var3 assuming we successfully initialize?
They are mapped as:
var1: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!
var2: UnsafeMutablePointer<Uint8>!
var3: UnsafeMutablePointer<Int8>!
For example
var1 = {"a", "b"}, var2 = {1,2,3} and var3 = "a"
I've tested following links and did not work:
How to pass an array of Swift strings to a C function taking a char ** parameter
: gives 'inout[UnsafeMutablePointer?] to type UnsafeMutablePointer?>!' error
Convert a Swift Array of String to a to a C string array pointer
: gives 'inout[UnsafeMutablePointer?] to type UnsafeMutablePointer?>!' error
No built-in support for arrays of C strings : this one needs more efforts and hoping to get easier version
github - Swift wrappers for C functions taking char** arguments
: gives 'inout[UnsafeMutablePointer] to type UnsafeMutablePointer?>!' error
This is quite a broad question, so here are some references and observations with a few examples. Hopefully these are helpful.
Please see Apple's documentation for UnsafeMutablePointer struct and also String and NSString:
https://developer.apple.com/documentation/swift/unsafemutablepointer
https://developer.apple.com/documentation/swift/string
https://developer.apple.com/documentation/foundation/nsstring
Another useful reading is Apple's docs about C and Swift interop: https://developer.apple.com/documentation/swift/imported_c_and_objective_c_apis
In this answer I'm also leaving out a lot of memory management aspects as well as things such as keeping track of the size of var1 and var2 arrays, since I don't know the specifics of your library.
Regarding the snippet for initializing the structure, you can't use the type name as the variable name and init will confuse the Swift compiler because it's reserved for naming class initializers. Let's name the variable myArgs instead of args and assume the C library initialization function is named initialize; if it's indeed init, one can easily write a wrapper named differently. Another problem with the snippet is that myArgs will remain unchanged after initialization, Ptr will actually get initialized, so you would have to use Ptr to access the initialized args structure. Thus we can omit Ptr and use implicit bridging to pass myArgs to the initialization function. The snippet becomes
var myArgs = args()
initialize(&myArgs)
Now you can access the members as follows:
// Assuming var1 is an array of at least 2 C strings.
// See Swift documentation about optionals on how to deal with
// cases when this assumption breaks down
let s1 = String(cString: myArgs.var1[0]!) // 1st element of var1
let s2 = String(cString: myArgs.var1[1]!) // 2nd element of var1
myArgs.var2.pointee // 1st element of var2
(myArgs.var2 + 1).pointee // 2nd element of var2
let s = String(cString: myArgs.var3) // value of var3
Now let's set var1 to be {"aa", "bbb"}:
var var1Buffer =
UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
var var1a : NSString = "aa"
var var1b : NSString = "bbb"
var var1aBuffer = UnsafeMutablePointer<Int8>.allocate(
capacity: var1a.length + 1)
var var1bBuffer = UnsafeMutablePointer<Int8>.allocate(
capacity: var1b.length + 1)
if (var1a.getCString(var1aBuffer, maxLength: var1a.length + 1,
encoding: String.Encoding.utf8.rawValue)
&& var1b.getCString(var1bBuffer, maxLength: var1b.length + 1,
encoding: String.Encoding.utf8.rawValue)) {
var1Buffer[0] = var1aBuffer
var1Buffer[1] = var1bBuffer
myArgs.var1 = var1Buffer
} else { print("Encoding failed...")}
Here is an example of setting var2 to be an array of 5 elements equal to 200:
var var2Buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 5);
var2Buffer.initialize(repeating: 200, count: 5)
myArgs.var2 = var2Buffer
And setting the value of var3:
let newVar3 : NSString = "This is new variable 3"
var var3Buffer = UnsafeMutablePointer<Int8>.allocate(capacity: newVar3.length + 1)
if (newVar3.getCString(var3Buffer, maxLength: newVar3.length + 1, encoding: String.Encoding.utf8.rawValue)) {
myArgs.var3 = var3Buffer
} else { print("Encoding failed...") }
The above examples assume UTF8 encoding.

Converting from Swift string to const char*

I'm trying to pass a const char * to an old C library converted from a Swift string in Swift.
This is the C function I'm calling:
artnet_node artnet_new(const char *ip, int verbose) { ...
how can I convert a Swift string to this const char type? It works when I pass ipAddress like this:
internal var ipAddress = "192.168.1.43"
but dit does not work when I pass it like this
internal var ipAddress:String = "192.168.1.43"
I need this in a function where I need to specify the type:
internal func setupArtnode(ip:String) -> Int{
I tried using AnyObject instead of String but that doesn't work either.
Thanks.
You should be able to pass a String directly to a C function expecting const char * and it will be automatically converted to a null-terminated UTF-8 string:
let string = "string"
let node = artnet_new(string, 1)
See Interacting with C APIs for more information. Here is the relevant excerpt:
When a function is declared as taking an UnsafePointer argument,
it can accept any of the following:
A String value, if Type is Int8 or UInt8. The string will automatically be converted to UTF8 in a buffer, and a pointer to that
buffer is passed to the function.
Not sure why but this code is working. This passes a string to a C function expecting a const char* which seems to be the same as a unsafePointer.
internal func setupArtnode(ipAddress:String) -> NSInteger{
let cString = self.ipAddress.cString(using: String.defaultCStringEncoding)!
let newString:String = NSString(bytes: cString, length: Int(ipAddress.characters.count), encoding:String.Encoding.ascii.rawValue)! as String
let key2Pointer = UnsafePointer<Int8>(newString)
node = artnet_new(key2Pointer, Int32(verbose)) // VERBOSE : true(1) , false(0)
...
Simple way for Swift 3
var ipAddress: String = "192.168.1.43"
var verbose: Int = 1
artnet_node artnet_new((ipAddress as NSString).utf8String, verbose)
You didn't specify what your Swift array contains. In any case, you need to convert your Swift array to an array of Int8:
let str = "Hello world"
let cArray = str.cString(using: .utf8)
artnet_new(cArray, 1)

Resources