Writing .exe programs for Linux

This page intends to document the different techniques I used in my Win32 programs to make them Linux friendly.

The need came after the two historical Linux programs: "exe-thumbnailer" and its successor "icoextract", stopped working on Ubuntu 24.x so I decided to write my own routines.

Table of Content

Detecting if we are on a Unix system

The first of all things that our Win32 .exe programs need to do is to check and adapt themselves if we are on a Unix system running through Wine. This is done by getting the procedure address - via the Win32 API GetProcAddress() - of the Wine-only function "wine_get_version()" contained in the Windows dll "ntdll.dll", loaded through the Win32 API GetModuleHandle(). If that procedure exists (its address is not null) then we are on Unix, else we are on Windows.

GetProcAddress

The full code for the Unix detection can be found in unix.inc, in the IsOnUnix() function.

Extracting .exe icons

For a Windows .exe to show its icon in the Linux file explorer, Linux Dock, or Linux App Menu, the first step is to extract its icon. Icons are resources (.rsrc) in the .exe binary that are accessible via Win32 APIs LoadLibrary() and EnumResourceNames(). Once extracted, you obtain a Windows .ico file which might not be recognized by Linux Desktop environments. You need to convert the .ico to a PNG. For this I am using Win32 GDI+ API. Finally, place the .png in /home/user/.local/share/icons for use in the coming sections.

Portable Executable Structure

The full code for the .exe icon extraction can be found in icostrip.inc, in the ICOstrip() function.

Showing an .exe icon in Linux file explorer

To display an icon/image on a file in the Linux file explorer e.g. in Nautilus, the following gio <?> command can be used:

	$> gio set -t 'string' '/path/to/file' 'metadata::custom-icon' 'file:///path/to/icon'
The /path/to/icon is of course obtained from the previous step linking to /home/user/.local/share/icons/myprogram.png. The /path/to/file needs to be a Unix path but since we are running inside a Win32 program via Wine we need to convert it, see Translating between Unix and Windows paths.
Here is Nautilus in normal conditions, always showing the same (default) icon for all .exe files:

Nautilus without .exe icons

And here is Nautilus after using the gio method from inside my programs (at startup when they run): (see section Calling native Unix commands from a Win32 .exe on how we are able to call gio from inside the Win32 .exe.)

Nautilus with .exe icons

The full code for revealing an .exe icon can be found in exe2unix.inc, in the IcoStrip2Unix() and FileIco2Unix() functions.

Showing an .exe icon in the Linux Dock

The Linux Dock conveniently lists the pinned Apps and the currently running Apps. On Ubuntu 24.04 it looks like this:

Dock without .exe icons

As you can see, it only shows icons and not program names, so all .exe look the same! In order to display the .exe icons in the dock, the first step is of course to extract the .exe icon and convert it to PNG (Extracting .exe icons). The next trick is to write a .desktop file in /home/user/.local/share/applications. The .desktop entry specification can be found at https://specifications.freedesktop.org/desktop-entry-spec/latest/ The key element to add in the .desktop is the Startup WM Class (see Looking Glass debugger) that identifies a running program. For Win32 .exe via Wine it's particularly easy: the WM Class is the full name of the exe in small caps e.g. vlc playlist manager.exe. As an example here is the .desktop for my program img-rnd:
	[Desktop Entry]
	Name=img-rnd
	StartupWMClass=img-rnd.exe
	NoDisplay=true
	Type=Application
	Terminal=false
	StartupNotify=true
	Icon=img-rnd

The NoDisplay=true typically allows to NOT register the program in the Linux App Menu. Concerning the icon, the main differences with the gio method (used to display an .exe icon in the file explorer) is that:

  1. only PNG images are supported by .desktop entries (gio could use .ico files, although I made it use .png for homogeneity)
  2. the icon image needs to be listed without its extension: Icon=img-rnd

Here is the Linux Dock after applying this technique to my programs: much better!

Dock with .exe icons

The full code for .exe icons in the Dock can be found in exe2unix.inc, in the Exe2Unix() function.

