Besides the other hardware components and as already pointed out, the software itself is a huge source of compatibility issues.
This is nothing new, you cannot always open a proprietary file format with a different application.
The format of the Linux executables is ELF, that of Windows is PE and macOS uses mach-o.
Read the linked pages and see how much the formats share and differ.
However, nothing stops an OS from supporting multiple executable formats (Linux actually does and so does Windows, probably also macOS).
If we ignore the file format issue, there's still a library problem. A program made for Linux may be written to use Linux-only libraries.
Even when there is a port, you need recompilation due to different calling conventions.
For example, calling a C standard function under Windows looks like this:
lea rdx, [REL filename]
lea rcx, [REL mode]
call fopen
Under Linux you have to use:
lea rdi, [REL filename]
lea rsi, [REL mode]
call fopen
If you ship all the needed libraries (with the right calling convention) with an application, there's still another problem.
The libraries need to talk to the OS and the way this is done is OS-specific.
For example, 32-bit applications used int 80h
under Linux and int 2eh
under Windows.
You can still fix this by porting the library, it now has to use the calling convention of the native OS but the interface of the host OS (e.g. the Linux SYS V ABI but the Windows syscall numbers).
This is already getting exponentially complex in the number of variables but, the real problem is not how to call the system calls, it's the interface they offer.
Trivially, the GUI system is pretty much a monolithic piece of Windows but under Linux you can use any Window manager you like. So if you have a program that calls a function that is specific to the Windows GUI, how do you translate that to Linux?
High-level operations, like create a window, can be translated (this is what libraries like Gtk and Qt do) but low-level operations (say: RegisterClassEx
) do not map one to one.
This is getting even messier if you consider how different are some kernel interfaces.
For example, the uid/gid/permissions thing in Linux and the SID/DACL thing in Windows.
However, you can still fix this: you have to reimplement the other OS interface. This is a massive amount of work, but can be side skipped with a trick: run the other OS in a virtual machine.
This is how WLS2 works and it's why it allows you to use Linux binary under Windows.
Note that WLS2 is possible because:
- Linux is open source, so can be modified easily.
- Linux legally allows that.
- Microsoft thought this was profitable to invest a large number of man-hours on that project.
This is not always the case, Linux cannot run a Windows kernel in a VM to support Windows binaries, it has to emulate it (see: Wine) and that's akin to reimplementing a big part of Windows!