Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Standard Directories on OS X with C++

Tags:

c++

macos

What is the modern, standard, canonical method for accessing standard directories on OS X from C++, such as ~/Library/Application Support or ~/Library/Preferences?

I've seen mention of the use CoreServices, but it's also mentioned that it's deprecated and I'm having trouble finding the documentation that will allow me to do more than just paste the code in.

I've found mention of using Objective C++, but most information about that option revolves around calling C++ code from Objective C, and again, Apple's documentation on it seems rather sparse, or at least I've not had success finding it.

like image 753
Chuck Avatar asked Apr 14 '16 21:04

Chuck


People also ask

Where are the C libraries in MacOS?

On Mac and iOS the C Standard Library implementation is part of libSystem, a core library located in /usr/lib/libSystem. dylib . LibSystem includes other components such as the math library, the thread library and other low-level utilities.

How do you go to a directory in Terminal Mac?

If you type cd .. (that's two periods), you'll go to the directory above the one you're currently in. So if you're in your home folder, and type cd .. , you'll go to your Mac's /Users folder. And if you type cd - (hyphen) you'll go back to the directory you were in before the last time you issued the cd command.

What is $PATH in Mac?

PATH is an essential environment variable that decides how programs and commands work on macOS. Setting the PATH variable for a program or script allows you to run it from anywhere on the file system without specifying its absolute path.


1 Answers

Well, there is the little-known NSSystemDirectories.h (at /usr/include/NSSystemDirectories.h) header that has existed on every version of OS X I checked all the way back to OS X 10.3. It's part of libc: http://opensource.apple.com//source/Libc/Libc-825.40.1/include/NSSystemDirectories.h

That provides a C API, though it's not too difficult to create an Objective-C++ wrapper: create a pure C++ interface and in the implementation, use Objective-C.

FolderManager.h:

#include <stdio.h>
namespace fm {
    enum {
        NSApplicationDirectory = 1,
        NSDemoApplicationDirectory,
        NSDeveloperApplicationDirectory,
        NSAdminApplicationDirectory,
        NSLibraryDirectory,
        NSDeveloperDirectory,
        NSUserDirectory,
        NSDocumentationDirectory,
        NSDocumentDirectory,
        NSCoreServiceDirectory,
        NSAutosavedInformationDirectory = 11,
        NSDesktopDirectory = 12,
        NSCachesDirectory = 13,
        NSApplicationSupportDirectory = 14,
        NSDownloadsDirectory = 15,
        NSInputMethodsDirectory = 16,
        NSMoviesDirectory = 17,
        NSMusicDirectory = 18,
        NSPicturesDirectory = 19,
        NSPrinterDescriptionDirectory = 20,
        NSSharedPublicDirectory = 21,
        NSPreferencePanesDirectory = 22,
        NSApplicationScriptsDirectory = 23,
        NSItemReplacementDirectory = 99,
        NSAllApplicationsDirectory = 100,
        NSAllLibrariesDirectory = 101,
        NSTrashDirectory = 102
    };
    typedef unsigned long SearchPathDirectory;

    enum {
        NSUserDomainMask = 1,       // user's home directory --- place to install user's personal items (~)
        NSLocalDomainMask = 2,      // local to the current machine --- place to install items available to everyone on this machine (/Library)
        NSNetworkDomainMask = 4,    // publically available location in the local area network --- place to install items available on the network (/Network)
        NSSystemDomainMask = 8,     // provided by Apple, unmodifiable (/System)
        NSAllDomainsMask = 0x0ffff  // all domains: all of the above and future items
    };
    typedef unsigned long SearchPathDomainMask;

    class FolderManager {
        public:

        FolderManager();
        ~FolderManager();

        const char *pathForDirectory(SearchPathDirectory directory, SearchPathDomainMask domainMask);
        const char *pathForDirectoryAppropriateForItemAtPath(SearchPathDirectory directory, SearchPathDomainMask domainMask, const char *itemPath, bool create = false);

        private:
            void *m_autoreleasePool;
    };
};

FolderManager.mm (note the filename extension)

#include "FolderManager.h"
#import <Foundation/Foundation.h>

using namespace fm;

FolderManager::FolderManager() {
    m_autoreleasePool = [[NSAutoreleasePool alloc] init];
}

FolderManager::~FolderManager() {
    [(NSAutoreleasePool *)m_autoreleasePool release];
}

const char * FolderManager::pathForDirectory(SearchPathDirectory directory, SearchPathDomainMask domainMask) {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *URLs = [fileManager URLsForDirectory:(NSSearchPathDirectory)directory inDomains:domainMask];
    if (URLs.count == 0) return NULL;

    NSURL *URL = [URLs objectAtIndex:0];
    NSString *path = URL.path;

    // `fileSystemRepresentation` on an `NSString` gives a path suitable for POSIX APIs
    return path.fileSystemRepresentation;
}

const char * FolderManager::pathForDirectoryAppropriateForItemAtPath(SearchPathDirectory directory,
 SearchPathDomainMask domainMask, const char *itemPath, bool create) {

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *nsPath = [fileManager stringWithFileSystemRepresentation:itemPath length:strlen(itemPath)];
    NSURL *itemURL = (nsPath ? [NSURL fileURLWithPath:nsPath] : nil);

    NSURL *URL = [fileManager URLForDirectory:(NSSearchPathDirectory)directory
                       inDomain:domainMask
              appropriateForURL:itemURL
                         create:create error:NULL];
    return URL.path.fileSystemRepresentation;
}

Note that this should be compiled without Automatic Reference Counting enabled (or at least, that's how I was using it; it's possible that it will also work with ARC, but I haven't tested that). It's important to create and release an NSAutoreleasePool in the constructor and destructor, respectively, otherwise, you may get memory leaks and warnings in the Console to that effect (unless an NSAutoreleasePool has been created elsewhere for you)**.

To use it, something like this:

int main(int argc, const char * argv[]) {

    char *folderPath = NULL;
    FolderManager folderManager;
    folderPath = (char *)folderManager.pathForDirectory(NSApplicationSupportDirectory, NSUserDomainMask);
    printf("folderPath == %s\n", folderPath);

    folderPath = (char *)folderManager.pathForDirectory(NSApplicationSupportDirectory, NSLocalDomainMask);
    printf("folderPath == %s\n", folderPath);

    folderPath = (char *)folderManager.pathForDirectory(NSTrashDirectory, NSAllDomainsMask);
    printf("folderPath == %s\n", folderPath);

    if (argc > 1) {
        folderPath = (char *)folderManager.pathForDirectoryAppropriateForItemAtPath(NSTrashDirectory, NSAllDomainsMask, argv[1]);
        printf("folderPath == %s\n", folderPath);

        folderPath = (char *)folderManager.pathForDirectoryAppropriateForItemAtPath(NSItemReplacementDirectory, NSUserDomainMask, argv[1], true);
        printf("folderPath == %s\n", folderPath);
    }

    return 0;
}

When I run this on my machine, passing it an argument of /Volumes/Untitled3/folder/testFile.chm, it will output the following:

folderPath == /Users/mdouma46/Library/Application Support
folderPath == /Library/Application Support
folderPath == /Users/mdouma46/.Trash
folderPath == /Volumes/Untitled 3/.Trashes/501
folderPath == /Volumes/Untitled 3/.TemporaryItems/folders.501/TemporaryItems/(A Document Being Saved By findFolder)

**Regarding the memory management comment about the importance of an NSAutoreleasePool: I tested it here in OS X Yosemite, Xcode 7.2.1, by commenting out the creation of the NSAutoreleasePool, and for the life of me, I cannot get any errors logged to console, so I'm not sure what's going on.

What I was expecting was the result I got when I compiled it in Xcode 3.2.6 in OS X 10.6:

findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x10010c6d0 of class NSCFArray autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x10010c820 of class NSCFString autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111540 of class NSCFString autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111710 of class NSPathStore2 autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111770 of class NSPathStore2 autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111a20 of class NSCFArray autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111a60 of class NSCFArray autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111d10 of class NSCFString autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111ac0 of class NSURL autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111c20 of class NSCFArray autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111de0 of class NSCFString autoreleased with no pool in place - just leaking
findFolder[300] *** __NSAutoreleaseNoPool(): Object 0x100111cb0 of class NSConcreteData autoreleased with no pool in place - just leaking
folderPath == /Users/mdouma46/Library/Application Support

This happens because many of the Objective-C calls return autoreleased objects: NSFileManager's URLsForDirectory:inDomain: returns an autoreleased array of NSURLs. Calling NSURL's path method returns (creates) autoreleased NSStrings (actually, the NSPathStore2 private subclass), etc...

like image 192
NSGod Avatar answered Oct 17 '22 04:10

NSGod