Recent

Author Topic: How to port .dylib for dynamic linking  (Read 3852 times)

CCRDude

  • Hero Member
  • *****
  • Posts: 502
How to port .dylib for dynamic linking
« on: November 13, 2018, 08:47:29 am »
I tried both Google and a search here, but results were either not fitting or not working.

I'm using FPC + Lazarus trunk snapshots to develop. I cross-compile from Windows to Mac.

The project I'm working on consists of an executable and a dynamically linked library. Functions are exported stdcall, and dynamically imported during runtime using LoadLibrary and GetProcAddress.

On Windows (.dll) and Linux (.so), everything works fine. On Mac OS (High Sierra), LoadLibrary works (receives a handle), but GetProcAddress fails (returns nil).

I read about the recommendation to prefix the export with an underscore, but that did not help (seems to have been for static linking?).

nm -m -U libsomething.dylib returns:
Code: [Select]
000000000003fd0 (__TEXT,__text) external MyFunctionName
...
0000000000003f80 (__TEXT,__text) external _MyFunctionName

Any ideas what I can do to make the import successful?

Thaddy

  • Hero Member
  • *****
  • Posts: 9193
Re: How to port .dylib for dynamic linking
« Reply #1 on: November 13, 2018, 08:55:39 am »
Quote
Functions are exported stdcall,
On Mac and Linux the calling convention is most likely if not always cdecl. stdcall is mostly Win32 specific.(So not so standard as the name suggests  :o )

A way to solve this is like so:
Code: Pascal  [Select]
  1. // add on top of your import unit and *remove* all specific calling specifiers from the individual the imported methods
  2. {$ifdef mswindows}{$ifdef wince}{$calling cdecl}{$else}{$calling stdcall}{$endif}{$elsif unix}{$calling cdecl}{$else}{$calling default}{$endif}
This is not complete but will cover the windows versions and unixes. (for win64 stdcall is actually ignored, wince is cdecl)

See https://www.freepascal.org/docs-html/current/prog/progsu7.html#x14-130001.2.7 and
https://www.freepascal.org/docs-html/current/prog/progse22.html#x173-1760006.3
« Last Edit: November 13, 2018, 10:05:44 am by Thaddy »
also related to equus asinus.

CCRDude

  • Hero Member
  • *****
  • Posts: 502
Re: How to port .dylib for dynamic linking
« Reply #2 on: November 13, 2018, 09:53:33 am »
But both library and executable are from my source, so I know I've specified "stdcall" in both (should not be that important on the executable side, since GetProcAddress should(?) not be affected? ;)

Anyway, updated both library and executable to use cdecl (and learned more about using macros to still have stdcall on Windows), same result, GetProcAddress returns nil.

Thaddy

  • Hero Member
  • *****
  • Posts: 9193
Re: How to port .dylib for dynamic linking
« Reply #3 on: November 13, 2018, 10:15:52 am »
You mean this (or something like this) macro?
Code: Pascal  [Select]
  1. {$macro on}{$if not defined(mswindows} {$define stdcall:=cdecl}{$endif}
That is also possible but less complete. It is also a bit confusing if others use your code: all is not what it seems, you hide essential information.
Note in general it is always better to use the calling convention for the platform for any library including your own.
« Last Edit: November 13, 2018, 10:17:58 am by Thaddy »
also related to equus asinus.

GetMem

  • Hero Member
  • *****
  • Posts: 3510
Re: How to port .dylib for dynamic linking
« Reply #4 on: November 13, 2018, 10:18:55 am »
Quote
Anyway, updated both library and executable to use cdecl (and learned more about using macros to still have stdcall on Windows), same result, GetProcAddress returns nil.
Right after GetProcAddress fails:
Code: Pascal  [Select]
  1. ShowMessage(SysErrorMessage(GetLastOSError));
It should work on OSX too. The obvious quess is that you don't use the full path to the library. Is the dynlib inside the bundle?

PS: Can you attach a demo application with a demo library which fails?

CCRDude

  • Hero Member
  • *****
  • Posts: 502
Re: How to port .dylib for dynamic linking
« Reply #5 on: November 13, 2018, 10:50:11 am »
SysErrorMessage(GetLastOSError) returns Success  %)

Yes, the dynlib is within the bundle, I use ExtractFilePath(Application.ExeName) + 'libmyapp.dylib' to access it, my exceptions show that this is the path used, the name is (different from libmyapp.dylib) quite unique, and if the library would not be found, LoadLibrary probably wouldn't return a handle.

Creating a miniature demo, I receive this error message:
Code: [Select]
This binary has no dynamic library support compiled in.
Recompile the application with a dynamic-library-driver in the program uses clause before other units using dynamic libraries.

Which unit do I need to include to be able to use dylibs on console on MacOS?
(edit: I added the Cocoa widgetset and LCL and LCLBase as requirements, and Interfaces and LCLIntf to the uses part, now the test program works, but still returns the same error. Will create an example dylib as well now)

@Thaddy: What I meant is this:
Code: [Select]
{$MACRO ON}
{$IF DEFINED(MSWindows)}
{$DEFINE AppNameConvention := stdcall}
{$ELSEIF DEFINED(Darwin)}
{$DEFINE AppNameConvention := cdecl}
{$IFEND}
...
type
   TFunc = procedure(); AppNameConvention;

