First, most modern computers have a microprocessor that executes instructions. All programs are basically written in CPU instructions (assembly language, which
assembles down to bare machine code). Forget about what you might know of programming languages like C or Java. Most of them (at some point) also compile down to machine code. So, don't think about them for now.
Every CPU's architecture (how the transistors inside are all "wired" up) is different, so instructions vary per processor as well. The x86 architecture is common in home PCs, the ARM architecture is most common in phones. There are other architectures but literally, most people don't care. These are the two main ones.
The solution? You write a computer program that acts like the foreign CPU, reads the foreign instructions, and essentially "translates" them to your computer CPU's own instructions. This program is what we refer to as an "emulator". But emulators are extremely complex computationally. They literally have to act like the foreign CPU in order to do "the translations". That takes time, which means that your emulated CPU will be substantially slower than your real physical CPU.
The smart among you might ask, "Okay, so, what about a situation where I install Linux on my former Windows PC and just run the program there. Same CPU. No need to emulate, right?"
Well yes, but actually, no.
Programs aren't just written with CPU instructions. That's an oversimplification. Everything
is an instruction but those instructions might also programmatically be interacting with the host operating system the program runs on. The operating system standardizes a lot. It provides programmers with Application Programming Interfaces (APIs) they use to do useful things, like actually "draw" the interface of the program to your screen. "Listen" to your keyboard input and clicks. "Interact" with the Internet. So on and so forth. Essentially, the programmer "calls" (that's the real term) parts of the API to make the commands.
Here is a real API call because I think those help a lot (taken straight out of the Microsoft WinAPI docs):
C++:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
This looks like a lot. But all we have is this in reality
CreateWindow(). That's literally the summary of the above. All the extra code you see is inside the paranthesis. Those are "parameters" of the API call, each seperated by a comma. They provide additional "information" on what the caller "wants" to do.
The WinAPI just doesn't exist in Linux. Linux has no "idea" what
CreateWindow() "means". Linux has it's own APIs, and naturally, Linux programs are written with those APIs.
That's where Wine comes in. It's a "compatibility layer" in that it basically provides (some) of those missing APIs. It takes every API call the Windows program makes and calls the equivalent Linux API. But it's not that simple, of course. Between those two steps, it needs to implement some code in order for the "translation" to be succesful.
And of course, it often isn't perfect. Either the APIs it implements aren't perfectly accurate, or they are missing altogether. Hence why you get crashes, programs not working, etc. It's fast because you execute directly on the machine, but it's functionally incomplete because it's practically impossible for the Wine team (which work for free basically) to do everything in an ideal way.
So, what about virtual machines like ones created with VirtualBox? Those are an interim solution between what I just talked about. They basically create a virtual computer that is only partially emulated. Crucially, the CPU is not emulated at all. Any CPU instructions that occur inside the virtual machine are passed to the real computer CPU to be executed. The only things emulated are other computer parts, like the storage controller (SATA), network interfaces, so on.
The emulators that I was talking about earlier are identical except in the part where they must also emulate the CPU, which is what mainly adds all the slowness.
The idea with virtual machines are, you just run the operating system your app is compatible with and use it there as normal. But it's not convenient at all because you essentially run two operating systems, and that takes resources. You must also switch between the two operating systems frequently, which is just annoying. But you do gain accuracy. Your app is basically guaranteed to work unless it specifically refuses to run in a virtual machine.
Do bare in mind that not all emulators emulate the whole operating system to run an app. MacOS's Rosetta 2 emulator is efficient because it's macOS x86 to macOS ARM and also because it works with Ahead of Time compilation. I won't get into what that means, but do know that these sort of emulators are super rare. In general, emulation is largely irrelevant to this thread topic. I imagine most people just want to run Windows programs on their Linuxes boxes here.
For me personally, I don't want to deal with all that at all. I do run Linux and I just use Linux apps with it. I do have a Windows virtual machine for rare edge cases.