Registering an .exe in the Linux App Menu

The principle is the exact same as the previous section, with two modifications:

  1. the NoDisplay=true must be removed from the .desktop entry, of course
  2. and we need to add an Exec value to launch the .exe through Wine

That last part was tricky to achieve, but here is how it looks for my program linkmaker:

	[Desktop Entry]
	Name=linkmaker
	StartupWMClass=linkmaker.exe
	Exec=env WINEPREFIX="/home/user/.wine" wine C:\\\\Apps\\\\linkmaker.exe
	Type=Application
	Terminal=false
	StartupNotify=true
	Icon=linkmaker
You can note that the Windows/Wine path to the .exe in the Exec value has its backslashes protected twice. If the .exe name contained spaces, they should be protected too by a double backslash, e.g.: C:\\\\Apps\\\\VLC\\ Playlist\\ Manager.exe.

And here is the Linux App Menu in Ubuntu 24.04, once we registered our Win32 .exe programs that way:

linux App Menu

The full code for .exe registration can be found in exe2unix.inc, in the Me2Unix() function.

Translating between Unix and Windows paths

Since were are running our .exe programs through Wine the drives and paths are different from native Linux.

Unix-Wine paths

If you are a Wine user, you may know that the Z:\ drive in Wine is mapped to the Unix root /, and the C:\ drive to /home/user/.wine/drive_c (the Wine installation path, where all Windows files are stored). So for example if you want to access the Shell Script interpreter /usr/bin/sh from inside our Win32 .exe, e.g. for testing its existence, this will be done through Z:\usr\bin\sh.

Hence the conversion from Unix to Wine path is trivial:

Unix Wine
/path/to/file Z:\path\to\file
~/path/to/file Z:\home\user\path\to\file

The conversion from Wine to Unix is a bit more complicated:

Wine Unix
Z:\path\to\file /path/to/file
C:\path\to\file /home/user/.wine/drive_c/path/to/file
F:\path\to\file Replace "F:" with the Wine mapping for this drive

The last part, for drives different from "C:" and "Z:", needs to rely on our method for mapping Wine drives to Unix paths. We call GetWineDrivesMapping() at the start of our application, then we find the correspondance between the drive and Unix path and apply the change.

Finally, if the drive is not registered as a Wine drive somehow, we fall back on a manual correspondance "X:" (Wine) >> "/home/user/.wine/dosdevices/x:" (Unix).

The full code for paths translation can be found in unix.inc, in the UnixPath() and WinePath() functions.

Mapping Wine drives to Unix paths

As seen in the previous section, we need to get Wine mappings from drives to Unix paths, in order to access files from both worlds. Some mappings are done natively by Wine, others can be added manually by the user by typing the command winecfg in the Linux terminal and going to the "Drives" tab:

Wine drives

Adding additional drive mappings is a must if you want your Win32 .exe program to access files from an external Hard Disk Drive. This is typically what I did for my portable HDD containing all my favorite TV Shows: mapping it to F: so that my program VLC Playlist Manager can run undisturbed.

The way to get all drive mappings is through a Unix command, for which we need the output: see getting Unix command output from a Win32 .exe. The command in question, to be typed from the user home, is:

	$> ll .wine/dosdevices
