Search code examples
delphifiremonkeydelphi-10.2-tokyomacos-high-sierra

Delphi FMX OSX Segmentation Fault 11


There are a number of Segmentation Fault 11 posts, but none seem to answer my question:

I've posted the code for the simple test application previously, but is provided for completeness. Windows 10, Delphi 10.2.3 with OSX High Sierra 10.13.6 and Xcode 9.4.1.

The HOST:

unit uDylibTest1;

interface

uses
  System.SysUtils,
  System.Types,
  System.Diagnostics,
  System.UITypes,
  System.Classes,
  System.Variants,

  FMX.Types,
  FMX.Controls,
  FMX.Forms,
  FMX.Graphics,
  FMX.Dialogs,
  FMX.StdCtrls,
  FMX.Platform,
  FMX.Controls.Presentation;


const
  // Windows DLL Names
  {$IFDEF MSWINDOWS}
  TestDLL = 'pTestDLL.dll';
  {$ENDIF MSWINDOWS}

  // macOS DYLIB Names
  {$IFDEF MACOS}
  //TestDLL = 'libpTestDLL.dylib';
    TestDLL = 'libpTestDLL.dylib';
  {$ENDIF MACOS}

type
  TfDylibTest = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TuDylibTest = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

{$IFDEF MSWINDOWS}
function say_Hello(Hello: string; out ReturnString: string): boolean; stdcall; external TestDLL Delayed;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string; out ReturnString: string): boolean; cdecl; external TestDLL;
{$ENDIF MACOS}

function local_say_hello(Hello: string): boolean; forward;

var
  fDylibTest: TfDylibTest;

implementation

function local_say_hello(Hello: string): boolean;
begin
  Result := True;
  showmessage(Hello);
end;

{$R *.fmx}
{$R *.Macintosh.fmx MACOS}
{$R *.Windows.fmx MSWINDOWS}

procedure TfDylibTest.Button1Click(Sender: TObject);
var
 b:boolean;
 sType: string;
 sDLLString: string;

begin
  b := False;
  sType := '';
  // Call the DLL Function
  {$IFDEF MSWINDOWS}
  sDLLString := 'The string passed to the Windows DLL';
  b := say_Hello(sDLLString, sType);
  {$ENDIF MSWINDOWS}
  {$IFDEF MACOS}
  sDLLString := 'The string passed to the macOS DYLIB';
  b :=  _say_Hello(sDLLString, sType);
  {$ENDIF MACOS}

  if b then
    showmessage('Returned From: ' + sType + ': TRUE')
  else
    showmessage('Retur was: FALSE');
end;

procedure TfDylibTest.Button2Click(Sender: TObject);
var
  b: boolean;
 iTime2Auth: integer;
 Opened: boolean;
 i: integer;

begin
  Opened := False;

  // test the Waitnofreeze
  iTime2Auth := 0;

  b := False;
  // Call the local Function
  b := local_say_Hello('The string passed to the LOCAL function');

  if b then
    showmessage('Say Hello OK - LOCAL')
  else
    showmessage('Say Hello Failed - LOCAL');
end;

procedure TfDylibTest.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  showmessage('[Test App] onClose');
end;

procedure TfDylibTest.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  canClose := True;
  showmessage('[Test App] onCloseQuery');
end;

procedure TfDylibTest.FormCreate(Sender: TObject);
begin
  showmessage('[Test App] onCreate');
end;

initialization
  showmessage('[Test App] Initialization');  <<<<-- THIS IS EXECUTED

finalization
  showmessage('[Test App] Finalization');    <<<<-- THIS IS NOT EXECUTED

end.

And the DYLIB:

unit uTestDLL;

interface

uses
  System.Diagnostics,
  System.Classes;

// External functions and procedures
{$IFDEF MSWINDOWS}
function say_Hello(Hello: string; out ReturnString: string): boolean; stdcall; forward;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string; out ReturnString: string): boolean; cdecl; forward;
{$ENDIF MACOS}

exports
  {$IFDEF MSWINDOWS}
  say_Hello;
  {$ENDIF MSWINDOWS}
  {$IFDEF MACOS}
  _say_Hello;
  {$ENDIF MACOS}

Implementation

{$IFDEF MSWINDOWS}
function say_Hello(Hello: string; out ReturnString: string): boolean; stdcall; export;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string; out ReturnString: string): boolean; cdecl; export;
{$ENDIF MACOS}
begin
  {$IFDEF MSWINDOWS}
  ReturnString := 'Windows DLL, Received: ' + Hello;
  {$ENDIF MSWINDOWS}
  {$IFDEF MACOS}
  ReturnString := 'macOS DYLIB, Received: ' + Hello;
  {$ENDIF MACOS}
  Result := True;
end;

end.

Basically a function in a DYLIB is passed a string parameter, it returns TRUE with an OUT variable of another string which proves that the data went into the DYLIB function and returned out.

I have simple showmessage() statements in the calling application for things like onCloseQuery and onClose etc, these all show, everything WORKS as it should until the application closes(onCloseQuery executed, onClose executed, and about half a second to a second after everything shuts down the problem report pops up). Note that the Initialisation Section IS executed, but the Finalization Section is NOT executed (although it is executed when running in Win32 mode). I've put the google drive link to the error log. OSX Problem Report

When I look at the stack trace, there are none of my two files (DylibTest and libpTesDLL.DYLIB) mentioned, it's all RTL/system units. I've googled Seg Fault 11 and searched the forum, but nothing seems related.

I'd appreciate any advice with how I could proceed?


