Delphi VCL/FMX for Linux before the official platform support


There was a time when Borland approached Linux platform in a Delphi's "clone" called Kylix.
This product eventually reached its end of life after the last release of Kylix 3 on August 2002.
Currently in the era of Delphi XE with Firemonkey, we can finally target more platforms than Windows. However, Linux is not yet among those targets. We now can officially see the support for Linux server in the roadmap, but no mention about the Linux desktop development so far.
This article will show you that you can quite easily target Linux desktop users already now, not only with Firemonkey, but even with VCL, so from most recent Delphi XE versions all the way back to probably Delphi 3. 

This article will cover:
  • Wine - running Windows apps on Linux, BSD, Solaris and Mac without VM or emulator
  • Pre-Firemonkey cross platform software with Delphi VCL
  • Firemonkey applications tailored for Wine
  • Code snippets


Wine - running Windows apps on Linux, BSD, Solaris and Mac without VM or emulator

So, just by the way, the image at the top of this post is a screen captured on my Ubuntu powered laptop, with Delphi 7 running. On the same computer I have also installed and working Delphi 6 and even both Windows and Linux versions of Lazarus.
The reason why we can have Delphi installed and functioning inside a Linux Desktop is because of Wine compatibility layer, which is not at all related to virtualization/emulation.

"Wine (originally an acronym for "Wine Is Not an Emulator") is a compatibility layer capable of running Windows applications on several POSIX-compliant operating systems, such as Linux, Mac OSX, & BSD. Instead of simulating internal Windows logic like a virtual machine or emulator, Wine translates Windows API calls into POSIX calls on-the-fly, eliminating the performance and memory penalties of other methods and allowing you to cleanly integrate Windows applications into your desktop."

Please do learn more about it on the official website. For the purpose of this article I would like you to realize something about Wine - it is hugely popular and widely used by Linux desktop users. As long as you at least make effort for your software to run under Wine, the Linux community will already appreciate it a lot, even if you do not port your code natively to Linux.
So how do you ensure that your software runs well under Wine? Well, Delphi itself is a good example, and I mean the Delphi IDE. You see, I am able to run Delphi versions 3,6 and 7 on my Ubuntu without any problem, however it would not be possible for me to run any of the Delphi XE versions. In fact I would not even be able to install them. There can be few reasons for programs not to work well under Wine, but there is one major reason that should be on top of your attention: dependencies. You see, Wine provides a broad set of libraries that are default on Windows operating systems, as part of it, and they are ported to Linux by Wine makers. Windows programs commonly link to API in those libraries, even if you don't realize it. Every time you use WinAPI unit for example. But lot of the linking happens just under the hood of the VCL. So as long as your program only expects to find those most default libraries, you should be good to go. The problem will arise, when your program expects to find a library that is not among those most default ones, and one that is not shipped with Wine by default. Great news is, that Delphi application historically are very self-contained, in form of a single EXE file that does not depend on any 3rd party runtime or SDK. 
Common problem are applications like those depending on particular C++ Redistributable runtime versions, .NET SDK, etc. For those it is not enough to just have your EXE file, it will also expect you to have installed all of such prerequisites. We are back to Delphi IDE which already depends strongly on those in the XE editions (.NET).
The best way to deal with software that absolutely does need 3rd party libraries, is to ship them with your product and have them gathered in your programs install directory, which conceptually is similar to the new snap packaging for GNU/Linux. In snap, you treat your software as a container with all of its dependencies/prerequisites packaged and shipped in a self-contained form. No external dependencies.
As a Delphi developer you still benefit from lack of special dependencies in your Delphi compiled EXE files by default, even though the recent Delphi IDE itself does depend on this strongly. This means that by default you are already good to go, and if you grow your code to the point where it needs extra 3rd party libraries, just please contain all of this and distribute along with your program in its own installation directory. This way even without especially porting your code outside of Windows, lot of Linux, or ever Mac users will be happy to be able to use it. And this is basically what this post is about.

There is, however, a second aspect to using Windows applications under Wine. It is, that wine isolates the application it runs similarly to how containers work. Your Wine installation will create a directory tree somewhere under your home folder, and the top of that tree will be the root portion of the filesystem ever visible to a Windows apps. This isolates your all Windows application environment from your host system, and is also great for security reasons. You can even have multiple Wine installations under the same user in your Linux with different setting, like compatibility settings for different versions of Windows. 
A Windows application running under wine will not realize this, tricked into "believing" that it actually is being executed under a legit Windows operating system. Well, actually there is a way for your code to reach out of this isolation, and more about this is discussed in the next chapter. 


Pre-Firemonkey cross platform software with Delphi VCL

The great extra aspect of having Delphi directly under Linux Desktop is that you can develop cross platform for Windows, Linux, BSD and Mac without any virtualization or other emulation.
You simply run Delphi on Ubuntu, write your code, compile, and the EXE file that you produce is immediately ready to be shipped to Windows, Linux, BSD and Mac users.

