Convert UnsafeMutablePointer<Int8> to String

Ok, so I am trying to convert an UnsafeMutablePointer<Int8> to a String. I have a C function that returns the executable path of a process using it's PID. In swift, I would like to convert this path to a string, but it is of the UnsafeMutablePointer... type.

Here is the C code:

char * getRootProcessPathUsingPID(pid_t pid) {
    int ret;
    char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
    
    ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf));
    if (ret <= 0) {
        fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
        fprintf(stderr, "    %s\n", strerror(errno));
        return 1;
        
        } else {
            //printf("%s", pathbuf);
        }
    return &pathbuf; // as mem adress to char or just char?
}

Here is my Swift Implementation:
print(String(format:"%s", getRootProcessPathUsingPID(546))) prints the path correctly.

print(getRootProcessPathUsingPID(546))
let str: String? = String(validatingUTF8: getRootProcessPathUsingPID(546))
print(str)

returns Optional("/Volumes/Broken/Applications/Spotify.app/Contents/MacOS/Spotify")

How do I just convert it to a string?

Sorry if this is a dumb question or if it has already been asked.

There's nothing particularly wrong with the format string approach if you're confident you never get garbled data. Your second approach is safer. In order to unwrap the string, use the if let construct:

if let str = String(validatingUTF8: getRootProcessPathUsingPID(546) {
  // do something useful with `str`
}
else
{ /* data was unusable */ }
1 Like

Thanks! That is a very good approach. Now when I print it, it says Optional("/Volumes/Broken/Applications/Spotify.app/Contents/MacOS/Spotify")

How do I get rid of the optional? I'm sure that is not in the variable itself, just when it is printed. I am still learning casting and things like that.

Oh my bad. When printing, add a ! to the end of the string or variable. Then you won't get the "Optional" in front if it :slight_smile:

In your C routine, pathbuf will be be deallocated when the routine returns because it is allocated on the stack. You are returning a pointer to an area of memory that is really no longer valid. It may work depending on the stack frames, usage, and compiler/environment, but, it is an invalid memory access waiting to happen. It is considered, at best, indeterminate behavior to return a pointer to a local variable. In most C++ compilers these days, this would actually be a compiler error. You either have to have the user pass a character array as an argument that you can fill, or allocate a character array using malloc() and return the malloc address. Freeing the pointer with free() would need to be done in Swift (I think it can be done).

You should also return either pathbuf by itself (in C, arrays are interchangeable with pointers), or &pathbuf[0], to be safe. Technically, you are returning a pointer to a pointer.

2 Likes

You could also make pathbuf a static character array. If you don't care about re-entrancy or concurrent operations, this can work.

1 Like

Thank you for the explanation! I would have not figured that out. Sometimes you just have to walk through your code and extensively criticize it. I think it would have helped if I looked at the source code for the proc_pidpath first.

How would I do that?

Here's a couple of examples:

Easiest is the static buffer.

char * getRootProcessPathUsingPID(pid_t pid) {
    int ret;
    static char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
    
    ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf));
    if (ret <= 0) {
        fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
        fprintf(stderr, "    %s\n", strerror(errno));
        return 1;
        
        } else {
            //printf("%s", pathbuf);
        }
    return &pathbuf[0]; // as mem adress to start of char buffer
}

Just added the keyword static to the pathbuf declaration. This declaration puts the buffer in static storage that survives invocations of the routine.

Next easiest is the "passing a buffer":

char * getRootProcessPathUsingPID(pid_t pid, char * pathbuf, size_t maxSize)  {
    int ret;

    /* Assuming in-bound buffer is big enough to hold path */

    ret = proc_pidpath (pid, pathbuf, maxSize);
    if (ret <= 0) {
        fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
        fprintf(stderr, "    %s\n", strerror(errno));
        return 1;
        
        } else {
            //printf("%s", pathbuf);
        }
    return &pathbuf[0]; // as mem adress to start of char buffer
}
1 Like

:grinning:Thank you! That all makes sense now.

proc_pidpath() fills the buffer with a C string containing the “file system representation” of the process, and URL has dedicated methods to handle that:

let fsPath = getRootProcessPathUsingPID(546)
let url = URL(fileURLWithFileSystemRepresentation: fsPath,
          isDirectory: false, relativeTo: nil)
let path = url.path
print(path)

Note that you can call proc_pidpath() directly from Swift, without a C helper function:

func processPath(forPid pid: pid_t) -> String? {

    var pathbuffer: [CChar] = Array(repeating: 0, count: Int(PROC_PIDPATHINFO_SIZE))
    let ret = proc_pidpath(pid, &pathbuffer, UInt32(pathbuffer.count))
    if ret <= 0 {
        print("proc_pidpath:", String(cString: strerror(errno)))
        return nil
    }
    
    let url = URL(fileURLWithFileSystemRepresentation: pathbuffer,
                  isDirectory: false, relativeTo: nil)
    return url.path
}

if you add

#include <libproc.h>
#include <sys/proc_info.h>

to the bridging header file.

1 Like

Unless I am mistaken, both return &pathbuf and return pathbuf return the address of the first array element in C.

1 Like

Not quite.

1 Like

You are right that the types are different. &pathbuf is a pointer to an array (not a pointer to a pointer) and its value is the address of the first array element. Therefore the compiler warns at return &pathbuf about incompatible types, and @jonprescott made the right suggestion.

1 Like

I learn something new everyday! I would have never figured that out in a million years.

Update:
I think that this is a rather different yet functional method to accomplish the task desired. I had no idea you could use C libraries natively in swift, by only using the bridging header. Now I can get rid of the C code and focus more on going in-depth and learning Swift. Does anybody have recommendations for learning Swift 5?

In this context URL(fileURLWithFileSystemRepresentation:...) is the proper method (I think), but here are some remarks about the other conversion methods:

  • String(validatingUTF8:) returns (as you noticed) an optional, which is nil if called with an ill-formed UTF-8 sequence.

  • An alternative is String(cString:) which “repairs” ill-formed UTF-8 sequences by replacing them with the Unicode replacement character U+FFFD. It returns a (non-optional) string.

  • String(format:) is problematic because it is not Unicode safe, as the following example demonstrates:

    "Aä€👺X".withCString { cStringPtr in
        let s = String(format: "%s", cStringPtr)
        print(s) // A√§‚Ǩüë∫X¨
    }
    
2 Likes