OverviewBrief overview of Wine's architecture...Basic Overview
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 Overview
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
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
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
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 based on (see below)
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).)
In earlier versions of Wine the master socket mentioned
above was actually created in the configuration directory;
either your home directory's /wine
subdirectory or wherever the WINEPREFIX
environment variable points>. Since that might not be possible
the socket is actually created within the /tmp
directory with a name that reflects the configuration directory.
This means that there can actually be several separate copies of
the wineserver running; one per combination of user and
configuration directory. Note that you should not have several
users using the same configuration directory at the same time;
they will have different copies of the wineserver running and
this could well lead to problems with the registry information
that they are sharing.
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).
Relays, Thunks, and DLL descriptors
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.
Core and non-core DLLs
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.
Module OverviewKERNEL ModuleNeeds some content...GDI ModuleX Windows System interface
The X libraries used to implement X clients (such as Wine)
do not work properly if multiple threads access the same
display concurrently. It is possible to compile the X
libraries to perform their own synchronization (initiated
by calling XInitThreads()). However,
Wine does not use this approach. Instead Wine performs its
own synchronization using the
wine_tsx11_lock() / wine_tsx11_unlock()
functions. This locking protects library access
with a critical section, and also arranges things so that
X libraries compiled without
(eg. with global errno variable) will
work with Wine.
In the past, all calls to X used to go through a wrapper called
TSX...() (for "Thread Safe X ...").
While it is still being used in the code, it's inefficient
as the lock is potentially aquired and released unnecessarily.
New code should explicitly aquire the lock.
USER Module
USER implements windowing and messaging subsystems. It also
contains code for common controls and for other
miscellaneous stuff (rectangles, clipboard, WNet, etc).
Wine USER code is located in windows/,
controls/, and
misc/ directories.
Windowing subsystemwindows/win.cwindows/winpos.c
Windows are arranged into parent/child hierarchy with one
common ancestor for all windows (desktop window). Each
window structure contains a pointer to the immediate
ancestor (parent window if WS_CHILD
style bit is set), a pointer to the sibling (returned by
GetWindow(..., GW_NEXT)), a pointer
to the owner window (set only for popup window if it was
created with valid hwndParent
parameter), and a pointer to the first child window
(GetWindow(.., GW_CHILD)). All popup
and non-child windows are therefore placed in the first
level of this hierarchy and their ancestor link
(wnd->parent) points to the desktop
window.
Desktop window - root window
| \ `-.
| \ `-.
popup -> wnd1 -> wnd2 - top level windows
| \ `-. `-.
| \ `-. `-.
child1 child2 -> child3 child4 - child windows
Horizontal arrows denote sibling relationship, vertical
lines - ancestor/child. To summarize, all windows with the
same immediate ancestor are sibling windows, all windows
which do not have desktop as their immediate ancestor are
child windows. Popup windows behave as topmost top-level
windows unless they are owned. In this case the only
requirement is that they must precede their owners in the
top-level sibling list (they are not topmost). Child
windows are confined to the client area of their parent
windows (client area is where window gets to do its own
drawing, non-client area consists of caption, menu,
borders, intrinsic scrollbars, and
minimize/maximize/close/help buttons).
Another fairly important concept is
z-order. It is derived from the
ancestor/child hierarchy and is used to determine
"above/below" relationship. For instance, in the example
above, z-order is
child1->popup->child2->child3->wnd1->child4->wnd2->desktop.
Current active window ("foreground window" in Win32) is
moved to the front of z-order unless its top-level
ancestor owns popup windows.
All these issues are dealt with (or supposed to be) in
windows/winpos.c with
SetWindowPos() being the primary
interface to the window manager.
Wine specifics: in default and managed mode each top-level
window gets its own X counterpart with desktop window
being basically a fake stub. In desktop mode, however,
only desktop window has an X window associated with it.
Also, SetWindowPos() should
eventually be implemented via
Begin/End/DeferWindowPos() calls and
not the other way around.
Visible region, clipping region and update regionwindows/dce.cwindows/winpos.cwindows/painting.c
________________________
|_________ | A and B are child windows of C
| A |______ |
| | | |
|---------' | |
| | B | |
| | | |
| `------------' |
| C |
`------------------------'
Visible region determines which part of the window is
not obscured by other windows. If a window has the
WS_CLIPCHILDREN style then all
areas below its children are considered invisible.
Similarly, if the WS_CLIPSIBLINGS
bit is in effect then all areas obscured by its siblings
are invisible. Child windows are always clipped by the
boundaries of their parent windows.
B has a WS_CLIPSIBLINGS style:
. ______
: | |
| ,-----' |
| | B | - visible region of B
| | |
: `------------'
When the program requests a display
context (DC) for a window it can specify
an optional clipping region that further restricts the
area where the graphics output can appear. This area is
calculated as an intersection of the visible region and
a clipping region.
Program asked for a DC with a clipping region:
______
,--|--. | . ,--.
,--+--' | | : _: |
| | B | | => | | | - DC region where the painting will
| | | | | | | be visible
`--|-----|---' : `----'
`-----'
When the window manager detects that some part of the window
became visible it adds this area to the update region of this
window and then generates WM_ERASEBKGND and
WM_PAINT messages. In addition,
WM_NCPAINT message is sent when the
uncovered area intersects a nonclient part of the window.
Application must reply to the WM_PAINT
message by calling the
BeginPaint()/EndPaint()
pair of functions. BeginPaint() returns a DC
that uses accumulated update region as a clipping region. This
operation cleans up invalidated area and the window will not
receive another WM_PAINT until the window
manager creates a new update region.
A was moved to the left:
________________________ ... / C update region
|______ | : .___ /
| A |_________ | => | ...|___|..
| | | | | : | |
|------' | | | : '---'
| | B | | | : \
| | | | : \
| `------------' | B update region
| C |
`------------------------'
Windows maintains a display context cache consisting of
entries that include the DC itself, the window to which
it belongs, and an optional clipping region (visible
region is stored in the DC itself). When an API call
changes the state of the window tree, window manager has
to go through the DC cache to recalculate visible
regions for entries whose windows were involved in the
operation. DC entries (DCE) can be either private to the
window, or private to the window class, or shared
between all windows (Windows 3.1 limits the number of
shared DCEs to 5).
Messaging subsystemwindows/queue.cwindows/message.c
Each Windows task/thread has its own message queue - this
is where it gets messages from. Messages can be:
generated on the fly (WM_PAINT,
WM_NCPAINT,
WM_TIMER)
created by the system (hardware messages)
posted by other tasks/threads (PostMessage)
sent by other tasks/threads (SendMessage)
Message priority:
First the system looks for sent messages, then for posted
messages, then for hardware messages, then it checks if
the queue has the "dirty window" bit set, and, finally, it
checks for expired timers. See
windows/message.c.
From all these different types of messages, only posted
messages go directly into the private message queue.
System messages (even in Win95) are first collected in the
system message queue and then they either sit there until
Get/PeekMessage gets to process them
or, as in Win95, if system queue is getting clobbered, a
special thread ("raw input thread") assigns them to the
private queues. Sent messages are queued separately and
the sender sleeps until it gets a reply. Special messages
are generated on the fly depending on the window/queue
state. If the window update region is not empty, the
system sets the QS_PAINT bit in the
owning queue and eventually this window receives a
WM_PAINT message
(WM_NCPAINT too if the update region
intersects with the non-client area). A timer event is
raised when one of the queue timers expire. Depending on
the timer parameters DispatchMessage
either calls the callback function or the window
procedure. If there are no messages pending the
task/thread sleeps until messages appear.
There are several tricky moments (open for discussion) -
System message order has to be honored and messages
should be processed within correct task/thread
context. Therefore when Get/PeekMessage encounters
unassigned system message and this message appears not
to be for the current task/thread it should either
skip it (or get rid of it by moving it into the
private message queue of the target task/thread -
Win95, AFAIK) and look further or roll back and then
yield until this message gets processed when system
switches to the correct context (Win16). In the first
case we lose correct message ordering, in the second
case we have the infamous synchronous system message
queue. Here is a post to one of the OS/2 newsgroup I
found to be relevant:
by David Charlap
" Here's the problem in a nutshell, and there is no
good solution. Every possible solution creates a
different problem.
With a windowing system, events can go to many
different windows. Most are sent by applications or
by the OS when things relating to that window happen
(like repainting, timers, etc.)
Mouse input events go to the window you click on
(unless some window captures the mouse).
So far, no problem. Whenever an event happens, you
put a message on the target window's message queue.
Every process has a message queue. If the process
queue fills up, the messages back up onto the system
queue.
This is the first cause of apps hanging the GUI. If
an app doesn't handle messages and they back up into
the system queue, other apps can't get any more
messages. The reason is that the next message in
line can't go anywhere, and the system won't skip
over it.
This can be fixed by making apps have bigger private
message queues. The SIQ fix does this. PMQSIZE does
this for systems without the SIQ fix. Applications
can also request large queues on their own.
Another source of the problem, however, happens when
you include keyboard events. When you press a key,
there's no easy way to know what window the
keystroke message should be delivered to.
Most windowing systems use a concept known as
"focus". The window with focus gets all incoming
keyboard messages. Focus can be changed from window
to window by apps or by users clicking on windows.
This is the second source of the problem. Suppose
window A has focus. You click on window B and start
typing before the window gets focus. Where should
the keystrokes go? On the one hand, they should go
to A until the focus actually changes to B. On the
other hand, you probably want the keystrokes to go
to B, since you clicked there first.
OS/2's solution is that when a focus-changing event
happens (like clicking on a window), OS/2 holds all
messages in the system queue until the focus change
actually happens. This way, subsequent keystrokes
go to the window you clicked on, even if it takes a
while for that window to get focus.
The downside is that if the window takes a real long
time to get focus (maybe it's not handling events,
or maybe the window losing focus isn't handling
events), everything backs up in the system queue and
the system appears hung.
There are a few solutions to this problem.
One is to make focus policy asynchronous. That is,
focus changing has absolutely nothing to do with the
keyboard. If you click on a window and start typing
before the focus actually changes, the keystrokes go
to the first window until focus changes, then they
go to the second. This is what X-windows does.
Another is what NT does. When focus changes,
keyboard events are held in the system message
queue, but other events are allowed through. This is
"asynchronous" because the messages in the system
queue are delivered to the application queues in a
different order from that with which they were
posted. If a bad app won't handle the "lose focus"
message, it's of no consequence - the app receiving
focus will get its "gain focus" message, and the
keystrokes will go to it.
The NT solution also takes care of the application
queue filling up problem. Since the system delivers
messages asynchronously, messages waiting in the
system queue will just sit there and the rest of the
messages will be delivered to their apps.
The OS/2 SIQ solution is this: When a
focus-changing event happens, in addition to
blocking further messages from the application
queues, a timer is started. When the timer goes
off, if the focus change has not yet happened, the
bad app has its focus taken away and all messages
targeted at that window are skipped. When the bad
app finally handles the focus change message, OS/2
will detect this and stop skipping its messages.
As for the pros and cons:
The X-windows solution is probably the easiest. The
problem is that users generally don't like having to
wait for the focus to change before they start
typing. On many occasions, you can type and the
characters end up in the wrong window because
something (usually heavy system load) is preventing
the focus change from happening in a timely manner.
The NT solution seems pretty nice, but making the
system message queue asynchronous can cause similar
problems to the X-windows problem. Since messages
can be delivered out of order, programs must not
assume that two messages posted in a particular
order will be delivered in that same order. This
can break legacy apps, but since Win32 always had an
asynchronous queue, it is fair to simply tell app
designers "don't do that". It's harder to tell app
designers something like that on OS/2 - they'll
complain "you changed the rules and our apps are
breaking."
The OS/2 solution's problem is that nothing happens
until you try to change window focus, and then wait
for the timeout. Until then, the bad app is not
detected and nothing is done."
Intertask/interthread
SendMessage. The system has to
inform the target queue about the forthcoming message,
then it has to carry out the context switch and wait
until the result is available. Win16 stores necessary
parameters in the queue structure and then calls
DirectedYield() function.
However, in Win32 there could be several messages
pending sent by preemptively executing threads, and in
this case SendMessage has to
build some sort of message queue for sent messages.
Another issue is what to do with messages sent to the
sender when it is blocked inside its own
SendMessage.
Wine/Windows DLLs
This document mainly deals with the status of current DLL
support by Wine. The Wine ini file currently supports
settings to change the load order of DLLs. The load order
depends on several issues, which results in different settings
for various DLLs.
Pros of Native DLLs
Native DLLs of course guarantee 100% compatibility for
routines they implement. For example, using the native USER
DLL would maintain a virtually perfect and Windows 95-like
look for window borders, dialog controls, and so on. Using
the built-in Wine version of this library, on the other
hand, would produce a display that does not precisely mimic
that of Windows 95. Such subtle differences can be
engendered in other important DLLs, such as the common
controls library COMMCTRL or the common dialogs library
COMMDLG, when built-in Wine DLLs outrank other types in load
order.
More significant, less aesthetically-oriented problems can
result if the built-in Wine version of the SHELL DLL is
loaded before the native version of this library. SHELL
contains routines such as those used by installer utilities
to create desktop shortcuts. Some installers might fail when
using Wine's built-in SHELL.
Cons of Native DLLs
Not every application performs better under native DLLs. If
a library tries to access features of the rest of the system
that are not fully implemented in Wine, the native DLL might
work much worse than the corresponding built-in one, if at
all. For example, the native Windows GDI library must be
paired with a Windows display driver, which of course is not
present under Intel Unix and Wine.
Finally, occasionally built-in Wine DLLs implement more
features than the corresponding native Windows DLLs.
Probably the most important example of such behavior is the
integration of Wine with X provided by Wine's built-in USER
DLL. Should the native Windows USER library take load-order
precedence, such features as the ability to use the
clipboard or drag-and-drop between Wine windows and X
windows will be lost.
Deciding Between Native and Built-In DLLs
Clearly, there is no one rule-of-thumb regarding which
load-order to use. So, you must become familiar with
what specific DLLs do and which other DLLs or features
a given library interacts with, and use this information
to make a case-by-case decision.
Load Order for DLLs
Using the DLL sections from the wine configuration file, the
load order can be tweaked to a high degree. In general it is
advised not to change the settings of the configuration
file. The default configuration specifies the right load
order for the most important DLLs.
The default load order follows this algorithm: for all DLLs
which have a fully-functional Wine implementation, or where
the native DLL is known not to work, the built-in library
will be loaded first. In all other cases, the native DLL
takes load-order precedence.
The DefaultLoadOrder from the
[DllDefaults] section specifies for all DLLs which version
to try first. See manpage for explanation of the arguments.
The [DllOverrides] section deals with DLLs, which need a
different-from-default treatment.
The [DllPairs] section is for DLLs, which must be loaded in
pairs. In general, these are DLLs for either 16-bit or
32-bit applications. In most cases in Windows, the 32-bit
version cannot be used without its 16-bit counterpart. For
Wine, it is customary that the 16-bit implementations rely
on the 32-bit implementations and cast the results back to
16-bit arguments. Changing anything in this section is bound
to result in errors.
For the future, the Wine implementation of Windows DLL seems
to head towards unifying the 16 and 32 bit DLLs wherever
possible, resulting in larger DLLs. They are stored in the
dlls/ subdirectory using the 32-bit
name.
Understanding What DLLs Do
The following list briefly describes each of the DLLs
commonly found in Windows whose load order may be modified
during the configuration and compilation of Wine.
(See also ./DEVELOPER-HINTS or the
dlls/ subdirectory to see which DLLs
are currently being rewritten for Wine)
ADVAPI32.DLL: 32-bit application advanced programming interfaces
like crypto, systeminfo, security and event logging
AVIFILE.DLL: 32-bit application programming interfaces for the
Audio Video Interleave (AVI) Windows-specific
Microsoft audio-video standard
COMMCTRL.DLL: 16-bit common controls
COMCTL32.DLL: 32-bit common controls
COMDLG32.DLL: 32-bit common dialogs
COMMDLG.DLL: 16-bit common dialogs
COMPOBJ.DLL: OLE 16- and 32-bit compatibility libraries
CRTDLL.DLL: Microsoft C runtime
DCIMAN.DLL: 16-bit
DCIMAN32.DLL: 32-bit display controls
DDEML.DLL: DDE messaging
D3D*.DLL DirectX/Direct3D drawing libraries
DDRAW.DLL: DirectX drawing libraries
DINPUT.DLL: DirectX input libraries
DISPLAY.DLL: Display libraries
DPLAY.DLL, DPLAYX.DLL: DirectX playback libraries
DSOUND.DLL: DirectX audio libraries
GDI.DLL: 16-bit graphics driver interface
GDI32.DLL: 32-bit graphics driver interface
IMAGEHLP.DLL: 32-bit IMM API helper libraries (for PE-executables)
IMM32.DLL: 32-bit IMM API
IMGUTIL.DLL:
KERNEL32.DLL 32-bit kernel DLL
KEYBOARD.DLL: Keyboard drivers
LZ32.DLL: 32-bit Lempel-Ziv or LZ file compression
used by the installshield installers (???).
LZEXPAND.DLL: LZ file expansion; needed for Windows Setup
MMSYSTEM.DLL: Core of the Windows multimedia system
MOUSE.DLL: Mouse drivers
MPR.DLL: 32-bit Windows network interface
MSACM.DLL: Core of the Addressed Call Mode or ACM system
MSACM32.DLL: Core of the 32-bit ACM system
Audio Compression Manager ???
MSNET32.DLL 32-bit network APIs
MSVFW32.DLL: 32-bit Windows video system
MSVIDEO.DLL: 16-bit Windows video system
OLE2.DLL: OLE 2.0 libraries
OLE32.DLL: 32-bit OLE 2.0 components
OLE2CONV.DLL: Import filter for graphics files
OLE2DISP.DLL, OLE2NLS.DLL: OLE 2.1 16- and 32-bit interoperability
OLE2PROX.DLL: Proxy server for OLE 2.0
OLE2THK.DLL: Thunking for OLE 2.0
OLEAUT32.DLL 32-bit OLE 2.0 automation
OLECLI.DLL: 16-bit OLE client
OLECLI32.DLL: 32-bit OLE client
OLEDLG.DLL: OLE 2.0 user interface support
OLESVR.DLL: 16-bit OLE server libraries
OLESVR32.DLL: 32-bit OLE server libraries
PSAPI.DLL: Proces Status API libraries
RASAPI16.DLL: 16-bit Remote Access Services libraries
RASAPI32.DLL: 32-bit Remote Access Services libraries
SHELL.DLL: 16-bit Windows shell used by Setup
SHELL32.DLL: 32-bit Windows shell (COM object?)
TAPI/TAPI32/TAPIADDR: Telephone API (for Modems)
W32SKRNL: Win32s Kernel ? (not in use for Win95 and up!)
WIN32S16.DLL: Application compatibility for Win32s
WIN87EM.DLL: 80387 math-emulation libraries
WINASPI.DLL: Advanced SCSI Peripheral Interface or ASPI libraries
WINDEBUG.DLL Windows debugger
WINMM.DLL: Libraries for multimedia thunking
WING.DLL: Libraries required to "draw" graphics
WINSOCK.DLL: Sockets APIs
WINSPOOL.DLL: Print spooler libraries
WNASPI32.DLL: 32-bit ASPI libraries
WSOCK32.DLL: 32-bit sockets APIs