The absolutely mind-blowing aspect of this is, that since we are not using any virtualization or emulation, the described method is actually a way to develop Windows applications natively on linux.

I do not pretend to know anything about how popular is Wine among BSD or Mac users, though I know as a fact that it is immensely popular among Linux Desktop users. Creating Windows applications tested to work under Wine is really way to go for existing on Linux. In fact there are lots of applications available on Ubuntu for a long time now, that you can install directly from officially supported DEB packages via dpkg or apt-get, that actually are Windows applications with partial and customized Wine compatibility runtime built-in. One great example includes the all popular TeamViewer, another is a popular Pinta image editor. I did not research this deeply, but trust me, Wine is well adapted in Linux world, and is totally a way to go if you want to support Linux but don't want to use native compilers (like Free Pascal with Lazarus).

Now, do you remember Kylix that I mentionned in the very beginning of this post? Well, it turns out that in fact it is basically the Delphi 5 IDE running on top of Wine, with a fast native code compiler, and tools for code navigation, auto-completion, parameter-name tooltips, and so on.
Some nice source on that (thanks +Daniela Osterhagen) here.

Now, about the versions of Delphi that do not run well under Wine, you can still use them natively on Windows to make programs that will work under Wine. But using Delphi IDE directly on Linux is great choice because you do not need to switch between operating systems. As long as you are fine with 32bit binaries, because all of earlier Delphi versions lack the support for 64 bit. And although this is not the main focus of this post, as a Delphi developer you can always turn to Lazarus, and again, you can have both the Linux and the Windows versions installed and working on your Linux desktop. 
*with the most recent Ubuntu compatibility layer in Windows 10 it may sooner rather than later become possible to also work the other way around, having both Linux and Windows applications installed and working under Windows. Not that I would prefer that way, but it might become interesting as an alternative.

The VCL applications by default will run great under Wine, and only aspect for special consideration from the top of my head is using database components. Sometimes you need to explicitly add certain units on the uses list of your Delphi project for related libraries to be statically linked into your application. One such case is with MIDAS. 

The special aspect of Delphi programming for Wine is when you want your application to be Wine-aware, which is to say, recognize that it is being ran under Wine and also access part of your host Linux OS that are outside of the Wine isolation. This is totally possible and I have been dealing with it for years now. So let me share this knowledge with you.

The trick to go out of the Wine isolation is to call external process, either by means of ShellExecute or TProcess or CreateProcess. As long as you specify a full path to the linux binary (absolute to the linux host filesystem) you will have Wine to properly execute it. Using ShellExecute is very convenient, but the other two methods mentioned will allow you to capture the output of your call.
Below is a screen captured of me running /bin/uname -a call from within a Windows native application:
Please notice that the call would not produce any output if I used uname -a without specifying the full absolute path. A short demo video from of my screen recorded:


You can find code snippets used in this demo at the end of this post in the last chapter.


Firemonkey applications tailored for Wine

The Firemonkey applications compiled for Windows by default work great under Wine as well. You can take all the advantage of Firemonkey custom drawn rendering, with all the whistles and blows, like animations, transparency alpha channels, etc.
One aspect is calling the external program like we did in the previous chapter. You could always use Windows API and things like CreateProcess or ShellExecute also in the Firemonkey, but your project would not be very cross platform any more, unless you add things like compiler conditional directives {$IFDEF MSWINDOWS} to your uses clause, so the project also compiles under non-windows platforms without linking to WinAPI.

I will make a video especially to demonstrate Fremonkey code under Delphi XE10.1 Berlin tailored for running under Wine on Ubuntu Linux and share it right here when ready (this coming or next weekend), but I can tell you that at my company I have been developing a Firemonkey app for past year and it works great under Wine too, so I  assure you that this is a green light even before I demonstrate this on the video.



Code snippets

Used in the demo video routine CaptureConsoleOutput(const ACommand, AParameters: String; AMemo: TMemo) actually comes from delphi.wikia.com article and here it the code:

