The following code gets me the result I want in the NSLog but there must be an easier way to do this? I'm also getting a 'format string is not a string literal' error, which still builds okay.
NSString *levelstring1 = #"Level ";
NSString *levelstring2 = [NSString stringWithFormat:#"%d", levelscore];
NSString *levelstring3 = #" (";
NSString *levelstring4 = [NSString stringWithFormat:#"%d", xpscore];
NSString *levelstring5 = #"/";
NSString *levelstring6 = [NSString stringWithFormat:#"%d", (levelscore*(levelscore+1)*100)];
NSString *levelstring7 = #")";
NSString *levelstringfinal = [[[[[[levelstring1 stringByAppendingFormat:levelstring2]stringByAppendingFormat:levelstring3]stringByAppendingFormat:levelstring4]stringByAppendingFormat:levelstring5]stringByAppendingFormat:levelstring6]stringByAppendingFormat:levelstring7];
NSLog(#"Level is %#",levelstringfinal);
The end result looks something like this: Level 1 (50/100).
You can combine them into something like:
NSString *levelstringfinal = [NSString stringWithFormat:#"Level %d (%d/%d)", levelscore, xpscore, (levelscore*(levelscore+1)*100)];
The warning you're getting is because the compiler can't check a variable's content, whereas with a string literal it can match placeholders and variables for some degree of consistency.
(Even if you had to build the string in pieces, the last line would be better using stringByAppendingString rather than stringByAppendingFormat because those are no longer format specifiers.)
Related
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)
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.
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)
Using format!, I can create a String from a format string, but what if I already have a String that I'd like to append to? I would like to avoid allocating the second string just to copy it and throw away the allocation.
let s = "hello ".to_string();
append!(s, "{}", 5); // Doesn't exist
A close equivalent in C/C++ would be snprintf.
I see now that String implements Write, so we can use write!:
use std::fmt::Write;
pub fn main() {
let mut a = "hello ".to_string();
write!(a, "{}", 5).unwrap();
println!("{}", a);
assert_eq!("hello 5", a);
}
(Playground)
It is impossible for this write! call to return an Err, at least as of Rust 1.47, so the unwrap should not cause concern.
In previous betas, I was able to pass a pointer to anything to C doing something like this
var aString : NSString = "This is a string" // create an NSString
var anUnmanaged = Unmanaged.passUnretained(aString) // take an unmanaged pointer
var opaque : COpaquePointer = anUnmanaged.toOpaque() // convert it to a COpaquePointer
var mut : CMutablePointer = &opaque // this is a CMutablePointer
doSomething(mut) // pass the pointer to an helper function written in C
With the C function like this:
void doSomething(void ** ptr)
{
// do something with the pointer, change it and make it point to another nsstring for example
}
Now, (for sure in Beta4, maybe even in Beta3) the syntax translated from C to Swift has changed
Before, the doSomething function was expecting a CMutablePointer (that now has been removed from Swift)
Now, it expects a:
UnsafePointer<UnsafePointer<()>>
() is a typealias to Void
So, syntactically talking is more clear.
However, Now I can't understand how to take the void** pointer of any object like I was doing in previous betas.
Any suggestion?
This worked for me:
var str = "Hello, playground"
var p: UnsafePointer<Void> = unsafeAddressOf(str)
Can you try this:
let strPtr = unsafeBitCast(aString, UnsafePointer<Void>.self)
let strPtrPtr = unsafeBitCast(strPtr, UnsafePointer<Void>.self)
And then:
doSomething(strPtrPtr)
Not sure if this is the correct answer, I hope that someone has a better method to do this.
The main problem was that there is no direct conversion between a Swift Object and a Void UnsafePointer ( UnsafePointer<()> )
However I managed how to do it passing through a COpaquePointer
// since I can't go directly from NSMutableString to UnsafePointer<()> (which is UnsafePointer<Void>) i pass through a COpaquePointer
var aString : NSMutableString = NSMutableString(string: "test") // create an NSString
var anUnmanaged = Unmanaged.passUnretained(aString) // take an unmanaged pointer
var opaque : COpaquePointer = anUnmanaged.toOpaque() // convert it to a COpaquePointer
var anUnsafe : UnsafePointer<()> = UnsafePointer(opaque) // convert it to an UnsafePointer to Void ( () is a Typealias to Void) using the init(_ other: COpaquePointer) initializer
doSomething(&anotherUnsafe)