Cross-platform Special Folders in FireMonkey

There was a question on the ADUG list last week about how to retrieve “special folder” locations on OS X. By special folder, I mean locations like the user’s Home directory, the Documents directory, Temp directory, etc. I thought I’d write up the solution both because it’s probably something that more people will be wondering and also because it’s a nice little introduction to calling out to the OS X API.

If you want either the path to the Home or the Temp directory, this is ridiculously easy. The IOUtils unit already contains TPath.GetTempPath and TPath.GetHomePath, and these work on both Windows and OS X.

However, if you want another directory, such as the Documents directory, you need to do a little more work.

I’m sure many of you have done this same thing on Windows, using the Windows API SHGetFolderPath, like this:

  function GetDocumentDirectory : string;
  var
    szBuffer: array [0..MAX_PATH] of Char;
  begin
    OleCheck (SHGetFolderPath ( FmxHandleToHWND(Handle),
                               CSIDL_MYDOCUMENTS,
                               0,
                               0,
                               szBuffer));
    Result := szBuffer;
  end;

As an aside, the first parameter to the SHGetFolderPath is a HWND. In VCL, you could just pass in the Handle of the form, however in FireMonkey the Window handle is a TFmxHandle, not a HWND. The Fmx.Platform.Win unit contains a FmxHandleToHWND function that, as the name suggests, will take your FireMOnkey handle and give you back a HWND.

So, back to the point, if this is how we do it in FireMonkey on Windows, how do we do it on OS X? Let’s have a look:

  function GetDocumentDirectory : string;
  var
    FileMgr : NSFileManager;
    URL : NSURL;
    Error : NSError;
  begin
    FileMgr := TNSFileManager.Create;
    URL := FileMgr.URLForDirectory(NSDocumentDirectory,
                                   NSUserDomainMask,
                                   nil,
                                   false,
                                   Error);
    if Assigned(Error) then
      raise Exception.Create(Error.localizedDescription.UTF8String);

    Result := URL.path.UTF8String;
  end;

First thing we do is get a reference to a NSFileManager interface. It, and the TNSFileManager class that implements it, can be found in the Macapi.Foundation unit.

Once we have our NSFileManager reference, we can use the URLForDirectory method, which is roughly equivalent to the SHGetFolderPath call in the Windows example. We need to pass in a NSError variable and then check it to make sure everything worked OK. We raise an exception if not.

However, what we get back from URLForDirectory is not a string but a NSURL instance. In order to get a string containing the path, we use, not surprisingly, NSURL.Path. However, this also is not a String, it’s a NSString. How do we convert that to a string? Well, NSString.UTF8String is a quick, easy way to get back a UTF8 encoded string. If you want a different encoding, check out some of the other extraction methods on NSString. Update : Chris Rolliston has written a nice follow-up post digging into converting NSStrings to Delphi Strings in much more detail.

In the example project, once I have the full path of the Documents directory, I use the IOUtils classes to enumerate over the folders and files in that folder and add the names to a listbox:

var
  DocumentsPath, Filename : String;
begin
  DocumentsPath := GetDocumentDirectory;
  Label1.Text := DocumentsPath;


  for Filename in TDirectory.GetDirectories(DocumentsPath) do
    Listbox1.Items.Add(Format('Folder : %s', [Filename]));

  for Filename in TDirectory.GetFiles(DocumentsPath) do
    Listbox1.Items.Add(Format('File : %s', [Filename]));

end;

Here’s the resulting app on Windows:

Screen shot 2011 09 22 at 10 05 32 PM

and on OSX:

Screen shot 2011 09 22 at 10 07 25 PM

You can download the sample project from my delphi-samples repository on github.

4 Comments

  • Good post Malcolm but I think the second part of the code might leak memory in its current state as you might have to FREE the FileMgr (NSFileManager)

    • Hi Sunil,

      Thanks, glad you liked it.

      Regarding the memory leak, no you don’t have to free it. NSFileManager is an interface, so while I am creating a TNSFileManager, I’m storing a reference to its interface, which is reference counted and will destroy the TNSFileManager for me when I let the reference go.

      Cheers
      Malcolm

      • Thanks for the clarification Malcolm

        And yes if it is an interface and reference counted then no chance of leaks here.

  • thanx for this information , but i have question >> How can i get the path of the app in OSX?
    i tried :
    getdir,getcurrentdir
    it works in windows but in OSX it doesn’t work , just returens “/”.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>