I didn't even get the idea to "overwrite" the name of a calling convention due to the possible irritation you refer to.
« Last Edit: November 13, 2018, 10:58:42 am by CCRDude »

laguna

  • Sr. Member
  • ****
  • Posts: 274
Re: How to port .dylib for dynamic linking
« Reply #6 on: November 13, 2018, 11:16:25 am »
I use this structure
   
Code: Pascal  [Select]
  1. ordir := ExtractFilePath(ParamStr(0));
  2.   {$IFDEF Darwin}
  3.     {$IFDEF CPU64}
  4.         opath := ordir;
  5.         opath := copy(opath, 1, Pos('/Contents', opath) - 1);
  6.         lib1 := opath + '/Contents/Frameworks/64bit/LibPortaudio-64.dylib';
  7.         lib2 := opath + '/Contents/Frameworks/64bit/LibSndFile-64.dylib';
  8.         lib3 := opath + '/Contents/Frameworks/64bit/LibMpg123-64.dylib';
  9.     {$ENDIF}
  10.  
  11.     {$IFDEF CPU32}
  12.         opath := ordir;
  13.         opath := copy(opath, 1, Pos('/Contents', opath) - 1);
  14.         lib1 := opath + '/Contents/Frameworks/32bit/LibPortaudio-32.dylib';
  15.         lib2 := opath + '/Contents/Frameworks/32bit/LibSndFile-32.dylib';
  16.         lib3 := opath + '/Contents/Frameworks/32bit/LibMpg123-32.dylib';
  17.     {$ENDIF}
  18.   {$ENDIF}
  19.  

CCRDude

  • Hero Member
  • *****
  • Posts: 502
Re: How to port .dylib for dynamic linking
« Reply #7 on: November 13, 2018, 11:45:29 am »
@laguna: thank you! I have different paths for 32/64 bit on Windows, haven't implemented such a thing on MacOS yet. Right now I'm testing x86_64 only... should I decide to support 32 bit as well, I'll take a look at your example, though I would use the Cocoa functions to determine the framework folder.

I was switching from ParamStr(0) to Application.ExeName because I thought the former had failed me at some point; might need to reevaluate that.

@GetMem: here's a most simple demo:
https://github.com/CCRDude/laz-darwin-dylib-test

Code: [Select]
[MyMacName] ~ $/Volumes/Exchange/mactest/DarwinDynlibTestExecutable
[i] /Volumes/Exchange/mactest/libdarwindynlibtestlibrary.dylib
[+] LoadLibrary()
[-] GetProcAddress(FourtyTwo); GetLastOSError = Success

On Windows, it works fine.

Thaddy

  • Hero Member
  • *****
  • Posts: 9193
Re: How to port .dylib for dynamic linking
« Reply #8 on: November 13, 2018, 11:55:14 am »
I was switching from ParamStr(0) to Application.ExeName because I thought the former had failed me at some point; might need to reevaluate that.
It will fail on any code that doesn't use an TApplication object. (That's why I mostly use Paramstr(0), I write a lot of console code w/o Tapplication object, either the one from rtl or the one from lcl)
« Last Edit: November 13, 2018, 11:57:42 am by Thaddy »
also related to equus asinus.

GetMem

  • Hero Member
  • *****
  • Posts: 3510
Re: How to port .dylib for dynamic linking
« Reply #9 on: November 13, 2018, 12:17:07 pm »
@CCRDude
Currently I have only access to an older OSX(10.8.2 - Mountain Lion), however your code works flawesly(Lazarus trunk/fpc 3.0.2). I modified your code a little bit to print out the result of the function and it works fine as in the attached images. Later at home, I will run a few tests with a more recent version. In my opinion either fpc trunk or the newer version of OSX is the culprit. 

GetMem

  • Hero Member
  • *****
  • Posts: 3510
Re: How to port .dylib for dynamic linking
« Reply #10 on: November 13, 2018, 05:58:56 pm »
It works on High sierra too, with Lazarus trunk/FPC 3.0.4.

PS: Only works with carbon, fails with cocoa. In my opinion you should file a bugreport and attach the same demo project.
« Last Edit: November 13, 2018, 06:16:16 pm by GetMem »

CCRDude

  • Hero Member
  • *****
  • Posts: 502
Re: How to port .dylib for dynamic linking
« Reply #11 on: November 13, 2018, 09:15:49 pm »
Thank you! I had meanwhile set up a Lazarus environment on the Mac to test without cross-compiling and even tried older FPC vresions; thanks for finding the Carbon vs. Cocoa difference! Reported on the bug tracker:
https://bugs.freepascal.org/view.php?id=34550

CCRDude

  • Hero Member
  • *****
  • Posts: 502
Re: How to port .dylib for dynamic linking
« Reply #12 on: November 18, 2018, 05:33:49 pm »
Not a bug, but a case of bad use. The solution was posted to the bugtracker, I'll quote it here in case the search leads anyone here:

LoadLibrary return value needs to be stored in TLibHandle, not THandle.

Verified inside the test code, and my own app, that this solves the issue.