procedure CaptureConsoleOutput(const ACommand, AParameters: String; AMemo: TMemo);

 const
   CReadBuffer = 2400;
 var
   saSecurity: TSecurityAttributes;
   hRead: THandle;
   hWrite: THandle;
   suiStartup: TStartupInfo;
   piProcess: TProcessInformation;
   pBuffer: array[0..CReadBuffer] of AnsiChar;      //<----- update
   dRead: DWord;
   dRunning: DWord;
 begin
   saSecurity.nLength := SizeOf(TSecurityAttributes);
   saSecurity.bInheritHandle := True;
   saSecurity.lpSecurityDescriptor := nil;
   if CreatePipe(hRead, hWrite, @saSecurity, 0) then
   begin
     FillChar(suiStartup, SizeOf(TStartupInfo), #0);
     suiStartup.cb := SizeOf(TStartupInfo);
     suiStartup.hStdInput := hRead;
     suiStartup.hStdOutput := hWrite;
     suiStartup.hStdError := hWrite;
     suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
     suiStartup.wShowWindow := SW_HIDE;
     if CreateProcess(nil, PChar(ACommand + ' ' + AParameters), @saSecurity,
       @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess)
       then
     begin
       repeat
         dRunning  := WaitForSingleObject(piProcess.hProcess, 100);        
         Application.ProcessMessages();
         repeat
           dRead := 0;
           ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);          
           pBuffer[dRead] := #0;
           OemToAnsi(pBuffer, pBuffer);
           AMemo.Lines.Add(String(pBuffer));
         until (dRead < CReadBuffer);
       until (dRunning <> WAIT_TIMEOUT);
       CloseHandle(piProcess.hProcess);
       CloseHandle(piProcess.hThread);    
     end;
     CloseHandle(hRead);
     CloseHandle(hWrite);
   end;
end;

Using CreateProcess allows us to capture the output which is why it comes very useful. I use in in Button's OnClick event and I parse the command and the parameters form a single string:
procedure TForm1.Button1Click(Sender: TObject);
 var
   ACommand, AParameters : string;
   p                     : integer;
 begin
   ACommand := trim(Edit1.Text);
   AParameters := '';
   p := Pos(' ',ACommand);
   if(p>0)then
     begin
       AParameters := trim(Copy(ACommand, p, Length(ACommand)));
       ACommand := trim(Copy(ACommand,1,p-1));
     end;
   if(ACommand='')then
     begin
       ShowMessage('Please type a command into the edit.');
       exit;
     end;
   CaptureConsoleOutput(ACommand, AParameters, Memo1);
 end;

So now you can start more higher level routines on top of this:

function AmIUnderWine: Boolean;
 var
   M: TMemo;
   s: string;
 begin
   Result := False;
   M := TMemo.Create(nil);
   try
     M.Clear;
     CaptureConsoleOutput('/bin/uname','',M);
     s := trim(M.Text);
   //First check if we are in Linux:
     Result := SameText(s,'Linux');
     if not Result then exit;
   //Then check if Wine is installed by checking its version info:
     CaptureConsoleOutput('/usr/bin/wine','--version',M);
     s := trim(M.Text);
     Result := Pos('wine-',s)>0;
   finally
     FreeAndNil(M);
   end;
 end;

And you can probably see the pattern and create much more high level API, like wrapping both the Linux host-level and wine isolation level routines like: CopyFile, DeleteFile, RenameFile, FindFirst, FindNext etc.
With this approach and this methodology you can really implement separate logic for your native Windows applications if you want the Linux/BSD/Mac support beyond what is already achievable through Wine by default. You could make a native Windows application that will feel truly native under those other operating systems.
And I really hope you do, and re-discover the power of cross-platform software with Delphi and Wine!

I have written myself a winehq.pas unit which compiles both under Delphi (VCL and FMX) and Lazarus that provides plenty of API for using in native window apps for wine-awareness. I do want to share the unit here with all of you but I need some time to decide on the licencing (will go with one of open source licences) and code hosting (because no more google code), perhaps I will go with the SourceForge. Decissions, decissions, decissions... so as soon as I get this figured out I will post it right here in the end of this post.




Comments

  1. It would be great if you could run the current IDEs under Wine, or even plug in the current generations of compilers into the Delphi 7 IDE. I still think Borland made a major mistake by not keeping the IDE in Native Delphi code as much as possible. Self hosting is a common paradigm now...

    ReplyDelete
    Replies
    1. I couldn't agree more! Current IDE depends strongly on .NET which to me is a major drawback.
      If Delphi did what Lazarus has done, the future would be super bright for it, as it would eventually also release native IDE for other Platforms. Instead of Always write on Windows, run anywhere, we would have write anywhere, run anywhere.

      Delete
    2. It actually depends on an ancient hardly supported platform (.net 2.0) and an no longer language runtime (J#, microsoft's abortive java variant on.net).

      Delete
    3. Yes apparently that is true, as I looked into "Delphi XE8" entry listed in WineHQ App DB: https://appdb.winehq.org/objectManager.php?sClass=version&iId=32991

      The extra steps required mention:
      "Win32 architecture and installed dotnet20 ie7 msxml3.
      After download from Microsoft site .NET Framework 3.5 with SP1 use manual installation. After installing showed message with installation problem. It's ok. Framework is installed correctly.
      It's all required for start installer.
      After installing required components we can start installer of RAD Studio XE8."

      Delete
  2. Krzysztof, have you made winehq.pas available yet?

    ReplyDelete

Post a Comment

Popular posts from this blog

How I use Linux to write software for multiple target platforms

Easy containers on Ubuntu Touch with qemu-debootstrap