Jump to content
Sign in to follow this  
Mike Torrettinni

How should I organize cross platform code?

Recommended Posts

I see 2 options how to organize code for cross platform deployment:

- units stay as the are now (based on content: utilities, support, data, visual...) and use IFDEF in methods all over the code as necessary

   Delphi uses this approach in their units, but it can get a bit tedious browsing through code with all the IFDEF's

- split units based on platform, so we end up with Utilities -> WinUtilities, LinuxUtilities and so on... and use IFDEF in uses to use appropriate unit

   This seems good, since you can end up with WinUtilities, WinExports, WinVisual, LinuxUtilities, LinuxBGService....

 

Any recommendations on which approach to use?

 

Share this post


Link to post

Ugh, the code you write shouldn't need those ifdefs. That the entire point of you using the libraries provided by others, like Emba. They present a common interface to you. That's the entire point of cross platform coding. 

  • Like 1

Share this post


Link to post
39 minutes ago, David Heffernan said:

Ugh, the code you write shouldn't need those ifdefs. That the entire point of you using the libraries provided by others, like Emba. They present a common interface to you. That's the entire point of cross platform coding. 

But in the reality it is sometimes like this (taken from Emba example Mobile Snippets\ShareSheet) :

 

uses
{$IFDEF ANDROID}
  Androidapi.Helpers,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.Os,
{$ENDIF}
  FMX.DialogService;

{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}

procedure TShareSheetForm.FormCreate(Sender: TObject);
begin
{$IFDEF ANDROID}
  FPermissionCamera := JStringToString(TJManifest_permission.JavaClass.CAMERA);
  FPermissionReadExternalStorage := JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE);
  FPermissionWriteExternalStorage := JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
{$ENDIF}
end;

 

Share this post


Link to post
59 minutes ago, Vandrovnik said:

But in the reality it is sometimes like this (taken from Emba example Mobile Snippets\ShareSheet) :

Samples are frequently just showing some features. They don't have to be examples of perfect design.

1 hour ago, David Heffernan said:

Ugh, the code you write shouldn't need those ifdefs.

+1 but I'd add "if possible". Surely one should estimate complexity of common interfaces and IFDEFed fragments. F.ex., IFDEFs in uses clause are hardly avoidable. But minimizing platform-specific IFDEFs (and other IFDEFS as well) should be the priority. Luckily with inlining wrapping platform-specific code into a common routine even won't cause a performance hit. Anyway separating platform-dependent and platform-independent code is nice and useful exercise. F.ex., one could discover that full file paths may contain no drive letters, or libs may have extensions other than "DLL", or that handy PostThreadMessage function is missing xD

Edited by Fr0sT.Brutal
  • Like 1
  • Thanks 1

Share this post


Link to post

I try to separate all code into platform separated untits meanwhile, even if this can get more work.

But the IFDEFs tend to get messy very soon, even in small units.

Even if the units are getting very small and simple, I found for myself that its very worth it to get a clean structure.

 

unit MyUnit

type
  TMyBaseOrAbstractClass = class or better an interface
  end;

uses
{$IF     DEFINED( ANDROID )}
  MyUnit.Impl.Android,
{$ELSEIF DEFINED( IOS )}
  MyUnit.Impl.iOS,
{$ELSEIF DEFINED( Macos )}
  MyUnit.Impl.Macos,
{$ELSEIF DEFINED( MSWINDOWS )}
  MyUnit.Impl.Win,
{$ELSEIF DEFINED( Linux )}
  MyUnit.Impl.Linux,
{$ELSE}
{$MESSAGE ERROR 'MyUnit: unkown platform' }  
{$ENDIF}
  FMX.DialogService;

...


unit MyUnit.Android

uses
  MyUnit;

type
  TMyBaseOrAbstractClass_Impl = class( TMyBaseOrAbstractClass )
  end;

 

Edited by Rollo62
  • Like 1
  • Thanks 1

Share this post


Link to post
1 hour ago, Rollo62 said:

Even if the units are getting very small and simple, I found for myself that its very worth it to get a clean structure.

I'd start with all platforms in a single unit but split it as you do if it grows larger

Edited by Fr0sT.Brutal
  • Thanks 1

Share this post


Link to post
6 hours ago, David Heffernan said:

That the entire point of you using the libraries provided by others, like Emba. They present a common interface to you. That's the entire point of cross platform coding. 

Makes sense, I guess I should get rid of old utility units, that were needed in old Delphi and might not be anymore.

 

2 hours ago, Fr0sT.Brutal said:
3 hours ago, Rollo62 said:

Even if the units are getting very small and simple, I found for myself that its very worth it to get a clean structure.

I'd start with all platforms in a single unit but split it as you do if it grows larger

This fits into my projects, or a combination, so makes sense to start slow and split if needed. Some units I know will have to be Windows only, so I will split the from the start.

Share this post


Link to post

@Fr0sT.Brutal

Yes, did that before.

Tends to get very ugly and messy with ios and Android soon.

Thats why I changed my method. 

 

 

  • Like 1

Share this post


Link to post

You can use unit scopes for this as partially done in RTL.

Place things which need different implementation for different platforms in separate units per implementation.

E.g. Windows.Utils, Linux.Utils etc.

Or even not bound to a platform but API:

OpenGL.Render, DirectX11.Render etc.

 

In uses clause of other units just write Utils or Render.

And setup needed unit scope names in project settings - Windows;OpenGL.

This approach with some architectural effort will minimize number of IFDEFs in code.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×