I've been using the windows API call ShellExec for years to pass in the file name of a file I want to launch programatically with the default application on Windows; but I am now creating an application for use on Windows and Mac with FireMonkey for the upcoming season of Developer Direct LIVE Mobile Summer School (starting Tuesday 16th July) and I need to implement the same functionality on Mac as well.
My first stop was to check IOUtils to see if this was implemented there, which it isn't in XE4.
When working with multiple platforms, you will find they often implement the same functionality in different ways. Take graphic handling for example where Windows uses Direct X and Mac OpenGL by default, these have co-ordinate While the FireMonkey framework takes great care in protecting our code from this, we will sometimes bump into a specific need that means we have to look at how we create our code to be cross platform, and this is a case of that.
Before I go any further, the code I have created is available for download from Code Central http://cc.embarcadero.com/item/29495
I set of with the aim of having reusable code for Windows and Mac that was readable and specific to what I was doing. As this was a specific type of function I wanted to create, I decided to write a class method that contained the code required. A class method enables you to call an class methods without creating an instance of the class. Class methods are simply defined with the class name before the method declaration (as below)
type
TFileLauncher = class
class procedure Open(const FilePath: string);
end;
With the stub created, and Ctrl+Shift+C for code completion pressed, I was now ready to complete the code in the procedure Open. I first added in the ShellExec API calls I have been using for years and then went around adding in a Mac alternative. This however leaves me with calls to multiple platforms specific code units! Not to worry, this is the specific reason we have IFDEF.
Using IFDefs
IFDEF's allow you to specifically have code included / excluded based upon specific criteria. E.g. When you compile your code in DEBUG build, {$IFDEF DEBUG} will be included. When you compile as release code, it will not.
IFDEF's work with a start {$IFDEF} or {$IFNDEF} (if not def) and end {$ENDIF} in between there is also an {$ELSE} command. For example when Debug is on the following code will show 'We are in DebugMode', and when you choose Release from the build configurations in your project group you will get Not in 'DebugMode' message.
procedure ShowMode;
begin
{$IFDEF DEBUG}
ShowMessage('We are in DebugMode');
{$ELSE}
ShowMessage('Not in DebugMode');
{$ENDIF}
end
There are multiple compiler directives that you can use in Delphi and the code created in this example in the end uses platform specific directives to only include the code required for the platform being compiling for (example below)
unit FileLauncher;
interface
uses
{$IFDEF MSWINDOWS}
Winapi.ShellAPI, Winapi.Windows;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
Posix.Stdlib;
{$ENDIF MACOS}
type
TFileLauncher = class
class procedure Open(FilePath: string);
end
implementation
class procedure TFileLauncher.Open(const FilePath: string);
begin
{$IFDEF MSWINDOWS}
ShellExecute(0, 'OPEN', PChar(FilePath), '', '', SW_SHOWNORMAL);
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
_system(PAnsiChar('open '+'"'+AnsiString(FilePath)+'"'));
{$ENDIF MACOS}
end
end.
The end result is my code now reads TFileLauncher.Open('SomeFileName.txt');
and works on Windows and Mac with a little help from a class method and an IFDEF