Brief overview of Wine's architecture...
Written by Ove Kåven <ovek@arcticnet.no>
With the fundamental architecture of Wine stabilizing, and people starting to think that we might soon be ready to actually release this thing, it may be time to take a look at how Wine actually works and operates.
Wine is often used as a recursive acronym, standing for "Wine Is Not an Emulator". Sometimes it is also known to be used for "Windows Emulator". In a way, both meanings are correct, only seen from different perspectives. The first meaning says that Wine is not a virtual machine, it does not emulate a CPU, and you are not supposed to install neither Windows nor any Windows device drivers on top of it; rather, Wine is an implementation of the Windows API, and can be used as a library to port Windows applications to Unix. The second meaning, obviously, is that to Windows binaries (.exe files), Wine does look like Windows, and emulates its behaviour and quirks rather closely.
Note: The "Emulator" perspective should not be thought of as if Wine is a typical inefficient emulation layer that means Wine can't be anything but slow - the faithfulness to the badly designed Windows API may of course impose a minor overhead in some cases, but this is both balanced out by the higher efficiency of the Unix platforms Wine runs on, and that other possible abstraction libraries (like Motif, GTK+, CORBA, etc) has a runtime overhead typically comparable to Wine's.
Win16 and Win32 applications have different requirements; for example, Win16 apps expect cooperative multitasking among themselves, and to exist in the same address space, while Win32 apps expect the complete opposite, i.e. preemptive multitasking, and separate address spaces.
Wine now deals with this issue by launching a separate Wine process for each Win32 process, but not for Win16 tasks. Win16 tasks are now run as different intersynchronized threads in the same Wine process; this Wine process is commonly known as a WOW process, referring to a similar mechanism used by Windows NT. Synchronization between the Win16 tasks running in the WOW process is normally done through the Win16 mutex - whenever one of them is running, it holds the Win16 mutex, keeping the others from running. When the task wishes to let the other tasks run, the thread releases the Win16 mutex, and one of the waiting threads will then acquire it and let its task run.
The Wine server is among the most confusing concepts in Wine. What is its function in Wine? Well, to be brief, it provides Inter-Process Communication (IPC), synchronization, and process/thread management. When the wineserver launches, it creates a Unix socket for the current host in your home directory's .wine subdirectory (or wherever the WINEPREFIX environment variable points) - all Wine processes launched later connects to the wineserver using this socket. (If a wineserver was not already running, the first Wine process will start up the wineserver in auto-terminate mode (i.e. the wineserver will then terminate itself once the last Wine process has terminated).)
Every thread in each Wine process has its own request buffer, which is shared with the wineserver. When a thread needs to synchronize or communicate with any other thread or process, it fills out its request buffer, then writes a command code through the socket. The wineserver handles the command as appropriate, while the client thread waits for a reply. In some cases, like with the various WaitFor synchronization primitives, the server handles it by marking the client thread as waiting and does not send it a reply before the wait condition has been satisfied.
The wineserver itself is a single and separate process and does not have its own threading - instead, it is built on top of a large poll() loop that alerts the wineserver whenever anything happens, such as a client having sent a command, or a wait condition having been satisfied. There is thus no danger of race conditions inside the wineserver itself - it is often called upon to do operations that look completely atomic to its clients.
Because the wineserver needs to manage processes, threads, shared handles, synchronization, and any related issues, all the clients' Win32 objects are also managed by the wineserver, and the clients must send requests to the wineserver whenever they need to know any Win32 object handle's associated Unix file descriptor (in which case the wineserver duplicates the file descriptor, transmits it to the client, and leaves it to the client to close the duplicate when the client has finished with it).
The Wine server cannot do everything that needs to be done behind the application's back, considering that it's not threaded (so cannot do anything that would block or take any significant amount of time), nor does it share the address space of its client threads. Thus, a special event loop also exists in each Win32 process' own address space, but handled like one of the process' own threads. This special thread is called the service thread, and does things that it wouldn't be appropriate for the wineserver to do. For example, it can call the application's asynchronous system timer callbacks every time a timer event is signalled (the wineserver handles the signalling, of course).
One important function of the service thread is to support the X11 driver's event loop. Whenever an event arrives from the X server, the service thread wakes up and sees the event, processes it, and posts messages into the application's message queues as appropriate. But this function is not unique - any number of Wine core components can install their own handlers into the service thread as necessary, whenever they need to do something independent of the application's own event loop. (At the moment, this includes, but is not limited to, multimedia timers, serial comms, and winsock async selects.)
The implementation of the service thread is in scheduler/services.c.
Loading a Windows binary into memory isn't that hard by itself, the hard part is all those various DLLs and entry points it imports and expects to be there and function as expected; this is, obviously, what the entire Wine implementation is all about. Wine contains a range of DLL implementations. Each of the implemented (or half-implemented) DLLs (which can be found in the dlls/ directory) need to make themselves known to the Wine core through a DLL descriptor. These descriptors point to such things as the DLL's resources and the entry point table.
The DLL descriptor and entry point table is generated by the winebuild tool (previously just named build), taking DLL specification files with the extension .spec as input. The output file contains a global constructor that automatically registers the DLL's descriptor with the Wine core at runtime.
Once an application module wants to import a DLL, Wine will look through its list of registered DLLs (if it's not registered, it will look for it on disk). (Failing that, it will look for a real Windows .DLL file to use, and look through its imports, etc.) To resolve the module's imports, Wine looks through the entry point table and finds if it's defined there. (If not, it'll emit the error "No handler for ...", which, if the application called the entry point, is a fatal error.)
Since Wine is 32-bit code itself, and if the compiler supports Windows' calling convention, stdcall (gcc does), Wine can resolve imports into Win32 code by substituting the addresses of the Wine handlers directly without any thunking layer in between. This eliminates the overhead most people associate with "emulation", and is what the applications expect anyway.
However, if the user specified --debugmsg +relay, a thunk layer is inserted between the application imports and the Wine handlers; this layer is known as "relay" because all it does is print out the arguments/return values (by using the argument lists in the DLL descriptor's entry point table), then pass the call on, but it's invaluable for debugging misbehaving calls into Wine code. A similar mechanism also exists between Windows DLLs - Wine can optionally insert thunk layers between them, by using --debugmsg +snoop, but since no DLL descriptor information exists for non-Wine DLLs, this is less reliable and may lead to crashes.
For Win16 code, there is no way around thunking - Wine needs to relay between 16-bit and 32-bit code. These thunks switch between the app's 16-bit stack and Wine's 32-bit stack, copies and converts arguments as appropriate, and handles the Win16 mutex. Suffice to say that the kind of intricate stack content juggling this results in, is not exactly suitable study material for beginners.
Wine must at least completely replace the "Big Three" DLLs (KERNEL/KERNEL32, GDI/GDI32, and USER/USER32), which all other DLLs are layered on top of. But since Wine is (for various reasons) leaning towards the NT way of implementing things, the NTDLL is another core DLL to be implemented in Wine, and many KERNEL32 and ADVAPI32 features will be implemented through the NTDLL. The wineserver and the service thread provide the backbone for the implementation of these core DLLs, and integration with the X11 driver (which provides GDI/GDI32 and USER/USER32 functionality along with the Windows standard controls). All non-core DLLs, on the other hand, are expected to only use routines exported by other DLLs (and none of these backbone services directly), to keep the code base as tidy as possible. An example of this is COMCTL32 (Common Controls), which should only use standard GDI32- and USER32-exported routines.