Solution

  • string is NOT a portable data type for interop across library boundaries. Use PChar instead, and then you need to decide WHO allocates and frees memory for strings - the library or the calling app - and HOW they are allocated and freed, and do so in an interop compatible manner. You are not passing strings across the library boundary in a safe manner, so it is no wonder that you are crashing your code.

    Try something more like this instead:

    HOST:

    // Windows DLL
    {$IFDEF MSWINDOWS}
    const
      TestDLL = 'pTestDLL.dll';
    
    function say_Hello(Hello: PChar; out ReturnString: PChar): boolean; stdcall; external TestDLL Delayed;
    procedure free_String(Mem: PChar); stdcall; external TestDLL Delayed;
    {$ENDIF}
    
    // macOS DYLIB
    {$IFDEF MACOS}
    const
      TestDLL = 'libpTestDLL.dylib';
    
    function say_Hello(Hello: PChar; out ReturnString: PChar): boolean; cdecl; external TestDLL name '_say_Hello';
    function free_String(Mem: PChar); cdecl; external TestDLL name '_free_String';
    {$ENDIF}
    
    ...
    
    procedure TfDylibTest.Button1Click(Sender: TObject);
    var
     b: boolean;
     sType: PChar;
     sDLLString: string;
    begin
      b := False;
      sType := nil;
    
      // Call the DLL Function
      {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
      sDLLString := 'The string passed to the ' + {$IFDEF MSWINDOWS}'Windows DLL'{$ELSE}'macOS DYLIB'{$ENDIF};
      b := say_Hello(PChar(sDLLString), sType);
      if b then
      begin
        try
          ShowMessage('Returned From: ' + string(sType) + ': TRUE');
        finally
          free_String(sType);
        end;        
        Exit;
      end;
      {$IFEND}
    
      ShowMessage('Retur was: FALSE');
    end;
    

    DYLIB:

    unit uTestDLL;
    
    interface
    
    {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
    
    // External functions and procedures
    function say_Hello(Hello: PChar; out ReturnString: PChar): boolean; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; forward;
    procedure free_String(S: PChar); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; forward;
    
    exports
      say_Hello{$IFNDEF MSWINDOWS} name '_say_Hello'{$ENDIF},
      free_String{$IFNDEF MSWINDOWS} name '_free_String'{$ENDIF};
    
    {$IFEND}
    
    implementation
    
    uses
      System.Diagnostics,
      System.Classes;
    
    {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
    
    function say_Hello(Hello: PChar; out ReturnString: PChar): boolean; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; export;
    var
      s: string;
    begin
      try
        s := 'FROM: ' + {$IFDEF MSWINDOWS}'Windows DLL'{$ELSE}'macOS DYLIB'{$ENDIF} + ', Received: ' + string(Hello);
        ReturnString := StrAlloc(Length(s));
        try
          StrPCopy(ReturnString, s);
        except
          StrDispose(ReturnString);
          throw;
        end;
        Result := True;
      except
        Result := False;
      end;
    end;
    
    procedure free_String(S: PChar); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; export;
    begin
      StrDispose(S);
    end;
    
    {$IFEND}
    
    end.
    

    Alternatively:

    HOST:

    // Windows DLL
    {$IFDEF MSWINDOWS}
    const
      TestDLL = 'pTestDLL.dll';
    
    function say_Hello(Hello: PChar): PChar; stdcall; external TestDLL Delayed;
    procedure free_String(Mem: PChar); stdcall; external TestDLL Delayed;
    {$ENDIF}
    
    // macOS DYLIB
    {$IFDEF MACOS}
    const
      TestDLL = 'libpTestDLL.dylib';
    
    function say_Hello(Hello: PChar): PChar; cdecl; external TestDLL name '_say_Hello';
    function free_String(Mem: PChar); cdecl; external TestDLL name '_free_String';
    {$ENDIF}
    
    ...
    
    procedure TfDylibTest.Button1Click(Sender: TObject);
    var
     sType: PChar;
     sDLLString: string;
    begin
      sType := nil;
    
      // Call the DLL Function
      {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
      sDLLString := 'The string passed to the ' + {$IFDEF MSWINDOWS}'Windows DLL'{$ELSE}'macOS DYLIB'{$ENDIF};
      sType := say_Hello(PChar(sDLLString));
      if sType <> nil then
      begin
        try
          ShowMessage('Returned From: ' + string(sType) + ': TRUE');
        finally
          free_String(sType);
        end;        
        Exit;
      end;
      {$IFEND}
    
      ShowMessage('Retur was: FALSE');
    end;
    

    DYLIB:

    unit uTestDLL;
    
    interface
    
    {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
    
    // External functions and procedures
    function say_Hello(Hello: PChar): PChar; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; forward;
    procedure free_String(S: PChar); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; forward;
    
    exports
      say_Hello{$IFNDEF MSWINDOWS} name '_say_Hello'{$ENDIF},
      free_String{$IFNDEF MSWINDOWS} name '_free_String'{$ENDIF};
    
    {$IFEND}
    
    implementation
    
    uses
      System.Diagnostics,
      System.Classes;
    
    {$IF DEFINED(MSWINDOWS) OR DEFINED(MACOS)}
    
    function say_Hello(Hello: PChar): PChar; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; export;
    var
      s: string;
    begin
      try
        s := 'FROM: ' + {$IFDEF MSWINDOWS}'Windows DLL'{$ELSE}'macOS DYLIB'{$ENDIF} + ', Received: ' + string(Hello);
        Result := StrAlloc(Length(s));
        try
          StrPCopy(Result, s);
        except
          StrDispose(Result);
          throw;
        end;
      except
        Result := nil;
      end;
    end;
    
    procedure free_String(S: PChar); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; export;
    begin
      StrDispose(S);
    end;
    
    {$IFEND}
    
    end.