(or ls -al if you don't have the ll alias)

The result of the command looks like this:

ll .wine/dosdevices

From there, a simple parser allows to map relevant drives to their Unix path. I used a global array dosdevices() to store the mappings, in the form "d:/media/user/myhdd".

The full code for drives mapping can be found in unix.inc, in the GetWineDrivesMapping() function.

Calling native Unix commands from a Win32 .exe

Where our Win32 .exe can become very powerful is when it is able to launch native Unix commands and break the walls imposed by Wine. This is a feature we use a lot: whether to call gio in order to assign an icon to a file (Showing an .exe icon in Linux file explorer), or using ls to list Wine drive mappings in ".wine/dosdevices", or calling the native Linux VLC Media Player with its Unix path, targetting a (Win32 .exe) generated playlist...

The way we do it is via a dynamically generated Shell script. We use the standard Unix command language interpreter Shell (sh): https://www.man7.org/linux/man-pages/man1/sh.1p.html

Shell script (sh)

The Shell allows us to do practically anything in the Linux world, even taking advantage of the native ENVIRONMENT variables (unaccessible from Wine). Native ENV vars allow us to type e.g. sh ~/myscript.sh in a script, instead of the fully resolved /usr/bin/sh /home/user/myscript.sh. Even more powerful: we can identify if any Unix binary is present, as well as its location, via the native command whereis unix_bin and act in consequence.

Our dynamic script is written in the user home, as ~/dues.sh (dues = dynamic unix execution script). After writing it, we make it executable in order to run it in Linux, this is done via the following Wine command:

	wine start /exec /usr/bin/chmod +x /home/user/dues.sh
(as indicated previously, Wine does not have access to ENV so we need to write fully resolved paths.)

We overwrite the ~/dues.sh script every time we run a different command, and we call wine start /exec to launch it.

The full code for native Unix calls can be found in unix.inc, in the CreateExecSh() and UnixShell() functions.

Getting Unix command output from a Win32 .exe

The biggest part being done: calling native Unix commands from a Win32 .exe, we use this technique to run a command and redirect its output to a file with the > operator:

Linux pipe

In our case, we use the temporary file ~/dues.log in the user home. After we launch the ~/dues.sh script, we poll on ~/dues.log every 250 ms, with a timeout of 3s: if the timeout is reached, we consider the command failed and there was no outptut ; on the contrary if the file ~/dues.log contains a result, we extract it and return it as the output of the Unix command. Finally we clean-up the ~/dues.log file.

The full code for Unix output can be found in unix.inc, in the GetUnixRet() function.

Creating Windows shortcuts on Linux

If you double-click on an .exe file in Ubuntu 24.04 / Nautilus, nothing happens. To launch any .exe you need to right-click it then pick "Open with..." and double-click on "Wine"... That is tedious to say the least. The big advantage of Windows shortcuts is that when Wine is installed, they can be launched via double-click.

You can create a symbolic link (symlink) by selecting a file in the file explorer and do Ctrl+Shift+M or using the ln command in a terminal, but not only the symlink on an .exe won't solve the double-click issue, but also the program will only be able to execute from the symlink working directory, which can bring lots of issues. As a matter of fact, Windows shortcuts have an extra field "Start in" which allow to use .dll's, .ini's, and any other resource located around the program, what symlinks cannot do...

linkmaker screenshot

I wrote the linkmaker.exe program for this exact use-case: create Windows shortcuts (.lnk) that launch under Linux via double-click, using all the techniques documented above including showing the .lnk with the icon of the .exe it targets. This is something I use all the time after writing a new utility: easily make a Windows shortcut of it on my Ubuntu desktop for quick access.

Looking Glass debugger

The Looking Glass is a basic debugger integrated in Gnome, see https://wiki.gnome.org/Projects/GnomeShell/LookingGlass . You use it by pressing Alt+F2 then typing lg (for looking glass) then Return, and looking in the Windows tab.

Looking Glass

One important field shown is the wmclass field, used as an identifier by the .desktop files. This is useful when running Linux Apps, but somehow less for Windows executables, as their WM Class is trivial (their name in small caps...)

Coming from Windows, I was used to get the same kind of information from a program called WinSpy from catch22.

WinSpy

Hide HDD folders like "System Volume Information" or "RECYCLER"

Coming from Windows, these folders are automatically hidden on external hard drives, but not on Linux. To do the same on Linux is quite easy actually: just create a text file called .hidden on your external hard drive, and add folders you wish to hide, 1 per line, e.g.:

	found.000
	$RECYCLE.BIN
	RECYCLER
	System Volume Information

After that press Ctrl+H to see/hide hidden files+folders in your Linux file explorer, and hit F5 to refresh the view.