FireMonkey forms and DLLs/dylibs

Well I’ll start with what actually worked – a FireMonkey form being shown from an Excel spreadsheet on the Mac (yes, the Mac — Windows refugees unite!):

Bizarre as it may sound, this combination was actually quite easy to get working, easier (it turned out) than Delphi to Delphi setups. Here’s the VBA code behind the button:

Option Explicit

#If Mac Then
Declare Function ChooseDate CDecl Lib "libChooseDateDlg.dylib" (ADate As Date) As Boolean
Declare Function ChooseDate Lib "ChooseDateDlg.dll" (ADate As Date) As Boolean
#End If

Sub ShowDlgButtonClick()
  ' Next line means putting the DLL in the same directory as the XLS works... on Windows.
  ' OS X is more strict - you have to put it somewhere like ~/lib
  ChDir ActiveWorkbook.Path
  ' Get the date currently contained in the cell.
  Dim DateCell As Range, DateValue As Date
  Set DateCell = ActiveSheet.Range("DateCell")
  If IsDate(DateCell.Value) Then DateValue = DateCell.Value Else DateValue = Date
  ' Pass the date to the library function, then update the cell if the user didn't cancel.
  If ChooseDate(DateValue) Then DateCell.Value = DateValue
End Sub

Simple, eh? On the Delphi side, the exported function uses the stdcall calling convention when targetting Windows, and cdecl (plus an underscore prefix!) when targetting OS X (why an underscore prefix? Because of a silly ‘feature’ of dlsym [= the POSIX GetProcAddress] on OS X – check out Eli Boling’s blog for details):

library ChooseDateDlg;

  ChooseDateForm in 'ChooseDateForm.pas' {frmChooseDate};

function ChooseDate(var ADate: TDate): WordBool;
  {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};

  ChooseDate {$IFDEF MACOS} name '_ChooseDate'{$ENDIF};

On Windows, the resulting DLL is completely self-contained, as you would expect; on the Mac, there’s just the small (20K) helper dylib for Delphi’s exception support that any FireMonkey executable or library requires. Put it alongside your own dylib, and you’re away.

My attempts to follow this up with Delphi clients for the same library weren’t as successful however. In the case of a VCL client, the issue there is FireMonkey’s use of GDI+, or more exactly, GDI+’s requirement that you call ‘startup’ and ‘shutdown’ functions to initialise and finalise it. Since this is unsafe to do in DLLMain, FireMonkey (or more exactly, the Winapi.GDIPOBJ unit) will only do so automatically when not compiled in a DLL. In principle, you should be able to call them in a DLL nonetheless before and after showing a form – in my case, at the head of ChooseDate and at its end. However, the FireMonkey units keep some GDI+ resources hanging around in global objects. Call the GDI+ shutdown procedure before releasing them, and GDI+ will helpfully crash the host application when your FireMonkey DLL is unloaded. Nice!

So, the host application must initialise and finalise GDI+. While annoying, this is nevertheless easy in a VCL application – just add the Winapi.GDIPOBJ unit to a uses clause somewhere. However, the next problem quickly arises: try to use an explicit import declaration of your DLL routine, and GDI+ is still liable to crash the application at a later point. Why? Because you haven’t guaranteed that the GDI+ DLL will be the unloaded after your FireMonkey one. The safest thing, in short, is just to dump the idea of an explicit import and use a LoadLibrary/GetProcAddress/FreeLibrary cycle instead.

After all that, at least it works though. This is more than can be said about using a FireMonkey dylib from a FireMonkey executable on the Mac, since it appears the two TApplication objects involved (one in the dylib, one in the executable) conflict – I get an exception from the dylib on load saying that the TApplicationDelegate class can’t be registered. Shades of Delphi.Net? Perhaps, but looking at the code, I would guess the delegate in question isn’t actually needed when the unit is being compiled in a dylib, so maybe there’s hope…


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s